前言
为什么要先准备框架内容的复习呢?
个人是因为简历平平,只有俩项目还看得过去,新年的首发面试就凉在项目上,上来便是轰炸项目和框架.后来反省了下,框架知识也是一块很重要的内容.面试首发一般都是针对简历的项目开始考察.
后来看了一些别人的面经,很多都是上来就扯项目,你说要是连第一关框架都入不了面试官的眼,那还指望其他加分吗? 尤其当我知道别人二面全怼项目的时候,才意识到框架其实是面试的大头.那些只考算法,网络,操作系统的只是bat等大厂而已.
对于绝大多数普通人(履历平平)来说,面试内容中项目和框架占了大半,不容忽视.
Spring篇:
Spring复习中最重要的两块:
1. IOC(控制反转)和DI(依赖注入)
2. AOP(面向切面编程)
控制反转(IoC): 一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI): IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器: 依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
三者之间关系看这篇:
http://blog.jobbole.com/101666/
IOC是什么?
这一篇讲的不错:
http://www.cnblogs.com/DebugLZQ/archive/2013/06/05/3107957.html
IOC更具体的 ,如初始化等:
https://segmentfault.com/a/1190000017348726
个人理解:
IOC是一种思想,而DI则是它的实现方式(还有另一种方式:服务定位)
IOC容器作为第三方,将各个对象粘合在一起.
在Spring中由Spring容器借助bean配置来控制
Spring IOC容器是DI注入的框架,负责创建对象,管理对象,整合对象,配置对象以及管理这些对象的生命周期.
通过配置文件管理对象的生命周期,依赖关系,不用重新修改并编译具体的代码,从而实现组件之间的解耦.
没有IOC的情况时,对象A依赖对象B时,需要对象手动创建B,这个时候控制权在A手中,此时耦合度高,会出现牵一发而动全身的问题.
出现IOC后,选择控制权从调用类中移除,所有对象的控制权都上交到IOC容器中,这时候它就起到了"粘合剂"的作用
控制反转的由来是因为:对象A获得依赖对象B的过程由主动行为变成了被动行为,控制权颠倒过来.
可以理解为:获得依赖对象的过程被反转了,即获得依赖对象的过程由自身管理变成了由IOC容器主动注入
依赖注入:
由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中
依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦
与new对象的区别?
正转与反转,传统应用程序是由我们将自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮助创建及注入依赖对象。
IoC优缺点
优点:实现组件之间的解耦,提供程序的灵活性和可维护性
缺点:对象生成因为是使用反射编程,在效率上有损耗
那在spring中如何放入一个对象管理呢?(以XML配置方式解释)
- 准备配置文件:配置文件中声明 Bean 定义也就是 Bean 配置元数据。
- 通过应用上下文将配置加载到IOC容器
- 由 IoC 容器进行解析元数据:IoC 容器的 Bean Reader 读取并解析配置文件,根据定义生成 BeanDefintion 配置元数据对象,IoC 容器根据 BeanDefintion 进行实例化、配置及组装 Bean。
- 实例化 IoC 容器:由客户端实例化容器,获取需要的 Bean。
什么是应用上下文?
应用上下文其实就是IOC容器在Spring中的代码实现,在Spring中IOC容器大致分为两种:
- BeanFactory
- 集成BeanFactory后派生而来的应用上下文,如其抽象接口为ApplicationContext
具体参考:
https://www.cnblogs.com/chenbenbuyi/p/8166304.html
Spring XML配置Bean注入的三种常见方式:
- 属性注入(set 注入):可以有选择地通过 Setter 方法完成调用类所需依赖的注入。
- 构造函数注入:通过调用类的构造函数,将接口实现类通过构造函数变量传入。
- 工厂方法注入
参考:
https://blog.csdn.net/a909301740/article/details/78379720
AOP(面向切面编程):
是对OOP的补充和完善,OOP是纵向有层次的结构,而AOP是横向的,注重的是解决许多问题的方法中的共同点,就比如 我们开发的软件中有很多业务方法,但是我们要对这个方法的执行进行监控,需要加入日志记录,这个时候日志代码写在哪里?显然不能写在业务代码里,这样不仅污染了业务代码,而且因为不同业务方法都需要这个日志代码,重复性很高,所以我们要单独领出来写成一个通用的代码块,这个时候AOP面向切面的编程就呼之欲出了.
总结:
AOP就是在目标类的基础上增加切面逻辑,生成增强的类(切面逻辑在目标函数执行之前,或者之后,或者抛出异常时执行)
AOP将应用系统分为两部分,一部分是核心业务逻辑,一部分是横向的通用逻辑,通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起.
AOP中的核心功能的底层实现机制:动态代理的实现原理,AOP中的拦截功能是由java的动态代理实现的.
重头戏来了:jdk动态代理和CGLIB动态代理.(当初考我两者区别,我只知道CGLIB快)
jdk:
1.由java内部的反射机制实现,通过实现 InvocationHandler 接口创建自己的调用处理器;
- 代理的目标类必须基于统一的接口,有一定的局限性,在生成类的过程中比较高效
- 代理机制是委托机制,不需要以来第三方的库,只要要JDK环境就可以进行代理,动态实现接口类,在动态生成的实现类里面委托为hanlder去调用原始实现类方法;
CGLIB:
- CGLIB既可以对接口的类生成代理,也可以针对类生成代理。以在运行期扩展Java类与实现Java接口。
- 底层借助asm实现,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,解决生成类过程低效的问题)
- CGLib的类库,使用的是继承机制,是被代理类和代理类继承的关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final
具体应用:
如果目标对象实现了接口,默认情况下是采用JDK动态实现AOP
如果目标对象没有实现接口,必须采用CGLIB库.
如何强制使用CGLIB实现AOP?
1. 添加CGLIB库,SPRING_HOME/cglib/*.jar
2. 在spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
过滤器和拦截器:
Filter配置在web.xml
赖于servlet容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的,是用来做一些过滤操作,获取我们想要获取的数据,比如:在Javaweb中,对传入的request、response提前过滤掉一些信息,或者提前设置一些参数,然后再传入servlet或者Controller进行业务逻辑操作。通常用的场景是:在过滤器中修改字符编码(CharacterEncodingFilter)、在过滤器中修改HttpServletRequest的一些参数(XSSFilter(自定义过滤器)),如:过滤低俗文字、危险字符等。
拦截器的配置一般在SpringMVC的配置文件中
在实现上,基于Java的反射机制,属于面向切面编程(AOP)的一种运用,就是在service或者一个方法前,调用一个方法,或者在方法后,调用一个方法,比如动态代理就是拦截器的简单实现,在调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在调用方法后打印出字符串,甚至在抛出异常的时候做业务逻辑的操作。由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理
参考:
https://blog.csdn.net/zxd1435513775/article/details/80556034
https://blog.csdn.net/xiaodanjava/article/details/32125687
https://blog.csdn.net/zxd1435513775/article/details/80556034
Bean和Spring容器之间的关系:
https://www.cnblogs.com/wuchanming/p/5426746.html
servlet和jsp本质是一样的吗?
答:一样
参考:
https://www.zhihu.com/question/37962386
servlet,过滤器,监听器,拦截器的区别:
https://www.cnblogs.com/jinb/p/6915351.html
Spring框架的启动入口?
Spring的入口配置在web.xml中。以监听器的形式来实现。
即ContextLoaderListener
它实现了接口ServletContextListener,也就是说他必须实现contextDestroyed, contextInitialized这两个方法
Spring框架由此启动, contextInitialized也就是监听器类的main入口函数
所有实现都隐藏在ContextLoader类里
参考:
https://blog.csdn.net/xiaolyuh123/article/details/60132518
web.xml 文件中一般包括什么?
servlet, filter, listenr的配置
servlet:是服务器端的程序,用来处理和响应请求,web.xml配置目的是拦截指定的url地址,传递给SpringMVC的DispatcherServlet处理
filter:如编码过滤器,解决乱码问题,启动一次
listenr:监听器,一般配置Spring监听器
web.xml的加载过程是context-param >> listener >> fileter >> servlet
初始化过程:
1. 在启动Web项目时,容器(比如Tomcat)会读web.xml配置文件中的两个节点<listener>和<contex-param>。
2. 接着容器会创建一个ServletContext(上下文),应用范围内即整个WEB项目都能使用这个上下文。
3. 接着容器会将读取到<context-param>转化为键值对,并交给ServletContext。
4. 容器创建<listener></listener>中的类实例,即创建监听(备注:listener定义的类可以是自定义的类但必须需要继承ServletContextListener),一般配置contextLoaderListener extends ContextLoader implements ServletContextListener
5. Spring启动主要的逻辑在父类ContextLoader的方法initWebApplicationContext实现。ContextLoaderListener的作用就是启动web容器时自动装配ApplicationContext的配置信息。更细化一点讲,Spring的启动过程其实就是Spring IOC容器的启动过程。
6. 在 web 容 器 启 动 时 , 会 触 发 容 器 初 始 化 事 件 , 此 时在 web 容 器 启 动 时 , 会 触 发 容 器 初 始 化 事 件 , 此 时
contextLoaderListener 会监听到这个事件,其 contextInitialized )初始化方法会被调用,在这个方法中可以通过event.getServletContext().getInitParameter("contextConfigLocation") 来得到context-param 设定的值,spring 会初始
化一个启动上下文,这个上下文被称为根上下文,即 WebApplicationContext,这是一个接口类,确切的说,其实
际的实现类是 XmlWebApplicationContext。这个就是 spring 的 IoC 容器,其对应的 Bean 定义的配置由 web.xml 中
的 context-param 标签指定。在这个类中还必须有一个contextDestroyed(ServletContextEvent event) 销毁方法.用于关闭应用前释放资源,比如说数据库连接的关闭。
7. 在Spring IOC 容器启动初始化完毕之后,会将其储存到ServletContext中
8. 以上都是在WEB项目还没有完全启动起来的时候就已经完成了的工作。如果系统中有Servlet,则Servlet是在第一次发起请求的时候被实例化的,而且一般不会被容器销毁,它可以服务于多个用户的请求。所以,Servlet的初始化都要比上面提到的那几个要迟。
参考:
https://www.cnblogs.com/yaoyiyao/p/7198076.html
https://blog.csdn.net/lieyanhaipo/article/details/58605545
Spring如何实现Bean注入?
Spring启动的时候读取应用程序提供的Bean配置信息,并且在Spring容器中生成一份相应的Bean配置注册表,然后根据这个张注册表实例化Bean,装配好Bean之间的依赖关系,为上层应用提供准备就绪的运行环境.
简单过程:
- BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中每一个
<bean>
解析成一个BeanDefinition
对象,并保存到BeanDefinitionRegistry
中; - 容器扫描BeanDefinitionRegistry中的BeanDefinition;调用InstantiationStrategy进行Bean实例化的工作;使用BeanWrapper完成Bean属性的设置工作;
- 利用容器中注册的 Bean 后处理器(实现 BeanPostProcessor 接口的 Bean)对已经完成属性设置工作的 Bean 进行后续加工,直接装配出一个准备就绪的 Bean。
单例Bean缓存池:Spring 在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例 Bean 的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。
classpath与classpath*区别?
Spring 中哪些地方用到了 ThreadLocal,起到了什么作用?
ThreadLocal与像synchronized这样的锁机制是不同的。首先,它们的应用场景与实现思路就不一样,锁更强调的是如何同步多个线程去正确地共享一个变量,ThreadLocal则是为了解决同一个变量如何不被多个线程共享。从性能开销的角度上来讲,如果锁机制是用时间换空间的话,那么ThreadLocal就是用空间换时间。
使用模板类访问底层数据,根据持久化技术的不同,模板类需要绑定数据连接或会话的资源。但这些资源本身是非线程安全的,也就是说它们不能在同一时刻被多个线程共享。虽然模板类通过资源池获取数据连接或会话,但资源池本身解决的是数据连接或会话的缓存问题,并非数据连接或会话的线程安全问题。
ThreadLocal 可以保存线程本地化对象的容器。当运行在多线程环境的某个对象使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程分配一个独立的变量副本。所以每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
举例:
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因为有状态的Bean就能够以singleton方式在多线程中正常工作了。
还有 事务也有.
解释 Spring 支持的几种 bean 的作用域
- singleton : bean 在每个 Spring ioc 容器中只有一个实例。
- prototype:一个 bean 的定义可以有多个实例。
- request:每次 http 请求都会创建一个 bean,该作用域仅在基于 web 的 Spring ApplicationContext 情形下有
效。 - session : 在 一 个 HTTP Session 中 , 一 个 bean 定 义 对 应 一 个 实 例 。 该 作 用 域 仅 在 基 于 web 的 Spring
ApplicationContext 情形下有效。 - global-session:在一个全局的 HTTP Session 中,一个 bean 定义对应一个实例。该作用域仅在基于 web 的 Spring ApplicationContext 情形下有效。
缺省的 Spring bean 的作用域是 Singleton.
Spring中的 Bean的生命周期:
https://www.jianshu.com/p/3944792a5fff
String AOP 和 MVC 拦截器区别?
String AOP 和 MVC 拦截器都是 AOP思想的具体实现
Filter过滤器:拦截web访问url地址。
Interceptor拦截器:拦截以 .action结尾的url,拦截Action的访问。
Spring AOP拦截器:只能拦截Spring管理Bean的访问(业务层Service)
参考:
https://blog.csdn.net/heyeqingquan/article/details/71600676
https://blog.csdn.net/u010061060/article/details/80327348
Spring事务:有7种传播行为,5种隔离方式
Spring事务管理分为 1. 编程式事务管理 2. 声明式事务管理两种
声明式事务和编程式事务区别:
Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。Spring 强大的声明式事务管理功能,这主要得益于 Spring 依赖注入容器和 Spring AOP 的支持。依赖注入容器为声明式事务管理提供了基础设施,使得 Bean 对于 Spring 框架而言是可管理的;而 Spring AOP 则是声明式事务管理的直接实现者。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
事务不会滚原因?
https://www.cnblogs.com/zeng1994/p/8257763.html
.Spring 注解中@Resource 和@Autowired 的区别
1@Autowired 与@Resource 都可以用来装配 bean. 都可以写在字段上,或写在 setter 方法上。
2@Autowired 默认按类型装配(这个注解是属于 spring 的),默认情况下必须要求依赖对象必须存在,如
果要允许 null 值,可以设置它的 required 属性为 false,如:@Autowired(required=false) ,如果我们想使用名
称装配可以结合@Qualifier 注解进行使用
3@Resource(这个注解属于 J2EE 的),默认按照名称进行装配,名称可以通过 name 属性进行指定,如果没
有指定 name 属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在 setter 方法上默认取属性名进行装配。 当找不到与名称匹配的 bean 时才按照类型进行装配。但是需要注意的是,如果 name 属性一旦指定,就只会按照名称进行装配。
Spring中多线程 线程安全问题?
IOC中的Bean大多都是service,dao等,都是无状态的,即自身没有状态,没有共享变量,只有一些操作,
数据库Connection都作为函数的局部变量(局部变量是在用户栈中的,而且用户栈本身就是线程私有的内存区域,所以不存在线程安全问题),用完即关(或交还给连接池)。
参考:
http://www.importnew.com/27440.html
Spring 的 controller 是单例吗,如何保证线程安全?
Spring controller 默认是单例的。因此是非线程安全的。
解决办法:
1 在控制器中不使用实例变量
2 将控制器的作用域从单例改为原型,即在 spring 配置文件 Controller 中声明 scope="prototype",每
次都创建新的 controller。
3 在 Controller 中使用 ThreadLocal 变量。
Spring中用到了哪些设计模式?
- 简单工厂模式:由一个工厂根据传入的参数,动态确定应该创建哪一个产品类.Spring中的BeanFactory就是简单工厂的体现,根据传入一个唯一的表示来获取Bean对象.
- 工厂方法模式:工厂父类定义产品的接口,工厂子类负责创建具体的产品,把产品的实例化延迟到子类工厂中,由子类工厂决定具体实例化哪个对象.可以理解为:有了很多个工厂方法,自己需要哪一个产品,就调用当前产品的工厂方法,获取相应的具体实例。Spring将应用程序的工厂对象交给Spring管理,这样Spring管理的就不是普通的Bean,而是工厂Bean
- 单例模式:保证一个类仅有一个实例.Spring中的Bean默认是单例模式.
下面对单件模式的懒汉式与饿汉式进行简单介绍:
1、饿汉式:在程序启动或单件模式类被加载的时候,单件模式实例就已经被创建。
2、懒汉式:当程序第一次访问单件模式实例时才进行创建。
如何选择:如果单件模式实例在系统中经常会被用到,饿汉式是一个不错的选择。
反之如果单件模式在系统中会很少用到或者几乎不会用到,那么懒汉式是一个不错的选择。
- 代理模式:在Spring AOP 使用的JDK动态代理或者CGLib动态代理,对类进行方法级别的切面增强,实现面向切面编程.
- 观察者模式:定义对象之间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于他的对象都得到通知并被自动更新,最常用的就是监听器 (listener)
- 模板方法模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。Spring 中的 JdbcTemplate 使用了模板方法模式。
Spring MVC篇:
Spring MVC入口:
前端控制器DispatcherServlet
在web.xml的servlet中配置
. springmvc原理
SpringMVC的拦截器不同于Spring的拦截器,SpringMVC具有统一的入口DispatcherServlet,所有的请求都通过DispatcherServlet,所有的操作都是通过该servlet进行的顺序操作,DispatcherServlet也没有代理,同时SpringMVC管理的Controller也没有代理。哪不难想到我们在执行controller之前做某些动作,执行完毕做某些动作,render完成做某些动作,都是servlet在开始便策划好的。SpringMVC的拦截器对应提供了三个preHandle,postHandle,afterCompletion方法。
Spring MVC 流程:
再谈一次拦截器:
一个拦截器实例在一个controller生命周期之内可以多次调用。但是缺点是只能对controller请求进行拦截,对其他的一些比如直接访问静态资源的请求则没办法进行拦截处理
拦截器在MVC中的流程图:
参考:
https://blog.csdn.net/leisure_life/article/details/72793738
Spring MVC 中控制器Controller返回值有哪些?
- void
- ModelAndView
- 字符串(真实路径,重定向,转发)
参考:
https://www.cnblogs.com/1102whw/p/8777586.html
3、数据源和连接池的区别
连接池是由容器(比如 Tomcat)提供的,用来管理池中的连接对象。
连接池自动分配连接对象并对闲置的连接进行回收。
连接池中的连接对象是由数据源(DataSource)创建的。
连接池(Connection Pool)用来管理连接(Connection)对象。
数据源(DataSource)用来连接数据库,创建连接(Connection)对象。
java.sql.DataSource 接口负责建立与数据库的连接,由 Tomcat 提供,将连接保存在连接池中。
Mybatis缓存:
默认开启一级缓存,一级缓存相对于一个SqlSession而言,各个SqlSession是相互隔离的,所以在参数和SQL完全一样的情况下,我们使用同一个SqlSession对象调用同一个Mapper的方法,只执行一次sql.
二级缓存是根据mapper的namespace划分的,相同namespace的mapper查询数据放在同一个区域,也就是根据命名空间来划分,如果两个mapper文件的命名空间一样,那么他们共享一个mapper缓存.
#{}和${}的区别是什么?
${}是 Properties 文件中的变量占位符,它可以用于标签属性值和 sql 内部,属于静态文本替换,比如${driver}会被静态替换为 com.mysql.jdbc.Driver。#{}是 sql 的参数占位符,Mybatis 会将 sql 中的#{}替换为?号,在 sql 执行前会使用 PreparedStatement 的参数设置方法,按序给 sql 的?号占位符设置参数值,比如 ps.setInt(0, parameterValue),#{item.name} 的 取 值 方 式 为 使 用 反 射 从 参 数 对 象 中 获 取 item 对 象 的 name 属 性 值 , 相 当 于param.getItem().getName()。
通常一个XML映射文件都会写一个Dao接口与之对应,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不一样,方法能重载吗?
Dao接口,就是Mapper接口,接口的全限名,就是映射文件的namespace的值,接口的方法名就是映射文件中MappedStatement的id值,接口方法内的采纳数就是传递给sql的参数,Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名凭借字符串作为key值,可唯一定位一个MapperStatement.dao接口的工作原理是jdk动态代理.Mybatis运行时使用jdk动态代理为Dao接口生成代理proxy对象,拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回.
Dao接口中的方法,是不能重载的,因为全限名+方法名的保存和寻找策略.
Mybatis 是如何进行分页的?分页插件的原理是什么?
Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页,可以在 sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载, association 指的就是一对一, collection指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。
未完待续