P1M2_Ioc容器设计实现及Spring源码分析

265 阅读12分钟

文章内容输出来源:拉钩教育Java高薪训练营

自定义IoC&AOP框架

Spring框架整体回顾

简介

  • Spring分层(Controller, Service, Dao)的full-stack轻量级开源框架
  • 以IoC和AOP为内核,提供了展现层SpringMVC和业务层失误管理等众多的企业级应用技术
  • 整合开源世界众多著名的第三方框架和类库
  • 使用最多的Java EE企业应用开源框架
  • spring.io

发展历程

  • EJB
  • Rod Johnson(spring之父)
  • 2017.9 发布 Spring 5.x

优势

  • 方便解耦,简化开发
  • AOP编程的支持
  • 声明式事务的支持(@Transactional)
  • 方便程序的测试
  • 方便集成各种优秀框架
  • 降低JavaEE API 的使用难度(对JavaEE API进行封装)
  • 源码是经典的Java学习范例

核心结构

模块化开发:数据处理模块、Web模块、AOP/Aspects模块、Core Container模块和Test模块。

框架版本

课程使用Spring Framework 5.1.12,JDK 11.0.5, Maven 3.6.x

Spring Framework不同版本对JDK有要求:

  • JDK 8+ : 5.x
  • JDK 6+ : 4.x
  • JDK 5+ : 3.x

核心思想

IOC和AOP并不是Spring提出的,之前已经存在。Spring在技术层面把这两个思想进行了非常好的实现。

IoC

  • Inversion of Control (控制反转):由IoC容器帮助实例化对象并进行管理。

  • 控制:对象创建(实例化、管理)的权力;反转:控制权交由外部环境(Spring框架、IoC容器)

  • 解决对象之间的耦合问题

  • DI(Dependancy Injection)依赖注入

  • IoC与DI描述的是同一件事情 -> 对象实例化及依赖关系维护

    • IoC站在对象的角度,权力反转给了容器
    • DI站在容器的角度,容器会把对象依赖的其他对象注入

AOP

  • Aspect oriented Programming 面向切面编程
  • OOP(封装、继承、多态):垂直纵向的继承体系。无法解决的问题:横切逻辑代码(多个纵向顺序流程中出现的相同子流程代码)的代码重复;与业务逻辑混杂臃肿,不方便维护
  • AOP:抽取横向逻辑,与业务逻辑拆分。在不改变原有逻辑情况下,增强横切逻辑代码,解耦合,避免重复

手写实现IoC和AOP

银行转账案例代码问题分析

  1. new关键字将service层的实现类TransferServiceImpl和Dao层的具体实现类JdbcAccountDaoImpl耦合在了一起。当需要切换Dao层实现时必须修改service代码。不符合面向接口开发的最优原则。
  2. service层没有添加事务控制,出现异常可能导致数据错乱。

问题解决思路

问题一思考

  1. 全限定类名+反射:配置在xml中
  2. 工厂模式解耦合

问题二思考

  1. 数据库事务归根结底是connect的事务(connection.commit, connection.rollback), 两次更新使用两个connect,则无法统一控制。-> connect绑定当前线程
  2. 事务控制不应该添加在dao层,而应该在service层。-> 动态代理增强

设计模式-工厂模式

  1. 简单工厂模式

    INoodles noodles = SimpleNoodlesFactory.createNoodles(2); // type
    
  2. 工厂方法

    INoodlesFactory noodlesFactory = new LzINoolesFactory();
    INoodles noodles = noodlesFactory.createNoodles();
    

设计模式-单例模式

  1. 饿汉式

    private static final HungrySingleton instance = new HungrySingleton();
    
    public static HungrySingleton getInstance() {
        return instance;
    }
    
  2. 懒加载

    private static LazySingleton instance;
    
    // 加synchronized关键字实现同步,保障线程安全
    public static synchronized LazySingleton getInstance(){
    	if (instance == null) {
    		instance = new LazySingleton();
    	}
    	return instance;
    }
    

设计模式-代理模式

  1. 静态代理

    // implements IRentingHouse
    
    private IRentingHouse rentingHouse;
    
    public RentingHouseProxy(IRentingHouse rentingHouse) {
        this.rentingHouse = rentingHouse;
    }
    
    @Override
    public void rentHouse(){
        // do sth before
        rentingHouse.rentHouse();
        // do sth after
    }
    
  2. 动态代理

        public Object getJdkProxy(Object obj) {
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
                    obj.getClass().getInterfaces(), (o, method, args) -> {
                        System.out.println("JdkProxy before");
                        Object result = method.invoke(obj, args);
                        System.out.println("JdkProxy after");
                        return result;
                    });
        }
    
        public Object getCglibProxy(Object obj) {
            return Enhancer.create(obj.getClass(), (MethodInterceptor) (o, method, objects, methodProxy) -> {
                System.out.println("CglibProxy before");
                Object result = method.invoke(obj, objects);
                System.out.println("CglibProxy after");
                return result;
            });
        }
    

    JDK Proxy 要求委托对象必须实现接口,cglib则不需要

Spring IoC高级应用与源码分析

Spring IoC 基础

  • 纯xml/xml+注解

    • JavaSE:

      ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
      //或者,但不推荐
      //new FileSystemXmlApplicationContext("c:/beans.xml");
      
    • JavaWeb:

      web.xml

      <!DOCTYPE web-app PUBLIC
       "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
       "http://java.sun.com/dtd/web-app_2_3.dtd" >
      
      <web-app>
        <display-name>Archetype Created Web Application</display-name>
      
        <!--配置Spring的IoC容器的配置文件-->
        <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        <!--使用监听器启动Spring的IoC容器-->
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      </web-app>
      

      TransferServlet.java

      @Override
      public void init() throws ServletException {
          WebApplicationContext webApplicationContext =
              WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
          ProxyFactory proxyFactory = (ProxyFactory) webApplicationContext.getBean("proxyFactory");
          transferService = (TransferService) proxyFactory.getJdkProxy(webApplicationContext.getBean("transferService"));
      }
      
  • 纯注解

    • JavaSE:

      ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
      
    • JavaWeb:

      @Configuration
      @ComponentScan({"carol"})
      @PropertySource("classpath:jdbc.properties")
      public class SpringConfig {
      
          @Value("${jdbc.driver}")
          private String driverClassnmae;
          @Value("${jdbc.url}")
          private String url;
          @Value("${jdbc.username}")
          private String username;
          @Value("${jdbc.password}")
          private String password;
      
          @Bean("dataSource")
          public DataSource createDataSource(){
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName(driverClassnmae);
              druidDataSource.setUrl(url);
              druidDataSource.setUsername(username);
              druidDataSource.setPassword(password);
              return druidDataSource;
          }
      }
      
      <!DOCTYPE web-app PUBLIC
       "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
       "http://java.sun.com/dtd/web-app_2_3.dtd" >
      
      <web-app>
        <display-name>Archetype Created Web Application</display-name>
      
        <!--告诉ContextLoaderListener知道我们使用注解的方式启动ioc容器-->
        <context-param>
          <param-name>contextClass</param-name>
          <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </context-param>
        <!--配置Spring的IoC容器的配置文件-->
        <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>carol.SpringConfig</param-value>
        </context-param>
        <!--使用监听器启动Spring的IoC容器-->
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      </web-app>
      

BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IoC容器的顶层接口/基础容器;ApplicationContext是它的一个子接口/高级接口,拥有更多功能。

纯xml模式

applicationContext.xml 文件头

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
 https://www.springframework.org/schema/beans/spring-beans.xsd">
  • spring-beans.xsd xml文件的规范
  • xmlns namespace 命名空间
  • xmlns:xxx xxx为自定义前缀,使用标签时需加入前缀 xxx:abc

实例化Bean的三种方式

  1. 使用无参构造函数

    <bean id="connectionUtils" class="utils.ConnectionUtils" />
    
  2. 静态方法

    <bean id="connectionUtils" class="factory.CreateBeanFactory" factory-method="getInstanceStatic" />
    
    public class CreateBeanFactory {
        // static修饰的⽅法
        public static ConnectionUtils getInstanceStatic() {
            return new ConnectionUtils();
        }
    }
    
  3. 实例化方法

    <bean id="createBeanFactory" class="factory.CreateBeanFactory" />
    <bean id="connectionUtils" factory-bean="createBeanFactory" factory-method="getInstance" />
    
    public ConnectionUtils getInstance() {
        return new ConnectionUtils();
    }
    

Bean的作用范围及生命周期

<bean id="accountDao" class="dao.impl.JdbcAccountDaoImpl" scope="prototype">
	<property name="ConnectionUtils" ref="connectionUtils"/>
</bean>
单例模式:singleton多例模式:prototype
对象出⽣当创建容器时,对象就被创建了。当使⽤对象时,创建新的对象实例。
对象活着只要容器在,对象⼀直活着。只要对象在使⽤中,就⼀直活着。
对象死亡当销毁容器时,对象就被销毁了。⻓时间不⽤时,被java的gc回收了。
总结对象⽣命周期与容器相同spring框架只负责创建,不负责销毁

Bean标签属性

  • id:⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。

  • class:⽤于指定创建Bean对象的全限定类名。

  • name:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。

  • factory-bean:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。

  • factory-method:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。

  • scope:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。

  • init-method:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。

  • destory-method:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

DI 依赖注入的xml配置

两种注入方式
  • 构造函数注入,需提供有参构造函数

    <bean id="abc" class="xxx">
        <!--name:参数名称;index:参数索引位置-->
    	<constructor-arg name="A" ref="ref2Others" />
        <constructor-arg index="1" value="123" />
    </bean>
    
  • set方法注入(更多使用),需提供set方法

    <bean id="abc" class="xxx">
    	<property name="A" ref="ref2Others" />
        <property name="B" value="123" />
    </bean>
    
数据类型分类
  • 基本类型和String :

  • 其他Bean类型:

  • 集合类型的注入(Array, List, Set, Map, Properties)

    <property name="myArray">
        <array>
            <value>array1</value>
            <value>array2</value>
            <ref bean="song1"/>
        </array>
    </property>
    
    <property>
    	<map>
            <entry key="key1" value="value1" />
            <entry key="Key2" value-ref="PersonBean" />
            <entry key-ref="keyBean">
              <ref bean="valueBean"/>
            </entry>
        </map>
    </property>
    
    <property>
    	<set>
        	<!--与array相同, 可替换-->
        </set>
    </property>
    
    <property>
    	<props>
        	<prop key="pro1">value1</prop>
        </props>
    </property>
    

xml与注解相结合模式

  • 引入注解功能,不需要引入额外的jar包
  • xml文件依然存在,容器的启动仍然从加载xml开始
  • 第三方jar中的bean使用xml,自己开发的bean使用注解
xml注解
@Component("accountDao")
bean的id属性直接配置在注解后,不配置则默认id为类名(首字母小写)
为了分层开发,提供三个别名@Controller(控制层), @Service(服务层), @Repository(dao层)
标签的scope属性@Scope("prototype"),默认单例,注解加在类上
标签的init-method属性*@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法
标签的destroy-method属性*@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法

*:需要引入jar包

<dependency>
	<groupId>javax.annotation</groupId>
	<artifactId>javax.annotation-api</artifactId>
	<version>1.3.2</version>
</dependency>
  • DI的注解实现方式
@Autowired
@Qualifier(value="jdbcAccountDaoImpl")
private AccountDao accountDao;
  • 使用注解功能
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
">
    <!--开启注解扫描,base-package指定扫描的包路径-->
    <context:component-scan base-package="carol" />

    <!--引入外部资源文件-->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--第三方jar中的bean定义在xml中-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

</beans>

纯注解模式

  • @Configuration 注解,表名当前类是⼀个配置类
  • @ComponentScan 注解,替代 context:component-scan
  • @PropertySource,引⼊外部属性配置⽂件
  • @Import 引⼊其他配置类
  • @Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息
  • @Bean 将⽅法返回对象加⼊ SpringIOC 容器
  • [参考](#Spring IoC 基础)

Spring IoC 高级特性

lazy-init延迟加载

  • xml
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />
<beans default-lazy-init="true">
	<!--可以被单个bean的lazy-init覆盖-->
</beans>
  • 注解
@Lazy
  • debug查询生成的bean

FactoryBean 和 BeanFactory

  • BeanFactory

    是容器的顶级接口,定义了基础行为,负责生产和管理Bean的一个工厂。具体使用它下面的子接口。

  • FactoryBean

    生成某一个类型的Bean实例并返回,可以帮助自定义Bean的创建过程。(有别于普通Bean)

    package org.springframework.beans.factory;
    
    import org.springframework.lang.Nullable;
    
    public interface FactoryBean<T> {
    	@Nullable
    	T getObject() throws Exception;
    
    	@Nullable
    	Class<?> getObjectType();
    
    	default boolean isSingleton() {
    		return true;
    	}
    
    }
    

    Company.java

    package carol.pojo;
    
    public class Company {
        private String name;
        private int scale;
    
        @Override
        public String toString() {
            return "Company{" +
                    "name='" + name + '\'' +
                    ", scale=" + scale +
                    '}';
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getScale() {
            return scale;
        }
    
        public void setScale(int scale) {
            this.scale = scale;
        }
    }
    
    

    CompanyFactoryBean.java

    package carol.factory;
    
    import carol.pojo.Company;
    import org.springframework.beans.factory.FactoryBean;
    
    public class CompanyFactoryBean implements FactoryBean {
        private String companyInfo; // name + "," + scale
    
        public void setCompanyInfo(String companyInfo) {
            this.companyInfo = companyInfo;
        }
    
        @Override
        public Object getObject() {
            // 需要较复杂的操作
            Company company = new Company();
            String[] strings = companyInfo.split(",");
            company.setName(strings[0]);
            company.setScale(Integer.parseInt(strings[1]));
            return company;
        }
    
        @Override
        public Class<?> getObjectType() {
            return Company.class;
        }
    }
    
    

    applicationContext.xml

    <bean id="companyBean" class="carol.factory.CompanyFactoryBean">
    	<property name="companyInfo" value="WAP,3000"/>
    </bean>
    

    结果

    applicationContext.getBean("companyBean"); // Company
    applicationContext.getBean("&companyBean"); // CompanyFactoryBean
    

后置处理器

BeanPostProcessor

  • 该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,见下方生命周期中下划线的两步。

  • 定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每 个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判 断我们将要处理的具体的bean。

  • SpringBean的生命周期

    1. 实例化Bean

    2. 设置属性值

    3. 调用BeanNameAware的setBeanName方法

    4. 调用BeanFactoryAware的setBeanFactory方法

    5. 调用ApplicationContextAware的setApplicationContext方法

    6. 调用BeanPostProcessor的预初始化方法

    7. 调用注解@PostConstruct方法

    8. 调用InitializingBean的afterPropertiesSet方法

    9. 调用定制的初始化方法init-method(xml)

    10. 调用BeanPostProcessor的后初始化方法

      • prototype

        将准备就绪的Bean交给调用者

      • singleton

        放入Spring缓存池

        销毁过程

        1. 调用注解@PreDestroy方法
        2. 调用DisposableBean的destory方法
        3. 调用定制的销毁方法destory-method(xml)

BeanFactoryPostProcessor

  • 时机:BeanFactory产生之后,Bean实例化之前
  • BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 BeanDefinition
  • BeanFactoryPostProcessor#ConfigurableListableBeanFactory -> getBeanDefinition
  • 经典应用PropertyPlaceholderConfigurer : 替换属性占位符${jdbc.}

Spring IoC源码深度剖析

  • spring-framework源码构建

    • 编译顺序:spring-core,spring-oxm,spring-context,spring-beans,spring-aspects,spring-aop

    • tasks-other-compileTestJava

Spring IoC 容器初始化主体流程

  • IoC容器不只是map,map只是容器的一个成员,叫做单例池(singletonObjects)
  • 容器是一组组件和过程的集合,包括BeanFactory, 单例池,BeanPostProcessor等等,以及之间的协作

  • Bean生命周期关键时机点

    • 参考
    • 构造器、初始化方法、Bean后置处理器的before&after方法:#refresh -> AbstractApplicationContext#finishBeanFactoryInitialization
    • Bean工厂后置处理器初始化、方法执行:#refresh -> AbstractApplicationContext#invokeBeanFactoryPostProcessors
    • Bean后置处理器初始化:#refresh -> AbstractApplicationContext#registerBeanPostProcessors
  • AbstractApplicationContext#refresh

BeanFactory创建流程

  • 入口: AbstractRefreshableApplicationContext#refreshBeanFactory

  • 实例化:DefaultListableBeanFactory beanFactory = createBeanFactory()

  • factory属性: allowBeanDefinitionOverriding, allowCircularReferences

  • 时序图

BeanDefinition加载注册

  • 读取xml为document

    AbstractRefreshableApplicationContext#refreshBeanFactory
    	AbstractXmlApplicationContext#loadBeanDefinitions
    		AbstractBeanDefinitionReader#loadBeanDefinitions
    			XmlBeanDefinitionReader#loadBeanDefinitions
    				XmlBeanDefinitionReader#doLoadBeanDefinitions
    
  • 注册

    DefaultBeanDefinitionDocumentReader#registerBeanDefinitions
    	DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
    		DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
    			DefaultBeanDefinitionDocumentReader#parseDefaultElement
    				DefaultBeanDefinitionDocumentReader#processBeanDefinition
    					BeanDefinitionReaderUtils#registerBeanDefinition
    						DefaultListableBeanFactory#registerBeanDefinition
    

    最终注册

Bean对象创建流程

Bean实例化

装配属性

lazy-init延迟加载机制

以上跳过容器初始化阶段,在第一次使用时才会创建

循环依赖

  • 对象的相互依赖关系形成死循环

处理机制

  • 单例bean构造器参数循环依赖:无法解决
  • prototype原型bean循环依赖:无法解决
  • 单例bean通过setXxx或者@Autowired进行循环依赖
    • 三级缓存机制:1. 单例池(成型SpringBean的) 2. early(升级过程中可以做一些扩展操作) 3. singletonFactorys
    • A在对象实例化之后立刻放到三级缓存,提前暴露自己
    • B在创建过程中,发现依赖A,在三级缓存中找到尚未成型的A,升级到二级缓存
    • B创建完成后会放入一级缓存
    • A使用一级缓存中的B完成创建

三级缓存拿出来的是ObjectFactory, 可以进行一定扩展

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference

Spring AOP高级应用与源码剖析

AOP术语

在什么地方插入什么横切逻辑代码

  • Joinpoint(连接点)

    它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。(方法开始时、结束时、正常运行完毕时、方法异常时等特殊时机)

  • Pointcut(切入点)

    它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我 们看出表现层 transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对 其增强。

  • Advice(通知/增强)

    它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么 这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知。

  • Target(目标对象)

    它指的是代理的⽬标对象。即被代理对象。

  • Proxy(代理)

    它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。

  • Weaving(织⼊)

    它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。

  • Aspect(切面)

    它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯的案例中TrasnactionManager 就是⼀个切⾯。

Spring中AOP的代理选择

动态代理技术:当被代理对象没有实现任何接口时,使用cglib,否则使用jdk。可以通过配置的方式强制使用cglib。

Spring中AOP实现

纯xml方式

  • pom.xml

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.4</version>
    </dependency>
    
  • applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           https://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           https://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/aop
           https://www.springframework.org/schema/aop/spring-aop.xsd
    ">
    
        <!--横切逻辑-->
        <bean id="logUtils" class="carol.utils.LogUtils" />
    
        <aop:config >
            <!--配置切面-->
            <aop:aspect id="logAspect" ref="logUtils">
                <!--切入点-->
                <!--expression="execution(* *..*.*(..))"--->
                <!--expression="execution(* carol.service.impl.*.*(..))"--->
                <aop:pointcut id="pt1" expression="execution(public void carol.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int))"/>
                <!--方位-->
                <!--前置通知/增强-->
                <!--aop:before method="beforeMethod" pointcut-ref="pt1"-->
                <!--aop:after 最终通知,无论如何都执行-->
                <!--aop:after-returning returning="retValue" 正常执行-->
                <!--aop:after-throwing 异常执行-->
                <aop:around method="around" pointcut-ref="pt1" />
            </aop:aspect>
        </aop:config>
    </beans>
    
    package carol.utils;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    /**
     * @author Carol
     */
    public class LogUtils {
        public void beforeMethod(JoinPoint joinPoint) {
            Object[] args = joinPoint.getArgs();
            for (Object arg : args) {
                System.out.println(arg);
            }
            System.out.println("业务逻辑开始执行之前");
        }
    
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("[around] before");
            Object result = null;
            try {
                // 控制原有逻辑是否执行
                result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            } catch (Exception e) {
                System.out.println("[around] exception");
            } finally {
                System.out.println("[around] after");
            }
    
            return result;
        }
    }
    
    

半xml+半注解

  • xml中开启对注解的支持

    <!--开启spring对注解aop的⽀持-->
    <aop:aspectj-autoproxy/>
    
  • Java

    package carol.utils;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    /**
     * @author Carol
     */
    @Component
    @Aspect
    public class LogUtils {
    
        @Pointcut("execution(public void carol.service.impl.TransferServiceImpl.transfer(java.lang.String, java.lang.String, int))")
        public void pt1() {
    
        }
    
    //    @Before("pt1()")
        public void beforeMethod(JoinPoint joinPoint) {
            Object[] args = joinPoint.getArgs();
            for (Object arg : args) {
                System.out.println(arg);
            }
            System.out.println("业务逻辑开始执行之前");
        }
    
        @Around("pt1()")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("[around] before");
            Object result = null;
            try {
                // 控制原有逻辑是否执行
                result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            } catch (Exception e) {
                System.out.println("[around] exception");
            } finally {
                System.out.println("[around] after");
            }
    
            return result;
        }
    }
    
    

全注解

  • xml

    <!--开启spring对注解aop的⽀持-->
    <aop:aspectj-autoproxy/>
    
  • 配置类

    /**
     * @author Carol
     */
    @Configuration
    @ComponentScan({"carol"})
    @PropertySource("classpath:jdbc.properties")
    @EnableAspectJAutoProxy
    public class SpringConfig {
    }
    

Spring声明式事务的支持

  • 四大特性:原子性、一致性、隔离性、持久性

  • 隔离级别 (处理并发问题)

    • 问题:脏读,不可重复读(update),虚读(幻读)(insert/delete)

      脏读不可重复读幻读
      Serializable(串行化)
      Repeatable read(可重复读)x
      Read Commited(读已提交)xx
      Read Uncommitted(读未提交)xxx
    • MySQL默认是Repeatable read(可重复读)

  • 事务的传播行为

    A调用B,AB本身都被添加了事务控制,需要协商

    • PROPAGATION_REQUIRED

      如果当前没有事务,就新建一个事务。如果已经存在一个事务中,加入其中。最常见。

    • PROPAGATION_SUPPORTS

      支持当前事务,如果当前没有事务,则以非事务的方式执行。

Spring中事务的API

  • PlatformTransactionManager

    事务管理器的顶级接口

    public interface PlatformTransactionManager {
    
    	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    			throws TransactionException;
    
    	void commit(TransactionStatus status) throws TransactionException;
    
    	void rollback(TransactionStatus status) throws TransactionException;
    }
    

纯xml模式

  • pom.xml

    <!--spring aop的jar包支持-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    
    <!--第三方的aop框架aspectj的jar-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    
    <!--引入spring声明式事务相关-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.1.12.RELEASE</version>
    </dependency>
    
  • xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans  xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
            xmlns:aop="http://www.springframework.org/schema/aop"
            xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            https://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            https://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
        <!--第三方jar中的bean定义在xml中-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
         </bean>
    
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <constructor-arg name="dataSource" ref="dataSource"/>
        </bean>
    
        <!--spring声明式事务配置,声明式事务无非就是配置一个aop,只不过有些标签不一样罢了-->
        <!--横切逻辑-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
        </bean>
        
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!--定制事务细节,传播行为、隔离级别等-->
            <tx:attributes>
                <!--一般性配置-->
                <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"/>
                <!--针对查询的覆盖性配置-->
                <tx:method name="query*" read-only="true" propagation="SUPPORTS"/>
            </tx:attributes>
        </tx:advice>
       
        <aop:config>
            <!--advice-ref指向增强=横切逻辑+方位-->
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))"/>
        </aop:config>
    

xml+注解

  • xml配置

    <!--声明式事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
  • 接口、类或者方法上加上@Transactional注解(优先级:方法>类>接口)

    @Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
    

纯注解

  • 在Spring的配置类上添加

    @EnableTransactionManagement//开启spring注解事务的⽀持
    public class SpringConfiguration {
    }
    

Spring AOP源码深度剖析

AOP代理对象创建流程

调用栈

o.sf.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean
|
o.sf.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAterInitialization
|
o.sf.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
|
o.sf.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
|
o.sf.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy
|
o.sf.aop.framework.DefaultAopProxyFactory#createAopProxy
|
o.sf.aop.framework.CglibAopProxy#getProxy

Spring声明式事务控制

@EnableTransactionManagement @Transactional