Spring Aop基本介绍

/ Java / 没有评论 / 2751浏览

AOP基本介绍

AOP的底层基于代理设计模式来实现的,通常用于拦截函数的执行, 并执行特定的代码,用于在多处代码中执行相同的事情。

什么地方使用AOP

  1. 在不同的地方做相同的事情,发现代码重复。比如说每个请求都需要进行日志打印
  2. 通常用于打印日志、处理异常、事物控制、性能统计

AOP包含哪几点

  1. 关注点 -> 重复代码
  2. 切面 -> 抽取重复代码
  3. 切入点 -> 拦截哪些方法

spring AOP实现基础,容器有如下几次扫描过程:

Spring AOP 的实现方式

  1. XML
  1. 注解

Spring AOP 通知类型

前置通知、后置通知、返回通知、异常通知、环绕通知

Spring AOP 注解实现需要用到的注解

  1. @Component
  2. @Aspect
  3. @Pointcut
  4. @Before
  5. @After
  6. @AfterReturning
  7. @AfterThrowing
  8. @Around

@Component

解释:就是spring注解里面的注解,泛指,只是没有像@Service、@Controller 一样,具有特殊意义。这个注解就是把切面类,纳入到spring容器管理,相当于。@Component=bean id="transaction" class="..Transaction",这个写法。

@Aspect

此注解的作用:标识这个类就是切面。@Aspect相当于xml文件中的aop配置标签:<aop:config> </aop:config>。然后切面里面,就有对应的切入点,通知等等。

@Pointcut

此注解的作用:标注一个切入点。此注解不能单独存在,必须依附在切面。一个切入点声明有两个部分:一个包含名字和任意参数的签名,还有一个切入点表达式,该表达式决定了我们关注那个方法的执行。作为切入点签名的方法必须返回void 类型:

@Pointcut("execution(\* com.lxk.spring.aop.annotation.PersonDaoImpl.\*(..))")
private void aa(){}//切入点签名

相当于如下:在xml配置切入点。

<aop:pointcut 
expression="execution(\* cn.itcast.spring0401.aop.annotation.PersonDaoImpl.\*(..))" 
id="aa()"/>

然后现在切入点就是这个aa(),方法啦。

AOP 注解直接实现

package com.yanzuoguang.lightframe.demo.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * Created by yanzu on 2018/5/10.
 */
@Aspect     // 定义切面
@Component  // 注入到Spring容器中
public class AopDemo {

    // Spring 前置通知
    @Before("execution(* com.yanzuoguang.lightframe.controller..*.*(..))")
    public void begin(JoinPoint joinPoint) {
        System.out.println("前置通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()));
    }

    @After("execution(* com.yanzuoguang.lightframe.controller..*.*(..))")
    public void commit(JoinPoint joinPoint) {
        System.out.println("后置通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()));
    }

    @AfterReturning(value = "execution(* com.yanzuoguang.lightframe.controller..*.*(..))", returning = "ret")
    public void afterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("返回通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()) 
                           + " 返回: " + ret);
    }

    @AfterThrowing(value = "execution(* com.yanzuoguang.lightframe.controller..*.*(..))", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        System.out.println("异常通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()) 
                           + " 错误:" + ex.getClass().getName() 
                           + " :" + ex.getMessage());
    }

    @Around("execution(* com.yanzuoguang.lightframe.controller..*.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object ret = null;
        try {
            System.out.println("环绕通知前" + proceedingJoinPoint.toString() 
                               + "参数:" + Arrays.asList(proceedingJoinPoint.getArgs()));
            ret = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            return ret;
        } catch (Exception ex) {
            ret = ex;
            throw ex;
        } finally {
            System.out.println("环绕通知后" + proceedingJoinPoint.toString() 
                               + "参数:" + Arrays.asList(proceedingJoinPoint.getArgs()) 
                               + " 返回: " + ret);
        }
    }
}

Aop注解切面实现

package com.yanzuoguang.lightframe.demo.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * Created by yanzu on 2018/5/10.
 */
@Aspect     // 定义切面
@Component  // 注入到Spring容器中
public class AopDemo {

    @Pointcut("execution(* com.yanzuoguang.lightframe.controller..*.*(..))")
    public void cut(){
    }

    // Spring 前置通知
    @Before("cut()")
    public void begin(JoinPoint joinPoint) {
        System.out.println("前置通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()));
    }

    @After("cut()")
    public void commit(JoinPoint joinPoint) {
        System.out.println("后置通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()));
    }

    @AfterReturning(value = "cut()", returning = "ret")
    public void afterReturning(JoinPoint joinPoint, Object ret) {
        System.out.println("返回通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()) 
                           + " 返回: " + ret);
    }

    @AfterThrowing(value = "cut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        System.out.println("异常通知" + joinPoint.toString() 
                           + "参数:" + Arrays.asList(joinPoint.getArgs()) 
                           + " 错误:" + ex.getClass().getName() + " :" + ex.getMessage());
    }

    @Around("cut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object ret = null;
        try {
            System.out.println("环绕通知前" + proceedingJoinPoint.toString() 
                               + "参数:" + Arrays.asList(proceedingJoinPoint.getArgs()));
            ret = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
            return ret;
        } catch (Exception ex) {
            ret = ex;
            throw ex;
        } finally {
            System.out.println("环绕通知后" + proceedingJoinPoint.toString() 
                               + "参数:" + Arrays.asList(proceedingJoinPoint.getArgs()) 
                               + " 返回: " + ret);
        }
    }
}

Aop XML实现

<aop:config>
    <!-- 定义一个切入点表达式: 拦截哪些方法 -->
    <aop:pointcut 
        id="pt"
        expression="execution(* com.yanzuoguang.lightframe.controller..*(..))" />
    <!-- 切面,aop为指定需要引用的Spring实体 -->
    <aop:aspect ref="aop">
        <!-- 环绕通知 -->
        <aop:around method="around" pointcut-ref="pt" />
        <!-- 前置通知: 在目标方法调用前执行 -->
        <aop:before method="begin" pointcut-ref="pt" />
        <!-- 后置通知: -->
        <aop:after method="after" pointcut-ref="pt" />
        <!-- 返回后通知 -->
        <aop:after-returning method="afterReturning"
            pointcut-ref="pt" />
        <!-- 异常通知 -->
        <aop:after-throwing method="afterThrowing"
            pointcut-ref="pt" />
    </aop:aspect>
</aop:config>

AspectJ中的exection表达式小结:

基本语法格式为:

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)  
除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。

下面,我们给出各种使用execution()函数实例。

基本Demo: execution(* com.sample.service.impl..*.*(..))

解释如下:

  1. 符号含义 execution() 表达式的主体;
  2. 第一个*符号, 表示返回值的类型任意;
  3. com.sample.service.impl指的是AOP所切的服务的包名,即,我们的业务部分
  4. 包名后面的..表示当前包及子包
  5. 第二个*,表示类名,*即所有类。此处可以自定义,下文有举例
  6. .*(..) 表示任何方法名,括号表示参数,两个点表示任何参数类型

1. 通过方法签名定义切点

execution(public * *(..))

匹配所有目标类的public方法,但不匹配SmartSeller和protected voidshowGoods()方法 。第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法;

execution(* *To(..))

匹配目标类所有以To为后缀的方法。它匹配NaiveWaiter和NaughtyWaiter的greetTo()和serveTo()方法。第一个*代表返回类型,而*To代表任意以To为后缀的方法;

2. 通过类定义切点

execution(*com.baobaotao.Waiter.*(..))

匹配Waiter接口的所有方法,它匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()方法。第一个*代表返回任意类型,com.baobaotao.Waiter.*代表Waiter接口中的所有方法;

execution(*com.baobaotao.Waiter+.*(..))

匹配Waiter接口及其所有实现类的方法,它不但匹配NaiveWaiter和NaughtyWaiter类的greetTo()和serveTo()这两个Waiter接口定义的方法,同时还匹配NaiveWaiter#smile()和NaughtyWaiter#joke()这两个不在Waiter接口中定义的方法。

3. 通过类包定义切点

在类名模式串中,“.*”表示包下的所有类,而“..*”表示包、子孙包下的所有类。

execution(* com.baobaotao.*(..))

匹配com.baobaotao包下所有类的所有方法;

execution(* com.baobaotao..*(..))

匹配com.baobaotao包、子孙包下所有类的所有方法,如com.baobaotao.dao,com.baobaotao.servier以及com.baobaotao.dao.user包下的所有类的所有方法都匹配。“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类;

execution(* com..*.*Dao.find*(..))

匹配包名前缀为com的任何包下类名后缀为Dao的类中以find为前缀方法。如com.baobaotao.UserDao#findByUserId()、com.baobaotao.dao.ForumDao#findById()的方法都匹配切点。

4. 通过方法入参定义切点

切点表达式中方法入参部分比较复杂,可以使用“*”“..”通配符,其中“*”表示任意类型的参数,而“..”表示任意类型参数且参数个数不限。

execution(* joke(String,int)))

匹配joke(String,int)方法,且joke()方法的第一个入参是String,第二个入参是int。它匹配NaughtyWaiter#joke(String,int)方法。如果方法中的入参类型是java.lang包下的类,可以直接使用类名,否则必须使用全限定类名,如joke(java.util.List,int);

execution(* joke(String,*)))

匹配目标类中的joke()方法,该方法第一个入参为String,第二个入参可以是任意类型,如joke(Strings1,String s2)和joke(String s1,double d2)都匹配,但joke(String s1,doubled2,String s3)则不匹配;

execution(* joke(String,..)))

匹配目标类中的joke()方法,该方法第 一个入参为String,后面可以有任意个入参且入参类型不限, 如joke(Strings1)、joke(String s1,String s2)和joke(String s1,double d2,Strings3)都匹配。

execution(* joke(Object+)))

匹配目标类中的joke()方法,方法拥有一个入参,且入参是Object类型或该类的子类。它匹配joke(Strings1)和joke(Client c)。如果我们定义的切点是execution(*joke(Object)), 则只匹配joke(Object object)而不匹配joke(Stringcc)或joke(Client c)。

Spring事物

事物特性

事物实现

SpringBoot 手写事物,SpringMVC将 PlatformTransactionManager 换成对应类型即可

package com.yanzuoguang.lightframe.demo.aop;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

@Component
public class TransactionUtils {
    @Autowired
    private PlatformTransactionManager transactionManager;

    //开启事物
    public TransactionStatus begin() {
        TransactionStatus transaction = transactionManager.getTransaction(new DefaultTransactionDefinition());
        System.out.println("开启事物");
        return transaction;
    }

    //提交事物
    public void commit(TransactionStatus transaction) {
        transactionManager.commit(transaction);
        System.out.println("提交事物");
    }

    //回滚事物
    public void rollback(TransactionStatus transaction) {
        transactionManager.rollback(transaction);
        System.out.println("回滚事物");
    }
}

XML声明式事物

<!-- 1. 数据源对象: C3P0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>
<!-- 2. JdbcTemplate工具类实例 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 配置事物 -->
<bean id="dataSourceTransactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
    <tx:attributes>
        <tx:method name="get*" read-only="true" />
        <tx:method name="find*" read-only="true" />
        <tx:method name="*" read-only="false" />
    </tx:attributes>
</tx:advice>
<!-- Aop配置: 拦截哪些方法(切入点表表达式) + 应用上面的事务增强配置 -->
<aop:config>
    <aop:pointcut expression="execution(* com.itmayiedu02.service.*.*(..))"
        id="pt" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pt" />
</aop:config>

Spring 7种事物传播行为

通常用的是 PROPAGATION_REQUIRED、 PROPAGATION_REQUIRES_NEW、 PROPAGATION_SUPPORTS、PROPAGATION_NOT_SUPPORTED