一、Spring Framework简介

Spring 优势 :
解耦,方便开发
AOP编程支持
方便程序测试
声明式事务支持
方便其他框架集成
降低JavaEE API 使用难度

image.png
主要模块结构:
CoreContainer: Spring核⼼容器, 是Spring框架最核⼼的部分,它管理着Spring应⽤中bean的创建、配置和管理。提供依赖注入功能,Spring 所有模块都建于此模块上
AOP /Aspects: 面向切面编程 与依赖注入一样方便于应用解耦
Data Access/Integration: 数据访问与集成 ,封装 JDBC和DAO 提供了事务管理服务,对ORM进⾏了集成,如 Hibernate、MyBatis等。该模块由JDBC、Transactions、ORM、OXM 和 JMS 等模块组成。
Web :模块提供了SpringMVC框架给Web应⽤
Test :测试模块以致⼒于Spring应⽤的测试,为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现

二、手写 IoC和AOP

IoC => 创建对象的权利由应用交由容器,即控制权转给容器->控制反转
解决了 对象之间的耦合问题
AOP 面向切面编程,
横切代码逻辑重复、逻辑代码与业务代码混合在一起 不方便维护,AOP酱逻辑代码抽取与业务代码分离

实例分析:
转账demo中存在的问题
1.Dao层对象实例化过程中使用的new关键字,导致耦合太高,如果dao层实现有变动,代码需要改动太多且需要重新编译
此问题可以通过反射的方式结合工厂模式来解决,xml->全限定名存在xml中 Class.forName(“xx.xxx.x”)
内部需要的其他实例只需定义相应的接口,设置set方法,通过反射向其提供需要的实例
image.png

  1. 服务层代码没有竟然还没有进⾏事务控制,如果转账过程中出现异常,将可能导致数据库数据错乱,后果可能会很严重

    需要增加事务控制,jdbc Connection会自动提交事务

问题一解决思路:
(1)创建存放Beans定义的XML文件
(2)创建工厂类,加载XML文件,通过反射将xml中定义的bean类型创建响应的对象,将其和Id存入Map中。
(3)根据xml配置 检查哪些实例需要传入属性值,维护对象之间的依赖
创建BeanFactory实现上述Bean的创建 注入功能
问题二解决思路:
(1)两次update操作使用同一个Connection,可以将连接存储在ThreadLocal中。所有的update从ThreadLocal中获取连接,创建ConnectionUtils类来管理Connection。
(2)事务控制放在Service层,增加事务管理器(TransactionManager),在Service进行事务提交和回滚。
TransactionManager单例模式采用饿汉式,类加载到内存中就被实例化成一个单例,由JVM保证线程安全

两个问题解决方案进一步优化:在不改变原有业务逻辑的情况下 使用动态代理方式 将事务操作增加到原有的业务逻辑中,即创建动态代理工厂(ProxyFactory) 生成代理对象(Proxy) ,
ProxyFactory=>生成TransferServiceImplProxy(增强了事务控制功能)
最后将ConnectionUtils TransactionManager ProxyFactory 纳入BeanFactory管理中。

在Java平台上,对于AOP的织入,有3种方式:

  1. 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
  2. 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
  3. 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。

最简单的方式是第三种,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。

Spring对UserService作AOP切入时,从Spring容器获取的UserService实例类型,它类似UserServiceEnhancerBySpringCGLIB1f44e01c,实际上是Spring使用CGLIB动态创建的子类,但对于调用方来说,感觉不到任何区别。
Spring对接口类型使用JDK动态代理,对普通类使用CGLIB创建子类。如果一个Bean的class是final,Spring将无法为其创建子类。

补充知识点:
JDK动态代理和CGLIB字节码生成的区别?
(1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类
(2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法
因为是继承,所以该类或方法最好不要声明成final
JDK动态代理

  1. package com.lf.shejimoshi.proxy.jdk;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import com.lf.shejimoshi.proxy.entity.UserManager;
  6. import com.lf.shejimoshi.proxy.entity.UserManagerImpl;
  7. //JDK动态代理实现InvocationHandler接口
  8. public class JdkProxy implements InvocationHandler {
  9. private Object target ;//需要代理的目标对象
  10. @Override
  11. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  12. System.out.println("JDK动态代理,监听开始!");
  13. Object result = method.invoke(target, args);
  14. System.out.println("JDK动态代理,监听结束!");
  15. return result;
  16. }
  17. //定义获取代理对象方法
  18. private Object getJDKProxy(Object targetObject){
  19. //为目标对象target赋值
  20. this.target = targetObject;
  21. //JDK动态代理只能针对实现了接口的类进行代理,newProxyInstance 函数所需参数就可看出
  22. return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
  23. }
  24. public static void main(String[] args) {
  25. JdkProxy jdkProxy = new JdkProxy();//实例化JDKProxy对象
  26. UserManager user = (UserManager) jdkProxy.getJDKProxy(new UserManagerImpl());//获取代理对象
  27. user.addUser("admin", "123123");//执行新增方法
  28. }
  29. }

Cglib动态代理(需要导入jar包cglib-3.2.5.jar 版本无限制)

  1. //Cglib动态代理,实现MethodInterceptor接口
  2. public class CglibProxy implements MethodInterceptor {
  3. private Object target;//需要代理的目标对象
  4. //重写拦截方法 类似JDK动态代理中的InvocationHandler 能够对各个方法进行拦截增强
  5. @Override
  6. public Object intercept(Object obj, Method method, Object[] arr, MethodProxy proxy) throws Throwable {
  7. System.out.println("Cglib动态代理,监听开始!");
  8. Object invoke = method.invoke(target, arr);//方法执行,参数:target 目标对象 arr参数数组
  9. System.out.println("Cglib动态代理,监听结束!");
  10. return invoke;
  11. }
  12. //定义获取代理对象方法
  13. public Object getCglibProxy(Object objectTarget){
  14. //为目标对象target赋值
  15. this.target = objectTarget;
  16. Enhancer enhancer = new Enhancer();
  17. //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
  18. enhancer.setSuperclass(objectTarget.getClass());
  19. enhancer.setCallback(this);// 设置回调
  20. Object result = enhancer.create();//创建并返回代理对象
  21. return result;
  22. }
  23. public static void main(String[] args) {
  24. CglibProxy cglib = new CglibProxy();//实例化CglibProxy对象
  25. UserManager user = (UserManager) cglib.getCglibProxy(new UserManagerImpl());//获取代理对象
  26. user.delUser("admin");//执行删除方法
  27. }
  28. }

拦截器类型

顾名思义,拦截器有以下类型:

  • @Before:这种拦截器先执行拦截代码,再执行目标代码。如果拦截器抛异常,那么目标代码就不执行了;
  • @After:这种拦截器先执行目标代码,再执行拦截器代码。无论目标代码是否抛异常,拦截器代码都会执行;
  • @AfterReturning:和@After不同的是,只有当目标代码正常返回时,才执行拦截器代码;
  • @AfterThrowing:和@After不同的是,只有当目标代码抛出了异常时,才执行拦截器代码;
  • @Around:能完全控制目标代码是否执行,并可以在执行前后、抛异常后执行任意拦截代码,可以说是包含了上面所有功能。

深入理解Spring使用CGLIB生成Proxy的原理:
定义一个普通UserService

  1. @Component
  2. public class UserService {
  3. // 成员变量:
  4. public final ZoneId zoneId = ZoneId.systemDefault();
  5. // 构造方法:
  6. public UserService() {
  7. System.out.println("UserService(): init...");
  8. System.out.println("UserService(): zoneId = " + this.zoneId);
  9. }
  10. // public方法:
  11. public ZoneId getZoneId() {
  12. return zoneId;
  13. }
  14. // public final方法:
  15. public final ZoneId getFinalZoneId() {
  16. return zoneId;
  17. }
  18. }

UserService加上AOP支持,就添加一个最简单的LoggingAspect

  1. @Aspect
  2. @Component
  3. public class LoggingAspect {
  4. @Before("execution(public * com..*.UserService.*(..))")
  5. public void doAccessCheck() {
  6. System.err.println("[Before] do access check...");
  7. }
  8. }

Spring生成的代理类(通过CGLIB创建一个UserService的子类,并引用了原始实例和LoggingAspect)

  1. public UserService$$EnhancerBySpringCGLIB extends UserService {
  2. UserService target;
  3. LoggingAspect aspect;
  4. public UserService$$EnhancerBySpringCGLIB() {
  5. }
  6. public ZoneId getZoneId() {
  7. aspect.doAccessCheck();
  8. return target.getZoneId();
  9. }
  10. }

该代理类会覆写所有publicprotected方法,并在内部将调用委托给原始的UserService实例。
这里出现了两个UserService实例:
一个是我们代码中定义的原始实例,它的成员变量已经按照我们预期的方式被初始化完成:UserService=new UserService();
第二个UserService实例实际上类型是UserService$$EnhancerBySpringCGLIB,它引用了原始的UserService实例:

  1. UserService$$EnhancerBySpringCGLIB proxy = new UserService$$EnhancerBySpringCGLIB();
  2. proxy.target = original;
  3. proxy.aspect = loggingAspect

此时容器往其他对象中注入的UserService实例都是代理类对象
Spring通过CGLIB创建的代理类,不会初始化代理类自身继承的任何成员变量,包括final类型的成员变量
代理类对象如果需要使用字段属性 只能通过get方法,因为proxy的目的是代理方法。
正确使用AOP避坑指南:

  1. 访问被注入的Bean时,总是调用方法而非直接访问字段;
  2. 编写Bean时,如果可能会被代理,就不要编写public final方法。

三、Spring 应用

1、BeanFactory(IoC容器) 启动方式
Beans.xml JAVA Bean 实例化对象类的全限定名和类之间的依赖关系。
Spring 框架IoC实现
(1)纯XML(Bean信息全部定义在xml)

  1. JavaSE应用->启动方式 <br />AppliactionConext applicationConext = new ClassPathApplicationConext("beans.xml") 或者<br />new FileSystemXMLApplicationContext("c:/beans.xml");<br />JavaWeb应用->启动方式<br />ContextLoaderListener(监听器去加载xml)<br />(2) xml+注解 部分bean使用xml 部分使用注解定义<br /> 容器启动方式同上<br /> 3)纯注解模式 所有的bean都是用注解来定义<br /> Java SE应用:<br />AppliactionContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);<br />JavaWeb应用:ContextLoaderListener(监听器去加载注解配置类)

2、BeanFactory和ApplicationContext 关系
BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝,⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类) Spring IoC和AOP应用 - 图3 XML文件文件头 xmlns: xml命名空间 可指定申明名称 xsi:指定xml文档需要符合的规范(.xsd文件)

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans
  5. https://www.springframework.org/schema/beans/spring-beans.xsd">

3、web方式启动spring ioc容器
web.xml中配置监听器

org.springframework.web.context.ContextLoaderListener
此监听器需要传入参数->beans.xml(applicationContext.xml)


contextConfigLocation
classpath:applicationContext.xml

启动之后 在Servlet中获取Spring容器对象,由Spring提供发工具类:

  1. var webAppContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
  2. webAppContext.getBean();

细节知识点
bean 实例化的三种方式:
方式一 无参构造器(最常使用)

下面两种方式是为了使我们自己new的对象加入到Spring容器中
方式二 静态方法 定义静态方法static a() => factory-method=”a”
方式二 普通方法 定义普通方法 a() =>

bean 生命周期:
scope 值:
singleton 单例模式(Spring默认)容器在实例就在,容器销毁实例就销毁
prototype 原型模式(多例模式),Spring框架只创建 不管理对象,有垃圾回收器进行回收
request(web中,单个请求中有效) session(session中有效) application(单个应用中)

DI 依赖注入
(1)纯xml配置
构造函数注入 (可根据参数名和索引进行注入)、属性set方法注入
按类型分
基本数据类型注入 => name=”xxx” value=”234”
bean类型 => name=”xxx” ref=”aaaa”
复杂数据类型 name=”xxxx” array/set/map/…
(2) XML+注解模式
此模式下 第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池,⾃⼰开发的bean定义使⽤注解
中定义 context:component-scan
(3)纯注解方式
定义SpringConfig配置类,外部引入Bean可以定义在此类中
web.xml中 引入context-class
image.png

四、Spring Ioc高级特性

一、延迟加载

ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。
lazy-init=”false”,⽴即加载,表示在spring启动时,⽴刻进⾏实例化。设置为tue 为立即实例化,
default-lazy-init=”true” 配置全局的所有bean延迟加载
注意:延迟加载只适用于singleton生命周期范围的bean. 只有当第一次调用getBean()时 才进行实例化。pototype范围的bean的存活与销毁并不在spring容器的管理范围内
应用范围:
(1)开启延迟加载⼀定程度提⾼容器启动和运转性能
(2)对不常使用的Bean使用延迟加载设置。这样偶尔使用时再加载,不必要从一开始就实例化Bean占用资源

二、FactoryBean和BeanFactory

BeanFactory 接口是容器顶级接口,定义了容器的一些基础行为,负责生产和管理一系列Bean的工厂。具体使用他下面的子接口如 ApplicationContext.

Spring中Bean有两种,一种是普通Bean,一种是工厂Bean(FactoryBean). 可以生成某一个类型Bean实例。也就是说我们可以借助于它⾃定义Bean的创建过程
Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤
其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤

FactoryBean源码:

  1. //可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
  2. public interface FactoryBean<T> {
  3. @Nullable
  4. // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
  5. T getObject() throws Exception;
  6. @Nullable
  7. // 返回FactoryBean创建的Bean类型
  8. Class<?> getObjectType();
  9. // 返回作⽤域是否单例
  10. default boolean isSingleton() {
  11. return true;
  12. }
  13. }

创建复杂对象可通过实现FactoryBean方式
定义公司类:
image.png

XML配置



获取Company
Object companyBean = applicationContext.getBean(“companyBean”);
获取FactoryBean,需要在id之前添加“&”
Object companyBean = applicationContext.getBean(“&companyBean”);

三、Spring Bean生命周期

BeanPostProcessor 和 BeanFactoryPostProcessor
⼯⼚初始化(BeanFactory)—> Bean对象
在BeanFactory初始化之后可以使用使用BeanFactoryPostProcessor进行后置处理做一些事情
在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处
理做⼀些事情
注意:对象不⼀定是Spring Bean,⽽Spring Bean⼀定是个对象
只有经历如下SpringBean完整生命周期如下流程的才是Spring Bean
Bean的生命周期图
SpringBean生命周期图.png

(0)Spring Bean 生成之前操作流程
Spring容器启动->BeanFactory创建->读取Beans.xml
image.png
->解析XML 将 标签数据解析成一个个BeanDefifinition对象,存储标签中定义的相关属性等。此时Bean还未初始化
image.png
-> BeanFactory初始化完成 ->调用BeanFactoryPostProcessor,此接口是针对整个Bean工厂处理,
image.png
此接口定义了一个方法⽅法参数为ConfigurableListableBeanFactory,其中的getBeanDefinition可以获取Bean的相关信息
image.png
配合典型应用 PropertyPlaceholderConfifigurer 可以将中占位符替换成具体值
例如将jdbc属性值替换成properties中配置值
image.png
(1)工厂方法、构造方法、反射等生成Bean对象
(2)根据依赖 注入所有属性值 例如依赖的其他Bean对象
(3)调用BeanFactoryAware接口中的setBeanName方法
(4) 调用BeanFactoryAware 接口中setBeanFactory
(5) 调用ApplicationContextAware接口 setApplicationContext方法
image.png
(6)调用BeanPostProcessor中与初始化方法
(7)调用InitializingBean接口中afterPropertiesSet()方法
(8)调用标签中指定的的init-method方法,@PostConstrcut可替代此方法
(9) 调用后初始化方法
是针对Bean级别的处理,可以针对某个具体的Bean.
image.png

以上是Bean创建且初始化的流程,Bean准备完成之后, pototype周期的Bean直接交由调用者。singleton周期的Bean放入Spring缓存池(HashMap),当缓存池中Bean销毁时调用destory()方法->destory-method
image.png

五、Spring AOP应用

一、AOP常用术语

  • 连接点:方法开始时,结束时,正常运行完毕时、方法异常时等这些特殊的时机点,称之为连接点。项目中每个方法都有连接点,连接点是一种候选点
  • 切入点:指定AOP思想想要影响的具体方法是哪些
  • Advice:增强 第一个层面 指的是横切逻辑 第二个层面 指的是方位点(在某一些连接点上加入横切逻辑,那么这些连接点就叫做方位点)
  • Aspect切面:切面概念对上述概念的一个综合。 切面=切入点+增强=切入点(锁点方法) + 方位点(锁定方法中的特殊时刻) + 横切逻辑

    二、AOP基础应用
    1、AOP核心配置
    xml 约束

    1. xmlns:aop="http://www.springframework.org/schema/aop"
    2. http://www.springframework.org/schema/aop
    3. https://www.springframework.org/schema/aop/spring-aop.xsd

Spring基于XML的AOP配置步骤:
第⼀步:把通知Bean交给Spring管理
第⼆步:使⽤aop:config开始aop的配置
第三步:使⽤aop:aspect配置切⾯
第四步:使⽤对应的标签配置通知的类型

  1. <!--把通知bean交给spring来管理-->
  2. <bean id="logUtil" class="com.lagou.utils.LogUtil"></bean>
  3. <!--开始aop的配置-->
  4. <aop:config>
  5. <!--配置切⾯-->
  6. <aop:aspect id="logAdvice" ref="logUtil">
  7. <!--配置前置通知-->
  8. <aop:before method="printLog" pointcut="execution(public *
  9. com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou
  10. .pojo.Account))"></aop:before>
  11. </aop:aspect>
  12. </aop:config>

ponitcut为切⼊点表达式,也称之为AspectJ切⼊点表达式,指的是遵循特定语法结构的字符串,其
作⽤是⽤于对符合语法格式的连接点进⾏增强。它是AspectJ表达式的⼀部分

切入点表达式:
全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)

三、事务应用

事务 ACID 原子性 一致性 隔离性 持久性
事务的隔离级别
不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题
脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。
不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)
场景:
员⼯A发起事务1,查询⼯资,⼯资为1w,此时事务1尚未关闭
财务⼈员发起了事务2,给员⼯A张了2000块钱,并且提交了事务
员⼯A通过事务1再次发起查询请求,发现⼯资为1.2w,原来读出来1w读不到了,叫做不可重复读
虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条数不⼀样)
场景:
事务1查询所有⼯资为1w的员⼯的总数,查询出来了10个⼈,此时事务尚未关闭
事务2财务⼈员发起,新来员⼯,⼯资1w,向表中插⼊了2条数据,并且提交了事务
事务1再次查询⼯资为1w的员⼯个数,发现有12个⼈,⻅了⻤了

数据库共定义了四种隔离级别:
Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼
Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆
该机制下会对要update的⾏进⾏加锁
Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三
Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低
注意:级别依次升⾼,效率依次降低
MySQL的默认隔离级别是:REPEATABLE READ
查询当前使⽤的隔离级别: select @@tx_isolation;
设置MySQL事务的隔离级别: set session transaction isolation level xxx; (设置的是当前mysql连接会话的,并不是永久改变的)

事务的传播⾏为
事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。
A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为
PROPAGATION_REQUIRED 如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加⼊到这个事务中。这是最常⻅的选择。
PROPAGATION_SUPPORTS ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY 使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则
执⾏与PROPAGATION_REQUIRED类似的操作。

Spring中的事务
PlatformTransactionManager
此接⼝是Spring的事务管理器核⼼接⼝。Spring本身并不⽀持事务实现,只是负责提供标准,应⽤底层
⽀持什么样的事务,需要提供具体实现类。此处也是策略模式的具体应⽤。在Spring框架中,也为我们
内置了⼀些具体策略,例如:DataSourceTransactionManager , HibernateTransactionManager 等
等。( 和 HibernateTransactionManager 事务管理器在 spring-orm-5.1.12.RELEASE.jar 中)
DataSourceTransactionManager 归根结底是横切逻辑代码,声明式事务要做的就是使⽤Aop(动态代
理)来将事务控制逻辑织⼊到业务代码

xml事务配置
image.png
基于注解
在接⼝、类或者⽅法上添加@Transactional注解
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
在 Spring 的配置类上添加 @EnableTransactionManagement 注解即可

SpringAOP基础知识:

Spring底层使用了两种方式来实现动态代理,一种是Java自带的动态代理,另一种是CGLib。如果是使用JDK动态代理生成的代理对象,Debug可以看到JdkDynamicAopProxy,而如果是CGLib生成的对象,可以看到是EnhancerBySpringCGLIB。
那Spring具体是使用的哪种方式呢?网上有很多文章说,Spring默认产生代理对象的行为是:如果你的Bean有对应的接口,是使用的基于JDK的动态代理,否则是使用CGLIB。但这样说其实不准确,Spring用了下面这个配置来控制它,如果这个配置是false,才是上面我们说的这个逻辑。而如果这个配置是true,则所有的要使用AOP的Bean都使用CGLIB代理,不管它是不是有接口。而我们使用最新版的SpringBoot的话,这个值默认就是true。

  1. spring.aop.proxy-target-class=true

所以现在如果使用SpringBoot的话,我们的AOP代理对象都是用CGLIB生成的

JDK和CGLib动态代理有什么区别?
两者实现的原理不同,JDK动态代理是基于Java反射来实现的,而CGLIB动态代理是基于修改字节码,生成子类来实现的,底层是使用的asm开源库。
两者都有一些限制,JDK动态代理,Bean必须要有接口;CGLIB不能对final类或方法做代理

哪些方法可以被代理?
如果是使用JDK动态代理,那只有public方法可以被代理。而如果使用CGLIB,除了private方法,都可以被代理。(当然,final方法除外)。
另一个比较有意思的问题是,如果两个方法在同一个类里面,一个方法调用另一个方法是不会走代理的。只有一个Bean调用另一个Bean的方法,才会走代理。
上面两个特性也就解释了为什么有时候你的@Transactional不生效的原因:

  • 在私有方法上不生效
  • 在final方法上不生效
  • 同一个类里面方法互相调用不生效

使用AOP有什么弊端?
AOP不是万能的,使用AOP也是有一些弊端的。个人觉得对大的弊端就是让代码可读性变差了,因为它并不是一个显式的调用,所以很有可能未来接手代码的人并不清楚这个方法被AOP代理了。
例如使用了AOP来做权限控制,如果这个权限控制不是那种简单的Access,而是要去查数据库里面的一些字段,比如状态之类的,还有复杂的逻辑判断。这种情况下,如果使用AOP来做,代码的可读性就不强,出了问题比较难以排查。

事务失效的问题
image.png

常见面试题

一、IOC如何解决循环依赖
核心思想就是将未完成初始化的对象提前发布,具体的实现采用三级缓存
一级缓存:SingletonObjects
二级缓存:earlySingletonObjects
三级缓存:singletonFactories

  1. 假如两个类A、B相互依赖,首先要对A进行实例化,实例化入口为getBean方法,实例化后将该对象包装成工厂类,注册到三级缓存中;此时A完成实例化,但未完成初始化|
  2. 实例化之后要对其进行初始化,也就是属性赋值,给A赋值B时,发现一二三级缓存中都没有B对象,此时去创建B对象,同样经过实例化和初始化过程,B对象初始化过程就是给B赋值A,在三级缓存中找到了A对象,然后赋值,将A添加到二级缓存,然后将三级缓存清除;此时B完成初始化。
  3. B实例化完成之后,可以赋值给A了,此时A完成初始化,注册到一级缓存中

二、为什么一定要使用三级缓存?用一级或二级不行吗?
重点是为了解决代理对象(如AOP)的循环依赖
AOP的实现是通过postBeanProcess后置处理器,在初始化之后做代理操作

第一种情况:如果只用一级缓存
成品和半成品都放在singletonObjects,虽然整个流程是通的,但假如整个流程进行中,有另外一个线程要来取A,那么就有可能拿到的只有一个属性都为null的半成品A
还有就是无法满足的是需要注入的是代理对象的时候

第二种情况:使用二级缓存
1⃣️使用singletonObjects 和 singletonFactories
成品放在singletonObjects,半成品通过singletonFactories获取

流程:
实例化A ->创建A的对象工厂并放入singletonFactories中 ->填充A的属性时发现取不到B->实例化B->创建B的对象工厂并放入singletonFactories中->从singletonFactories中获取A的对象工厂并获取A填充到B中->将成品B放入singletonObjects,并从singletonFactories中删除B的对象工厂->将B填充到A的属性中->将成品A放入singletonObjects并删除A的对象工厂

问题:
虽然适用于普通的IOC有并发场景
但如果A上加了个AOP切面,就无法满足需求,因为从singletonFactories获取对象的时候会对对象进行增强处理
image.png
假设现在有三个对象ABC,A依赖B,B依赖A、C,C依赖A
A实例化设置B属性,B开始实例化,B设置属性A的时候拿到的A是从三级缓存中拿到代理后的A,由于A没加载完毕,不会放入一级缓存!这时候B设置属性C,C开始实例化,设置属性A,又去三级缓存拿A,C拿到的A和B拿到的A显然不是同一个对象,出问题!

2⃣️使用singletonObjects 和 earlysingletonObjects
成品放在singletonObjects,半成品放在earlysingletonObjects

流程:
实例化A ->将半成品的A放入earlySingletonObjects中 ->填充A的属性时发现取不到B->实例化B->将半成品的B放入earlySingletonObjects中->从earlySingletonObjects中取出A填充B的属性->将成品B放入singletonObjects,并从earlySingletonObjects中删除B->将B填充到A的属性中->将成品A放入singletonObjects并删除earlySingletonObjects

问题:
虽然是线程安全的,但如果A上加了AOP切面,因为earlySingletonObjects存放的是原始对象,而我们需要注入的其实是A的代理对象,因此B拿到的是没有经过代理的A

3⃣️二级缓存缓存增强后的bean
这与spring加载流程不一样,spring加载流程为:实例化->设置属性->初始化->增强
但是在循环依赖中,bean并不会增强后放入二级缓存

问题:
IOC IoC 就是 Inversion of Control,控制反转。在 Java 开发中,IoC 意味着将你设计好的类交给系 统去控制,而不是在你的类内部控制。这称为控制反转。 下面我们以几个例子来说明什么是 IoC。

一、假设我们要设计一个 Girl 和一个 Boy 类,其中 Girl 有 kiss 方法,即 Girl 想要 Kiss 一个 Boy。那么,我们的问题是,Girl 如何能够认识这个 Boy?、

在我们中国,常见的MM与 GG 的认识方式有以下几种:1 青梅竹马;2 亲友介绍;3 父母包 办。 那么哪一种才是最好呢? 青梅竹马:Girl 从小就知道自己的 Boy。
image.pngimage.png
然而从开始就创建的 Boy 缺点就是无法在更换。并且要负责 Boy 的整个生命周期。如果我们 的 Girl 想要换一个怎么办?(木笔严重不支持 Girl 经常更换 Boy) 亲友介绍:由中间人负责提供 Boy 来见面
image.png
image.png
亲友介绍,固然是好。如果不满意,尽管另外换一个好了。但是,亲友 BoyFactory 经常是以 Singleton 的形式出现,不然就是,存在于 Globals,无处不在,无处不能。实在是太繁琐了 一点,不够灵活。我为什么一定要这个亲友掺和进来呢?为什么一定要付给她介绍费呢?万 一最好的朋友爱上了我的男朋友呢? 父母包办:一切交给父母,自己不用费吹灰之力,只需要等着 Kiss 就好了。
image.png
Well,这是对 Girl 最好的方法,只要想办法贿赂了 Girl 的父母,并把 Boy 交给他。那么我们 就可以轻松的和 Girl 来 Kiss 了。看来几千年传统的父母之命还真是有用哦。至少 Boy 和 Girl 不用自己瞎忙乎了。 这就是 IOC,将对象的创建和获取提取到外部。由外部容器提供需要的组件。 我们知道好莱坞原则:“Do not call us, we will call you.” 意思就是,You, girlie, do not call the boy

We will feed you a boy。 我们还应该知道依赖倒转原则即 Dependence Inversion Princinple,DIP。Eric Gamma 说,要 面向抽象编程。面向接口编程是面向对象的核心。
image.png
组件应该分为两部分,即 Service, 所提供功能的声明 Implementation, Service 的实现好处是: 多实现可以任意切换,防止 “everything depends on everything” 问题.即具体依赖于具体。 所以,我们的 Boy 应该是实现 Kissable 接口。这样一旦 Girl 不想 kiss 可恶的 Boy 的话,还可 以 kiss 可爱的 kitten 和慈祥的 grandmother。

二、IOC 的 type IoC 的 Type 指的是 Girl 得到 Boy 的几种不同方式。我们逐一来说明。 IOC type 0:不用 IOC
image.png
image.png
Girl 自己建立自己的 Boy,很难更换,很难共享给别人,只能单独使用,并负责完全的生命 周期。
IOC type 1,先看代码:
image.png
这种情况出现于 Avalon Framework。一个组件实现了 Servicable 接口,就必须实现 service 方法,并传入一个 ServiceManager。其中会含有需要的其它组件。只需要在 service 方法中初 始化需要的 Boy。另外,J2EE 中从 Context 取得对象也属于 type 1,它依赖于配置文件:
IOC type 2:
IOC type 3
image.png
Type 2 出现于 Spring Framework,是通过 JavaBean 的 set 方法来将需要的 Boy 传递给 Girl。 它必须依赖于配置文件。
image.png
这就是 PicoContainer 的组件 。通过构造函数传递 Boy 给 Girl。
image.png
Well,以上的这些理论部分有些已经有了新的定义了。过些天我会再写一些文章具体说明。 比如,原来的三种 type 结构现在已经重新定义为依赖注射的许多层次。 IoC 很年轻,还在发展。伴随着 IOC 的发展,AOP,COP,SOP 等等都在不断的发展。作为程 序员,随时关注着新的思想的发展是一件很轻松愉快的事情。有没有人愿意和我一起探讨学 习共同进步呀! 大家发的答案我也都看了,回答的都对~,不太全面~ 按照我给的 下次按照要求进行回答 把木笔老师的答案划分成了五代发展史:第一代:自己 new;第二代:通过使用接口实现; 第三代:工厂模式(由工厂生产);第四代:服务中心代理模式(注册到服务中心,使用时 在服务中心并查找返回代理);第五代,set 注入(IoC 容器管理)