绿色健康小清新

耐得住寂寞,守得住繁华

Spring

Spring

Spring 框架简介及官方压缩包目录

  • 主要发明者:Rod Johnson

  • 轮子理论推崇者:

    • 轮子理论:不用重复发明轮子.
    • IT 行业:直接使用写好的代码.
  • Spring 框架宗旨:不重新发明技术,让原有技术使用起来更加方便.

  • Spring 几大核心功能

    • IoC/DI 控制反转/依赖注入
    • AOP 面向切面编程
    • 声明式事务.
  • Spring 框架 runtime

    • test: spring 提供测试功能
    • Core Container:核心容器.Spring 启动最基本的条件.
      • Beans : Spring 负责创建类对象并管理对象
      • Core: 核心类
      • Context: 上下文参数.获取外部资源或这管理注解等
      • SpEl: expression.jar
    • AOP: 实现 aop 功能需要依赖
    • Aspects: 切面 AOP 依赖的包
    • Data Access/Integration : spring 封装数据访问层相关内容
      • JDBC : Spring 对 JDBC 封装后的代码.
      • ORM: 封装了持久层框架的代码.例如 Hibernate
      • transactions:对应 spring-tx.jar,声明式事务使用.
    • WEB:需要 spring 完成 web 相关功能时需要.
      • 例如:由 tomcat 加载 spring 配置文件时需要有 spring-web包
  • Spring 框架中重要概念

    • 容器(Container): Spring 当作一个大容器.
    • BeanFactory 接口.老版本.
    • 新版本中 ApplicationContext 接口,是 BeanFactory 子接口.BeanFactory 的功能在 ApplicationContext 中都有.
  • 从 Spring3 开始把 Spring 框架的功能拆分成多个 jar.。Spring2 及以前就一个 jar

IoC(DI)

  • 中文名称:控制反转

  • 英文名称:(Inversion of Control)

  • ioC是什么?

    • ioC完成的事情就是原先由程序员主动通过new实例化对象事情转交给Spring负责
    • 控制反转中控制指的是:控制类的对象
    • 控制反转中反转指的是转交给Spring负责
    • ioC最大的作用:解耦
    • 程序员不需要管理对象,解除了对象管理和程序员之间的耦合

环境搭建

  • 导入jar包

    • 四个核心一个log
  • 在src下新建applicationContext.xml

    • 文件名称和路径自定义

    • 记住Spring容器ApplicationContext,applicationContext.xml配置的信息最终都存储到了ApplicationContext容器中

    • spring配置文件是基于schema

      • schema文件扩展名是.xsd
      • 把schema理解成DTD的升级版。比DTD具备了更好的扩展性
      • 每次引入一个xsd文件时是namespace(xmlns)
    • 配置文件中只需要引入基本schema

      • xmlns xml NameSpace : xml文件命名空间

      • xsi xml schema instance : xml Schema 实例

      • xsi:schemaLocation 指定命名空间 + Schema文件的位置

      • xmlns:alias xmlns:alias : 命名空间的别名

1
<?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            http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>

Spring创建对象的三种方式

通过无参方法创建

  • 无参构造创建:默认情况
  • 有参构造创建:需要明确配置
    • 需要在类中提供有参构造方法
    • 在applicationContext.xml中设置调用哪个构造方法创建对象
      • 如果设定的条件匹配多个构造方法执行最后的构造方法
      • index:参数的索引,从0开始
      • name:参数名
      • type:类型(区分开关键字和封装类int 和 Integer)
1
2
3
4
5
<bean  id="peo"  class="com.Spring1.pojo.People">
<!--ref引用另一个bean value基本数据类型或者String等-->
<constructor-arg index="0" name="id" type="int" value="123"></constructor-arg>
<constructor-arg index="1" name="name" type="java.lang.String" value="张三"></constructor-arg>
</bean>

实例工厂

  • 工厂设计模式:帮助创建类对象,一个工厂可以生产多个对象

  • 实例工厂:需要先创建工厂,才能生产对象

  • 实现步骤

    • 必须要有一个实例工厂
    • 在applicationContext.xml中配置工厂对象和需要创建的对象
1
<bean id="factory" class="com.Spring1.pojo.PeopleFactory"></bean> <bean id="peo1" factory-bean="factory" factory-method="newInstance"></bean>

静态工厂

1
<bean id="peo2" class="com.Spring1.pojo.PeopleFactory" factory-method="newInstance"></bean>

如何给Bean的属性赋值(注入)

  1. 通过构造方法设置值

    1
    2
    3
    4
    <bean id="StudentBean" class="spring1.entity.Student"   >
    <constructor-arg index="0" type="int" name="stuNo" value="1"></constructor-arg>
    <constructor-arg index="1" type="java.lang.String" name="stuName" value="jiang"></constructor-arg>
    </bean>
  2. 设置注入(通过set方法)

    • 如果属性是基本数据类型或String类型

      1
      2
      3
      4
      5
      6
      <bean id="peo" class="com.Spring2.Pojo.People">
      <property name="id" value="222"></property>
      <property name="name" value="张三"></property>
      <property name="map" ><null/></property> <!--设null-->
      <property name="list" value=""></property> <!--设“”-->
      </bean>
    • 如果属性是Set<?>类型或者List<?>类型或者数组或者Map

    • 如果属性是一个properties

  3. 通过p命名空间

    1
    <bean id="StudentBean" class="spring1.entity.Student" p:stuAge="2" p:map-ref="StudentMap"  ></bean>
  4. 自动注入 约定优于配置(可以在xmlns区域设置default-autowire=“ ”)

    • byName: 自动寻找:其他bean的id值=该Course类的属性名
    • byType: 其他bean的类型(class) 是否与 该Course类的ref属性类型一致 (注意,此种方式 必须满足:当前Ioc容器中 只能有一个Bean满足条件 )
    • constructor: 其他bean的类型(class) 是否与 该Course类的构造方法参数 的类型一致;此种方式的本质就是byType
    • 自动装配虽然可以减少代码量,但是会降低程序的可读性,使用时需谨慎
  5. 通过注解

DI

  • 中文名称:依赖注入

  • 英文名称:Dependency Injection

  • DI是什么?

    • DI和IoC是一样的
    • 当一个类(A)中需要依赖另一个类(B)对象时,把B赋值给A的过程叫做依赖注入

使用Spring简化MyBatis

  1. 导入mybatis所有jar包和spring基本包,spring-jdbc,spring-tx,spring-aop,spring整合mybatis的包

  2. 配置applicationContext.xml配置文件

  3. 编写代码

    • 正常编写pojo
    • 编写mapper包下时必须使用接口绑定方案,或注解方案(必须有接口)
    • 正常编写service接口和Service实现类
      • 需要在Service实现类声明Mapper接口对象,并生成get/set方法
      • spring无法管理Servlet

AOP(OOP)(四种方式)

  • AOP:中文名称面向切面编程

  • 英文名称(Aspect Oriented Programming)

AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

在传统的业务处理代码中,通常都会进行事务处理、日志记录等操作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。

为了解决这一问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充。

而Spring中的AOP是基于动态代理实现的,即 JDK动态代理和Cglib动态代理。

  • 正常程序执行流程都是纵向执行流程

    demo1—>demo2—>demo3

    • 又叫面向切面编程,在原有纵向执行流程中添加横切面
    • 不需要修改原有程序代码(体现出程序高扩展性)
      • 高扩展性
      • 原有功能相当于释放了部分逻辑.让职责更加明确
  • 面向切面编程是什么?

    在程序原有纵向执行流程中,针对某一个或某一些方法添加通知,形成横切面过程就叫做面向切面编程

常用概念

  • 原有功能:切点,pointcut
  • 前置通知:在切点之前执行的功能:before advice
  • 后置通知:在切点之后执行的功能:after advice
  • 如果切点执行过程中出现异常,会触发异常通知.throws advice
  • 所有功能总称叫做切面
  • 织入:把切面嵌入到原有功能的过程叫做织入

spring提供了2中AOP实现方式

  • Schema-based
    • 每个通知都需要实现接口或类
    • 配置spring配置文件时在<aop:config>配置
  • AspectJ
    • 每个通知不需要实现接口或类
    • 配置spring配置文件是在<aop:config>的子标签<aop:aspect>中配置

Schema-based实现步骤

  1. c导入jar包(aopalliance.jar aspectjweaver.jar)

  2. 新建通知类

    • 新建前置通知类(MethodBeforeAdvice)

      method:切点方法对象Method对象

      objects:切点方法参数

      o:切点方法所在的类的对象

      1
      public void before(Method method, Object[] objects, Object o) throws Throwable
    • 新建后置通知类(AfterReturningAdvice)

      o:切点方法的返回值

      method:切点方法对象

      objects:切点方法的参数

      o1:切点方法所在的类的对象

      1
      public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable
  3. 配置spring配置文件

    • 引入aop命名空间以及schemalocation
    • 配置通知类对象以及配置切面
    • *是通配符匹配任意方法名,任意类名,任意一级包名
    • 如果希望匹配任意方法参数 (…)
    1
    execution(* com.Spring3.*.service.impl.*.*(..) )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置通知类对象,在切面引入-->
<bean id="beforeAdvice" class="com.Spring3.MyBeforeAdvice"></bean>
<bean id="afterAdvice" class="com.Spring3.MyAfterAdvice"></bean>
<bean id="demo" class="com.Spring3.Demo"></bean>
<!--配置切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="mypoint" expression="execution(* com.Spring3.Demo.demo02() )"/>
<!--配置通知-->
<aop:advisor advice-ref="beforeAdvice" pointcut-ref="mypoint"></aop:advisor>
<aop:advisor advice-ref="afterAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
</beans>

配置异常通知的步骤(AspectJ方式)

  1. 只有当切点报异常才能触发异常通知

  2. 在spring中只有AspectJ方式提供了异常通知的方法

  3. 实现步骤

    • 新建类,在类中写任意名称的方法

      1
      2
      3
      4
      5
      public class MyThrowAdvice  {
      public void myexception(Exception e1){
      System.out.println("执行异常通知,异常message:"+e1.getMessage());
      }
      }
    • 在applicationContext.xml中配置

      <aop:aspect>的 ref 属性表示:方法在哪个类中.

      <aop: xxxx/> 表示什么通知

      method: 当触发这个通知时,调用哪个方法

      throwing: 异常对象名,必须和通知中方法参数名相同(可以不在通知中声明异常对象)

      1
      2
      3
      4
      5
      6
      <aop:config>
      <aop:aspect ref="mythrow">
      <aop:pointcut id="mypoint" expression="execution(* com.Spring3.Demo.demo01())"></aop:pointcut>
      <aop:after-throwing method="myexception" pointcut-ref="mypoint" throwing="e1" ></aop:after-throwing>
      </aop:aspect>
      </aop:config>

配置异常通知(Schema-base方式)

新建一个类实现ThrowsAdvice接口

  • 必须自己写方法,且必须叫afterThrowing

  • 有两种参数方式(必须是1个或4个)

  • 异常类型必须要与切点报的异常一致

1
2
3
4
5
6
7
public void afterThrowing(Exception ex) throws Throwable{
System.out.println("执行异常通知-schema-base方式");
}

public void afterThrowing(Method m, Object[] args, Object target, Exception ex) {
System.out.println("执行异常通知");
}

环绕通知(Schema-based方式)

把前置通知和后置通知都写到一个通知中,组成环绕通知

实现步骤:

  • 新建一个类实现MethodInterceptor(拦截器)
1
2
3
4
5
6
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕-前置");
Object result=methodInvocation.proceed(); //放行,调用切点方式
System.out.println("环绕-后置");
return result;
}

使用注解(基于Aspect)

  1. 引入context名称空间

  2. spring不会自动去寻找注解,必须告诉spring哪些包下类的中可能有注解

    1
    <context:component-scan base-package="com.aop.annotation"></context:component-scan>
  3. @Component

    • 相当于<bean/>
    • 如果没有参数,把类名首字母变小写,相当于<bean id=""/>
    • @Component(“自定义名称”)
  4. 实现步骤

    • 在spring配置文件中设置注解在哪些包中

      1
      <context:component-scan base-package="com.aop.annotation"></context:component-scan>
    • 在Demo类中添加@Component

      在方法上添加@Poiuntcut("")定义切点

      1
      @Pointcut(value = "execution(public void com.aop.annotation.Demo.demo1(String,int)  ) && args(name,id)")
    • 在通知类中配置

      @Component类被spring管理

      @Aspect相当于<aop:aspect/>表示通知方法在当前类中

扫描器 会将 指定的包 中的 @Componet @Service @Respository @Controller修饰的类产生的对象 增加到IOC容器中

@Aspect不需要 加入扫描器,只需要开启即可:<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

通过注解形式 实现的aop,如果想获取 目标对象的一些参数,则需要使用一个对象:JointPoint(直接在方法参数中加即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//此为注解方式的执行顺序
try{
try{
//@Around
//@Before
method.invoke(..);
//@Around
}catch(){
throw.....;
}finally{
//@After
}
//@AfterReturning
}catch(){
//@AfterThrowing
}

自动注入

在spring配置文件中对象名和ref="id"id名相同使用自动注入,可以不配置property

两种配置方法

  • 在<bean>中通过autowire=""配置,只对这个<bean>生效

  • 在<beans>中通过default-autowire=""配置,表当当前文件中所有<bean>都是全局配置内容

  • autowire=""可取值

    • default: 默认值,根据全局 default-autowire=””值.默认全局和局部都没有配置情况下,相当于 no
    • no: 不自动注入3.3 byName: 通过名称自动注入.在 Spring 容器中找类id
    • byType: 根据类型注入. byName:根据名称注入
    • spring 容器中不可以出现两个相同类型的
    • constructor: 根据构造方法注入.
    • 提供对应参数的构造方法(构造方法参数中包含注入对戏那个)
    • 底层使用 byName, 构造方法参数名和其他的 id相同.

Spring中加载properties文件

  1. 在src下新建xxx.properties文件

  2. 在spring配置文件中先引入xmlns:context,在下面添加

    • 如果配需要配置多个配置文件,逗号分隔

      1
      <context:property-placeholder location="classpath:com/login/db.properties"/>
  3. 添加了属性文件记载,并且在<beans>中开启自动注入注意的地方

    用sqlSessionFactoryBeanName属性

  4. 在被Spring管理的类中通过@Value("${key}")取出properties中内容

    • 添加注解扫描

      1
      <context:component-scan base-package="com.login.service"></context:component-scan>
    • 在类中添加

      • key和变量名不必相同
      • 变量名可以任意,只要保证key对应的value能转换成这个类型就可以
      1
      2
      @Value("${my.demo}") 
      private String test;

scope属性

  1. <bean>的属性

  2. 作用:控制对象有效范围(单例,多例)

  3. <bean/>标签对应的对象默认是单例的

    无论获取多少次,都是同一个对象

  4. scope可取值

    • singleton 默认值,单例
    • prototype 多例,每次请求重新实例化
    • request 每次请求重新实例化
    • session 每次会话对象时,对象是单例的
    • application 在application对象内是单例的
    • global session spring推出的一个对象,依赖于spring-webmvc-portlet,类似于session

声明式事务

  1. 编程式事务

    • 由程序员编程事务控制代码
    • OpenSessionInView编程式事务
  2. 声明式事务

    • 事务控制代码已经由spring写好.程序员只需要声明出那些方法需要进行事务控制和如何进行事务控制
  3. 声明式事务都是针对于ServiceImpl类下方法的

  4. 事务管理器基于通知(advice)的

  5. 在spring配置文件中配置声明式事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="datasource"></property>
</bean>
<!--配置声明事务-->
<tx:advice id="txAdvice" transaction-manager="txManager" >
<tx:attributes>
<!--哪些方法需要有事务管理-->
<!--方法以ins开头事务管理-->
<tx:method name="ins*" />
<tx:method name="del*"/>
<tx:method name="upd*"/>
<tx:method name="*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.Transaction.Service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
  1. 属性解释

    • name="哪些方法需要有事务管控

      支持通配符

    • readonly=“boolean” 是否是只读事务

      • 如果为true,告诉数据库此事务为只读事务.数据化优化,会对性能有一定提升,所以只要是查询的方法,建议使用此数据

      • 如果为false(默认),事务需要提交的事务,建议新增,删除,修改

    • propagation控制事务传播行为(数据库层面)

      • 当一个具有事务控制的方法,方法被另一个有事务控制的方法调用后,需要如何管理事务(新建事务?在事务中执行?把事务挂起?报异常?)
      • REQUIRED(默认值):如果当前有事务,就在事务中执行,如果当前没有事务新建一个
      • SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行
      • MANDATORY:如果当前有事务就在事务中执行,如果当前没有事务,就报错
      • REQUIRED_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起.
      • NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起
      • NEVER:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,报错
      • NESTED:必须在事务状态下执行,如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务
  2. isolation 事务隔离级别

    • 在多线程或并发访问下如何保证访问到数据具有完整性

    • 脏读:(读取未提交数据

      一个事务(A)读取到另一个事务(B)中未提交的数据,另一个事务中数据 可能进行了改变,此时A事务读取的数据可能和数据库数据不一致的,此时认为数据时脏数据,读取脏数据过程叫做脏读

    • 不可重复读前后多次读取,数据内容不一致

      • 主要针对的是某行数据(或行中某列)

      • 主要针对的操作时修改操作

      • 两次读取在同一次事务内

      • 当事务A第一次读取事务后,事务B对事务A读取的数据进行修改,事务A中再次读取的数据和之前读取的数据不一致,过程不可重复读

    • 幻读前后多次读取,数据总量不一致

      • 主要针对的操作是新增或删除
      • 两次事务的结果
      • 事务 A 按照特定条件查询出结果,事务 B 新增了一条符合条件的数据. 事务 A 中查询的数据和数据库中的数据不一致的,事务 A 好像出现了幻觉,这种情况称为幻读.
    • DEFAULT:默认值,由底层数据库自动判断应该使用什么隔离级别

    • READ_UNCOMMITTED:可以读取未提交数据,可能出现脏读,不可重复读,幻读,效率最高

    • READ_COMMITTED:只能读取其他事务已提交数据.可以防止脏读,可能出现不可重复读和幻读.

    • REPEATABLE_READ: 读取的数据被添加锁,防止其他事务修改此数据,可以防止不可重复读.脏读,可能出现幻读.

    • SERIALIZABLE: 排队操作,对整个表添加锁.一个事务在操作数据时,另一个事务等待事务操作完成后才能操作这个表.

  3. rollback-for=“异常类型全限定路径”

    1
    <tx:method name="upd*" propagation="NESTED" rollback-for="java.lang.Exception"/>

    当出现什么异常时需要进行回滚

    建议:给定该属性值。手动抛出异常一定要给定该属性值

  4. no-rollback-for=""

    当出现什么异常时不回滚事务

Spring常用注解

  1. Component创建类对象,相当于配置<bean/>

  2. Service与@Component功能相同

    写在ServiceImpl类上

  3. Repository与@Component功能相同

    写在数据访问层类上

  4. Controller与@Component

    写在控制器类上

  5. Resource(不需要写set,get方法)

    java中的注解

    默认按照byName注入,如果没有名称,按照byType注入。建议对象名和spring容器中对象名相同

  6. AutoWired(不需要写set/get)

    spring的注解

    默认按照byType注入

  7. value()获取properties文件中的内容

  8. PointCut()定义切点

  9. Aspect()定义切面类

  10. Before前置通知

  11. After后置通知

  12. AfterReturning后置通知,必须切点正确执行

  13. AfterThrowing异常通知

  14. Arround:环绕通知


AOP的核心概念

核心概念

要完全理解Spring AOP首先要理解AOP的核心概念和术语,这些术语并不是Spring指定的,而且很不幸,这些术语并不能直观理解,但是,如果Spring使用自己的术语,那将更加令人困惑。

  • Aspect:切面,由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序。事务管理(Transaction management)在java企业应用中就是一个很好的切面样例。
  • Join point:接入点,程序执行期的一个点,例如方法执行、类初始化、异常处理。 在Spring AOP中,接入点始终表示方法执行。
  • Advice:增强,切面在特定接入点的执行动作,包括 “around,” “before” and "after"等多种类型。包含Spring在内的许多AOP框架,通常会使用拦截器来实现增强,围绕着接入点维护着一个拦截器链。
  • Pointcut:切点,用来匹配特定接入点的谓词(表达式),增强将会与切点表达式产生关联,并运行在任何切点匹配到的接入点上。通过切点表达式匹配接入点是AOP的核心,Spring默认使用AspectJ的切点表达式。
  • Introduction:引入,为某个type声明额外的方法和字段。Spring AOP允许你引入任何接口以及它的默认实现到被增强对象上。
  • Target object:目标对象,被一个或多个切面增强的对象。也叫作被增强对象。既然Spring AOP使用运行时代理(runtime proxies),那么目标对象就总是代理对象。
  • AOP proxy:AOP代理,为了实现切面功能一个对象会被AOP框架创建出来。在Spring框架中AOP代理的默认方式是:有接口,就使用基于接口的JDK动态代理,否则使用基于类的CGLIB动态代理。但是我们可以通过设置proxy-target-class=“true”,完全使用CGLIB动态代理。
  • Weaving:织入,将一个或多个切面与类或对象链接在一起创建一个被增强对象。织入能发生在编译时 (compile time )(使用AspectJ编译器),加载时(load time),或运行时(runtime) 。Spring AOP默认就是运行时织入,可以通过枚举AdviceMode来设置。

模拟aspect advice的执行过程

在这里我们不再展示测试代码,而是通过简单的代码来模拟aspect advice的执行过程。

尽管Spring AOP是通过动态代理来实现的,但是我们可以绕过代理,直接模拟出它的执行过程,示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package doubt;
public class AspectAdviceInvokeProcess {
public static void main(String[] args){
try {
//正常执行
AspectInvokeProcess(false);
System.out.println("=====分割线=====");
//异常执行
AspectInvokeProcess(true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

/**
* 切面执行过程
* @param isException
* @throws Exception
*/
public static void AspectInvokeProcess(boolean isException) throws Exception{
try {
try {
aroundAdvice(isException);
} finally {
afterAdvice();
}
afterReturningAdvice();
return;
} catch (Exception e) {
afterThrowingAdvice(e);
throw e;
return;
}
}

/**
* 环绕增强
* @param isException
* @throws Exception
*/
private static void aroundAdvice(boolean isException) throws Exception {
System.out.println("around before advice");
try {
JoinPoint_Proceed(isException);
} finally {
System.out.println("around after advice");
}
}

/**
* 编织后的接入点执行过程
* @param isException
*/
public static void JoinPoint_Proceed(boolean isException){
beforeAdvice();
targetMethod(isException);
}

/**
* 前置增强
*/
private static void beforeAdvice() {
System.out.println("before advice");
}

/**
* 目标方法
* @param isException
*/
private static void targetMethod(boolean isException) {
System.out.println("target method 执行");
if(isException)
throw new RuntimeException("异常发生");
}

/**
* 后置增强
*/
private static void afterAdvice() {
System.out.println("after advice");
}

/**
* 正常返回增强
*/
private static void afterReturningAdvice() {
System.out.println("afterReturning");
}

/**
* 异常返回增强
* @param e
* @throws Exception
*/
private static void afterThrowingAdvice(Exception e) throws Exception {
System.out.println("afterThrowing:"+e.getMessage());
}
}

同一aspect,不同advice的执行顺序

上述代码的执行结果,直接体现了同一apsect中不同advice的执行顺序,结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
around before advice
before advice
target method 执行
around after advice
after advice
afterReturning
===============分割线==============
around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生

得出结论:

不同aspect,advice的执行顺序

详情可见,《Spring官方文档》

Spring AOP通过指定aspect的优先级,来控制不同aspect,advice的执行顺序,有两种方式:

  • Aspect 类添加注解:org.springframework.core.annotation.Order,使用注解value属性指定优先级。

  • Aspect 类实现接口:org.springframework.core.Ordered,实现 Ordered 接口的 getOrder() 方法。

其中,数值越低,表明优先级越高,@Order 默认为最低优先级,即最大数值:

1
2
3
4
5
/**
* Useful constant for the lowest precedence value.
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

最终,不同aspect,advice的执行顺序:

  • 入操作(Around(接入点执行前)、Before),优先级越高,越先执行;

  • 一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行接入点;

  • 出操作(Around(接入点执行后)、After、AfterReturning、AfterThrowing),优先级越低,越先执行。

  • 一个切面的出操作执行完,才轮到下一切面,直到返回到调用点;

如下图所示:

先入后出,后入先出

同一aspect,相同advice的执行顺序

同一aspect,相同advice的执行顺序并不能直接确定,而且 @Order 在advice方法上也无效,但是有如下两种变通方式:

  • 将两个 advice 合并为一个 advice,那么执行顺序就可以通过代码控制了
  • 将两个 advice 分别抽离到各自的 aspect 内,然后为 aspect 指定执行顺序

Transactional Aspect的优先级

Spring事务管理(Transaction Management),也是基于Spring AOP。

在Spring AOP的使用中,有时我们必须明确自定义aspect的优先级低于或高于事务切面(Transaction Aspect),所以我们需要知道:

事务切面优先级:默认为最低优先级

1
LOWEST_PRECEDENCE = Integer.MAX_VALUE

事务的增强类型:Around advice,其实不难理解,进入方法开启事务,退出方法提交或回滚,所以需要环绕增强。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract aspect AbstractTransactionAspect extends TransactionAspectSupport implements DisposableBean {

protected AbstractTransactionAspect(TransactionAttributeSource tas) {
setTransactionAttributeSource(tas);
}

@SuppressAjWarnings("adviceDidNotMatch")
Object around(final Object txObject): transactionalMethodExecution(txObject) {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
try {
return invokeWithinTransaction(methodSignature.getMethod(), txObject.getClass(), new InvocationCallback() {
public Object proceedWithInvocation() throws Throwable {
return proceed(txObject);
}
});
}
catch (RuntimeException ex) {
throw ex;
}
catch (Error err) {
throw err;
}
catch (Throwable thr) {
Rethrower.rethrow(thr);
throw new IllegalStateException("Should never get here", thr);
}
}
}

如何修改事务切面的优先级:

在开启事务时,通过设置 @EnableTransactionManagement 和 tx:annotation-driven/ 中的, order 属性来修改事务切面的优先级。

详情可见,《Spring官方文档》

-------------本文结束感谢您的阅读-------------
六经蕴籍胸中久,一剑十年磨在手

欢迎关注我的其它发布渠道