Java基础

1.面向对象的特征

封装、继承、多态

封装

封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法,如果属性不想被外界访问,我们⼤可不必提供⽅法给外界访问。但是如果⼀个类没有提供给外界访问的⽅法,那么 这个类也没有什么意义了。

继承

继承是使⽤已存在的类的定义作为基础建⽴新类的技术,新类的定义可以增加新的数据或新的功 能,也可以⽤⽗类的功能,但不能选择性地继承⽗类。通过使⽤继承我们能够⾮常⽅便地复⽤以 前的代码。

关于继承如下 3 点请记住:

  1. ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性 和⽅法⼦类是⽆法访问,只是拥有。
  2. ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
  3. ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法

多态

所谓多态就是指程序中定义的引⽤变量所指向的具体类型和通过该引⽤变量发出的⽅法调⽤在编 程时并不确定,⽽是在程序运⾏期间才确定,即⼀个引⽤变量到底会指向哪个类的实例对象,该 引⽤变量发出的⽅法调⽤到底是哪个类中实现的⽅法,必须在由程序运⾏期间才能决定。

在 Java 中有两种形式可以实现多态:继承(多个⼦类对同⼀⽅法的重写)和接⼝(实现接⼝并 覆盖接⼝中同⼀⽅法)。

2.基本数据类型

8个:byte、short、int、long、float、double、 char、boolean;

Java基础 - 图1

3.int&Integer

简单的说,如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象。

如果通过==比较Integer与int两个数据类型时,会自动将Integer进行拆包,进行值比较。

如果比较的两边都是Integer,则==比较的是其地址。

4.Math.round(-11.5)

Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五 入的原理是在参数上加 0.5 然后进行下取整。

5.参数传递

java中参数传递是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个 参数被传递到方法中时,参数的值就是对该对象的引用。Integer等传递过程中会自动拆箱。

基本数据类型实现引用传递:

  • 改成长度为1的数组
  • 放在类中传递
  • 原子类

6.StringBuilder,StringBuffer

StringBuilder 是 Java 5 中引入的,它和 StringBuffer 的方 法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被 synchronized 修饰,因此它的效率也比 StringBuffer 要高。

7. JVM 加载 class 文件的原理机制

JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的 类加载器是一个重要的 Java 运行时系统组件,它负责在运行时查找和装入类文件 中的类。 由于 Java 的跨平台性,经过编译的 Java 源程序并不是一个可执行程序,而是一 个或多个类文件。当 Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、 连接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读 入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应 的 Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类 被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么 就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。

类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加 载器(Extension)、系统加载器(System)和用户自定义类加载器 (java.lang.ClassLoader 的子类)。从 Java 2(JDK 1.2)开始,类加载过程采 取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制 中,JVM 自带的 Bootstrap 是根加载 器,其他的加载器都有且仅有一个父类加载 器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。下面是关于几个类 加载器的说明:

  • Bootstrap:一般用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);
  • Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父 加载器是 Bootstrap;
  • System:又叫应用类加载器,其父类是 Extension。它是应用最广泛的 类加载器。它从环境变量 classpath 或者系统属性 java.class.path 所指定的目 录中记载类,是用户自定义加载器的默认父加载器。

8.接口与抽象类

  • 接口可以继承(extends)接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。
  • 接口方法都隐式地用public abstrat修饰,且不能有实现(Java1.8之后,接口方法可以有默认实现);抽象类中可以有抽象方法和非抽象方法,抽象方法可以有public,protected或default修饰。
  • 接口的属性都隐式地用public static final修饰。
  • 从原理上讲,抽象是对类的抽象,是自下而上的总结。接口是对行为的规范,是自上而下的标准。

jdk7-jdk9 接口的变化: jdk7或更早的版本,接口中只能由常量和抽象方法。 jdk8中加入了默认方法和静态方法,实现类不能调用接口的静态方法。 jdk9中加入了私有方法和私有静态方法。

9.final

  • (1)修饰类:表示该类不能被继承;
  • (2)修饰方法:表示方法不能被重写;
  • (3)修饰变量:表示变量只能一次赋值以后值不能被修改(常量)。

10.try..finally

在 finally 中改变返回值的做法是不好的,因为如果存在 finally 代码块,try 中的 return 语句不会立马返回调用者,而是记录下返回值待 finally 代码块执行完 毕之后再向调用者返回其值,然后如果在 finally 中修改了返回值,就会返回修改 后的值。

11.线程通信图

Java基础 - 图2

说明:其中 Running 表示运行状态,Runnable 表示就绪状态(万事俱备,只欠 CPU),Blocked 表示阻塞状态,阻塞状态又有多种情况,可能是因为调用 wait() 方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用 了 sleep()方法或 join()方法等待休眠或其他线程结束,或是因为发生了 I/O 中断。

12.面向对象六原则一法则

  • 单一职责原则:一个类的功能要尽可能专业。实现高内聚、低耦合。模块化。
  • 开闭原则:对扩展开放,对修改关闭。
  • 依赖倒转原则:面向接口编程(声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型。)
  • 里氏替换原则:任何时候都可以用子类型替换掉父类型。
  • 接口隔离原则:接口要小而专,绝不能大而全。接口功能单一。
  • 合成聚合复用原则:优先使用聚合或合成关系复用代码,不要滥用继承。Is-A 关系、Has-A 关系、Use-A 关系。任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。
  • 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有 尽可能少的了解。

13.设计模式

在 GoF 的《Design Patterns: Elements of Reusable Object-Oriented Software》中给出了三类(创建型[对类的实例化过程的抽象化]、结构型[描述如 何将类或对象结合在一起形成更大的结构]、行为型[对在不同的对象之间划分责任 和算法的抽象化])共 23 种设计模式,包括:Abstract Factory(抽象工厂模式), Builder(建造者模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式);Facade(门面模式),Adapter(适配器 模式),Bridge(桥梁模式),Composite(合成模式),Decorator(装饰模 式),Flyweight(享元模式),Proxy(代理模式);Command(命令模式), Interpreter(解释器模式),Visitor(访问者模式),Iterator(迭代子模式), Mediator(调停者模式),Memento(备忘录模式),Observer(观察者模式), State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibility(责任链模式)。

工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。

代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。动态代理就用的比较多。

适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。

模板方法模式:提供一个抽象类,将部分逻辑以具体方法或构造器的形式 实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不 同的方式实现这些抽象方法(多态实现),从而实现不同的业务逻辑。

建造者模式:一个类的实例变量过多,通过普通的构造器实现各种组合比较麻烦,如果用原本的set,又需要写好多行,所以在每次set的时候返回这个实例,就可以链式编程了。

原型模式:原本我们要创建两个实例,但是两个实例有很多地方都是相似的,且变化具有规律性。创建一个类,实现Cloneable接口,在重写clone的方法中实现不同性。例如一分试卷,试卷的乱序。原型模式跟工厂模式相似,不过原型模型实现Cloneable接口。

桥接模式:相当于将两个集合进行一个组合(例如支付方式和支付平台的组合)。把两个集合都抽象成接口(或者抽象类)。

组合模式:利用决策树进行多级判断,节点抽象

备忘录模式:保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。

14.为什么要用单例模式而不是直接使用类方法

类方法不能实现对象的aop,ioc功能,只能作为简单的工具类。简单的来说不符合面向对象编程的核心理念。

15.重写

@OverWrite

重写的方法中

  • 方法名称和参数列表必须相同
  • 返回参数和抛出异常可以是原方法的子类
  • 访问权限修饰符可以比原方法范围更大

注意: 静态方法不能重写(因为重写实际上是配合多态使用的),如果子类中有一个与父类一样的静态方法,那是将父类方法进行了覆盖而非重写 构造方法无法被重写,private/final/static修饰的方法都不能被重写 子类可以重写父类的synchorized方法,且重写后锁对象编程子类实例

  1. class K {
  2. }
  3. class J extends K {
  4. }
  5. class A {
  6. K get() {
  7. return new K();
  8. }
  9. }
  10. class B extends A {
  11. @Override
  12. public J get() {
  13. return new J();
  14. }
  15. }
  16. public class OverWriteStudy {
  17. public static void main(String[] args) {
  18. A a = new A();
  19. B b = new B();
  20. A c = new B();
  21. System.out.println(b.get());
  22. System.out.println(c.get());
  23. }
  24. }

16.重载

@overload

不改变方法名称,只要方法参数的顺序、数量、类型的其中一个改变就可以,返回参数和访问修饰符可以改变

  1. public class OverLoadStudy {
  2. public void testOverLoad(Integer a){
  3. System.out.println(a);
  4. }
  5. public void testOverLoad(int a){
  6. System.out.println(a);
  7. }
  8. }

虽然重写和重载是两个毫不相关的东西,但面试官总会比较着问,所以下面整理了一个表格:

Java基础 - 图3

17.Java中的基本数据类型占几个字节

  • 1: byte, boolean
  • 2: short, char
  • 4: int, float
  • 8: long, double

18.Integer自动拆装箱的问题

自动装箱相当于调用Integer.valueOf(num)

  1. public static void main(String[] args) {
  2. Integer integer = Integer.valueOf(10);
  3. Integer integer2 = Integer.valueOf(10);
  4. System.out.println(integer==integer2); //true
  5. integer = Integer.valueOf(1000);
  6. integer2 = Integer.valueOf(1000);
  7. System.out.println(integer==integer2); //false
  8. integer = 1000;
  9. integer2 = 1000;
  10. System.out.println(integer==integer2); //false
  11. integer = new Integer(10);
  12. integer2 = new Integer(10);
  13. System.out.println(integer==integer2); //false
  14. }

19.强类型转换时的注意点

  • 当对小于int的数据类型(byte,char,short)进行运算时,首先会自动强转成int;
  • 基本数据类型和boolean之间不能互相转换;
  • 自动类型转换顺序: byte,short,char->int->long->float->double , 如果是反向的,则需要强转

20.JAVA程序初始化的顺序

注意代码块和变量的执行先后按照其代码的顺序,例如父类的静态代码块写在静态变量前,则先执行静态代码块

1.父类的静态变量、父类的静态代码块

2.子类的静态变量、子类的静态代码块

3.父类的非静态变量、父类的非静态代码块、父类的构造函数

4.子类的非静态变量、子类的非静态代码块、子类的构造函数

  1. class F{
  2. private static int a =1;
  3. static {
  4. System.out.printf("%s执行静态代码块:a%d%n",F.class,a);
  5. a = 11;
  6. System.out.printf("%s执行静态代码块后:a%d%n",F.class,a);
  7. }
  8. private int b = 2;
  9. {
  10. System.out.printf("%s执行静态代码块:b%d%n",this,b);
  11. b = 22;
  12. System.out.printf("%s执行静态代码块后:b%d%n",this,b);
  13. }
  14. public F() {
  15. System.out.printf("%s构造函数%n",F.class);
  16. }
  17. }
  18. class S extends F{
  19. private static int c =1;
  20. static {
  21. System.out.printf("%s执行静态代码块:c%d%n",S.class,c);
  22. c = 11;
  23. System.out.printf("%s执行静态代码块后:c%d%n",S.class,c);
  24. }
  25. private int d= 2;
  26. {
  27. System.out.printf("%s执行静态代码块:d%d%n",this,d);
  28. d = 22;
  29. System.out.printf("%s执行静态代码块后:d%d%n",this,d);
  30. }
  31. public S() {
  32. System.out.printf("%s构造函数%n",S.class);
  33. }
  34. }
  35. public class SequenceStudy {
  36. public static void main(String[] args) {
  37. S s = new S();
  38. Class<S> sClass = S.class;
  39. Class<?> m = Class.forName("S");
  40. }
  41. }
  1. // S s = new S()的输出结果
  2. class F执行静态代码块:a1
  3. class F执行静态代码块后:a11
  4. class S执行静态代码块:c1
  5. class S执行静态代码块后:c11
  6. S@7ca48474执行静态代码块:b2
  7. S@7ca48474执行静态代码块后:b22
  8. class F构造函数
  9. S@7ca48474执行静态代码块:d2
  10. S@7ca48474执行静态代码块后:d22
  11. class S构造函数
  12. // Class<S> sClass = S.class; 输出结果为空
  13. // Class<?> m = Class.forName("S"); 输出结果
  14. class F执行静态代码块:a1
  15. class F执行静态代码块后:a11
  16. class S执行静态代码块:c1
  17. class S执行静态代码块后:c11

当一个类继承了其它类时,在它的构造函数(constructor)中super()必须被首先调用,如果super()没有被调用,则编译器将在构造函数(constructor)的第一行插入对super()的调用。

21.序列化

实现Serializable接口,标记型接口,里面没有任何方法。ObjectOutputStream.writeObject(), ObjectOutputStream.readObject();

  • 序列化时,只对对象的状态进行保存,而不管对象的方法(类的信息不保存)
  • 当一个父类实现序列化后,子类会自动实现序列化
  • 当一个对象的实例变量引用引用其他对象,序列化该对象时,也把引用对象进行序列化
  • 对象中被static或者transient修饰的变量在序列化的时候不被保存
  1. @Data
  2. class User implements Serializable {
  3. private static final long serialVersionUID = -8475669200846811112L;
  4. private final transient int t = 121;
  5. private final String username = "Java";
  6. private final String address = "China";
  7. }
  8. public class SerializableStudy {
  9. public static void main(String[] args) {
  10. User user = new User();
  11. byte[] bytes = SerializationUtils.serialize(user);
  12. User u = SerializationUtils.deserialize(bytes);
  13. System.out.println(u);
  14. }
  15. }
  1. // 输出
  2. User(t=121, username=Java, address=China)
  1. 添加了工具包
  2. <dependency>
  3. <groupId>org.apache.commons</groupId>
  4. <artifactId>commons-lang3</artifactId>
  5. <version>3.12.0</version>
  6. </dependency>

22.equals方法与hashcode

在重写equals方法的时候最好重写hashcode,避免一些错误。(因为有一些工具类是通过hashcode来进行比较两个实例是否相同。

例如HashMap在存储的时候先通过hashcode来判重,如果hashcode不相同再比较equals是否相等,如果没有重写hashcode,虽然不会出错,但会降低性能的两个对象。

  1. (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))

重写hashCode方法: 生成一个 int 类型的变量 result,并且初始化一个值,比如17 对类中每一个重要字段,也就是影响对象的值的字段,也就是 equals 方法里有比较的字段,进行以下操作: a. 计算这个字段的值 filedHashValue = filed.hashCode(); b. 执行 result = 31 * result + filedHashValue;

23.接口与抽象类

我自己的总结:从设计角度触发:接口是一种方法的规范,抽象类是对一些对象集体特征的抽象,它可以包含属性和方法。所以,接口作为方法的规范,我们希望其尽量地去耦合,所以很多接口设计成只有一个方法(我们称其为,函数式接口,在Java8后可以用lamda表达式传参),或者没有方法(标记接口,在反射过程中,可用于区分与其他类)。基于这个设计,在实现中:

  • 接口的方法都是public abstract方法(默认,可以不显式地写出)
  • 接口的成员只能是public static final 类型的,不能被重写。(因为接口是对方法的规范,其参数只是用于帮助实现方法的,所以设为静态不可变)
  • 一个类可以实现多个接口,却只能继承一个抽象类(因为抽象类中包含着非静态的,会随着将来子类实例化后产生的数据和操作)
  • 上述两点是在java8之前的规范,也满足了设计的初衷。但是在java语言发展过程中,我们发现,
    • 如果对于接口的静态变量需要有一些特殊的比较复杂的处理,无法实现。所以在java8之后,添加了静态方法(我这里实验了一下,接口的静态方法是不能处理接口的静态变量的)。让接口有一些通用功能,也满足了向下兼容。
    • 另外,如果一个接口的抽象方法很有可能被实现成一种同样的方法,那每次实现这个接口都重写一段一样的代码,就很麻烦。然后java8就很机智的给接口的抽象方法多了一种选择,可以有default默认方法(同样的,默认方法也不能处理接口的静态变量)。
  • 在java8的改进之后,似乎抽象类和接口已经很相似了,但其实还有一些区别,抽象类可以定义一些非静态的方法和代码块,而接口不行。另外抽象类和接口的设计逻辑也有很大不同,总的来说接口是自顶向下设计出来的规范,而抽象类是自底向上抽象而来的。这也就决定了,为什么我们用接口如此频繁而抽象类的使用却不是那么多。
  1. interface IA{
  2. int id = 11;
  3. static void changeId(){
  4. System.out.println(id);
  5. }
  6. default void test(){
  7. System.out.println(id);
  8. };
  9. }
  10. abstract class Aa{
  11. private int id = 12;
  12. private static final String name= "催化";
  13. Aa(int id){
  14. System.out.println("构造函数");
  15. this.id = id;
  16. }
  17. private void showId(){
  18. System.out.println(this.id);
  19. }
  20. }
  21. class ImplA extends Aa implements IA{
  22. ImplA(int id) {
  23. super(id);
  24. }
  25. @Override
  26. public void test() {
  27. System.out.println("haha");
  28. }
  29. }
  30. public class InterfaceStudy implements IA{
  31. public static void main(String[] args) {
  32. ImplA implA = new ImplA(123);
  33. System.out.println(implA.hashCode());
  34. implA.test();
  35. }
  36. }
  1. // 输出
  2. 构造函数
  3. 920011586
  4. haha

24.求余数的特殊方法

如果要求一个数除以Java基础 - 图4之后的结果,可以通过位与运算:

Java基础 - 图5#card=math&code=num%20%5C%26%20%282%5En-1%29)

26.JVM,JDK,JRE

JVM

JVM是运行Java字节码的虚拟机。JVM有针对不同操作系统的特定实现(Windows,Linux,macOS),目的是使相同的字节码文件能在不同操作系统中运行,即Java的可移植性。JAVA语言通过字节码文件,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言的可移植特点。

在.class->机器码这一步中。 JVM类加载器首先加载字节码文件,然后通过解释器逐行翻译,这种方式执行效率低,而随着JDK版本的发展,又依次引入了JIT编译器和AOT编辑模式。JIT编译器属于运行时编译,通过惰性评估(Lazy Evaluation)的方法, 收集每一次执行的信息,找到程序中需要经常被执行的代码片段,将其编译成机器码并缓存,下次遇到该代码片段便可直接获取其机器码,提高效率。所以说Java是编译与解释共存的语言。

JDK,JRE

JDK 是 Java Development Kit, 包含JDK,编译器(javac),工具(javadoc,jdb)等。能够创建并编译程序。

JRE 是 Java运行时环境,是运行.class文件所需要的所有内容集合。包括JVM,Java类库,Java命令和其他的一些基础构件。

一般来说,如果是为了运行一下Java程序,只需要JRE。但如果是部署Java Web应用,部署war包的时候,则需要安装JDK。

27.JAVA和C++的关系

  • 都是面向对象的语言
  • C++有指针,Java只有对象,传参都是值传递
  • C++支持多继承,Java的类只能单继承
  • Java有自动内存管理机制,无需手动回收垃圾
  • C中,字符串或字符数组最后都会有一个格外的字符’\0’表示结束,但Java中没有

28.String,StringBuffer,StringBuilder

String类中使用final修饰字符数组来保存变量private final char value[],所以String对象是不可变的。

String字符串设计成不可变的原因: 便于实现字符串池化(String Pool)技术:会在堆中开辟一块区域,作为字符串缓存。 利于多线程安全调度。 避免安全问题:防止黑客通过反射改变String指向对象 配合集合类:集合类的使用需要计算一个类的HashCode, 将String设计成不可以变的,就不需要修改其HashCode值

StringBuilder和StringBuffer都继承自AbstractStringBuilder类,AbstractStringBuilder中也是用字符数组保存字符串char[] value,没有用final修饰,是可变的。StringBuffer的方法加了同步锁,是线程安全的,StringBuilder的方法没有加锁,是线程不安全的。

29.异常

Java基础 - 图6

30.IO

Java基础 - 图7

31.BIO,NIO,AIO

BIO(Blocking I/O)

同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待期完成。

NIO(Non-blocking/New I/O)

同步非阻塞I/O模式。在 Java 1.4 中引⼊了 NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以 理解为 Non-blocking,不单纯是 New。它⽀持⾯向缓冲的,基于通道的 I/O 操作⽅法。

AIO(Asynchronous I/O)

AIO 也就是 NIO 2。在 Java 7 中引⼊了 NIO 的改进版 NIO 2,它 是异步⾮阻塞的 IO 模型。就⽬前来 说 AIO 的应⽤还不是很⼴泛,Netty 之前也尝试使⽤过 AIO,不过⼜放弃了。

32.浅拷贝与深拷贝

原型模式中的拷贝分为浅拷贝和深拷贝。

浅拷贝:对值类型的成员变量进行值的复制,对引用类型成员变量只复制引用,不复制引用的对象。

深拷贝:值类型的成员变量进行值的复制,对引用类型的成员变量也进行引用对象的复制.

Object 类的 clone 方法只会拷贝对象中的基本数据类型的值,对于数组、 容器对象、引用对象等都不会拷贝,这就是浅拷贝。如果要实现深拷贝,必须 将原型模式中的数组、容器对象、引用对象等另行拷贝。

33.HttpServlet容器响应Web客户请求的流程

Servlet获取http请求,创建HttpRequestHttpResponse并封装,调用Servlet的service方法,service方法内部根据requesr的方法来判断是doGet还是doPost,处理后Servlet把响应结果返回。

1)Web客户向Servlet容器发出Http请求;

2)Servlet容器解析Web客户的Http请求;

3)Servlet容器创建一个HttpRequest对象,在这个对象中封装Http请求信息;

4)Servlet容器创建一个HttpResponse对象;

5)Servlet容器调用HttpServlet的service方法,这个方法中会根据request的Method来判断具体是执行doGet还是doPost,把HttpRequest和HttpResponse对象作为service方法的参数传给HttpServlet对象

6)HttpServlet调用HttpRequest的有关方法,获取HTTP请求信息;

7)HttpServlet调用HttpResponse的有关方法,生成响应数据;

8)Servlet容器把HttpServlet的响应结果传给Web客户。

34.Proxy动态代理和Cglib动态代理的区别

1.Proxy动态代理实现了被代理对象的接口,Cglib是继承了被代理对象

2.Proxy和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。

3.Proxy调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。