一、AOP简介

AOP:面向切面编程,基于动态代理实现,
20201222171441.png
切点:需要添加额外代码(事务)的方法叫做切点
前置通知:在切点方法之前执行的代码叫做前置通知(例如:开始事务)
后置通知:在切点方法执行完毕之后执行的代码叫做后置通知(例如:提交事务)
切面:切点+通知称之为切面

通知分类:
前置通知:方法执行之前执行
后置通知:方法体执行完毕但是还没rerun之前执行
返回后通知:在rerun执行完毕之后执行(提交)
异常通知:方法出现异常之后执行(回滚)
环绕通知:在方法执行前后执行,通常用来做日志处理

二、AOP的使用

spring在实现AOP的时候,优先以JDK代理生成代理类及代理类对象,只有被代理类没有实现任何接口是才会CGLIB代理

2.1 schema-based方案(使用较少)

这种方案每种通知就是一类。不利于维护

前置通知类:

  1. //前置通知类
  2. public class BankServiceBeforeAdvice implements MethodBeforeAdvice{
  3. @Override
  4. public void before(Method method, Object[] args, Object object) throws Throwable {
  5. System.out.println("====开启事务====");
  6. }
  7. }

返回后通知类:

  1. //返回后通知
  2. public class BankServiceReturnAfterAdvice implements AfterReturningAdvice{
  3. @Override
  4. public void afterReturning(Object object, Method method, Object[] args, Object object2) throws Throwable {
  5. System.out.println("====提交事务====");
  6. }
  7. }

在spring的配置文件中配置通知类bean、切面及被代理对象
三步:

  1. 配置通知对象
  2. 配置切面:指定哪些方法与通知进行关联
  3. 配置被代理类对象(得到的是代理类对象,通过JDK代理生成的)

    <?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 
                       http://www.springframework.org/schema/beans/spring-beans.xsd
                       http://www.springframework.org/schema/context
                       http://www.springframework.org/schema/context/spring-context.xsd
                       http://www.springframework.org/schema/aop
                       http://www.springframework.org/schema/aop/spring-aop.xsd">
     <!-- AOP相关配置 -->
     <!-- 1.配置通知对象 -->
     <bean id="beforeAdvice" class="com.woniuxy.advice.BankServiceBeforeAdvice"></bean>
     <bean id="afterReturnAdvice" class="com.woniuxy.advice.BankServiceReturnAfterAdvice"></bean>
     <!-- 2.配置切面:指定哪些方法与通知进行关联 -->
     <aop:config>
         <!-- 2.1 配置切点:找到“哪些”方法需要添加通知 -->
     <!-- 其中execution(* )第一个*代表四种访问修饰符都包含(private等)
                      *.*(..)表示在*类下的所有方法,..表示有参数,无参数的方法都包含
         -->
         <aop:pointcut expression="execution(* com.woniuxy.service.Impl.*.*(..))" 
             id="pc"/>
         <!-- 2.2 配置通知 -->
         <aop:advisor advice-ref="beforeAdvice" pointcut-ref="pc"/>
         <aop:advisor advice-ref="afterReturnAdvice" pointcut-ref="pc"/>
     </aop:config>
    
     <!-- 3.配置被代理类对象(得到的是代理类对象,通过JDK代理生成的) -->
     <bean id="bankService" class="com.woniuxy.service.Impl.BankServiceImpl"></bean>
    </beans>
    

    测试类:

    public class SchemaTest {
     @Test
     public void test() {
         ApplicationContext context = 
                 new ClassPathXmlApplicationContext("spring-context.xml");
         // 从容器中获取对象(代理类对象)
         Object object = context.getBean("bankService");
         if(object instanceof BankService) {
             BankService bankService = (BankService)object;
             bankService.transfer();
             bankService.save();
         }
     }
    }
    

2.2 aspectj方案(使用较多)

将一个类中的不同方法作为不同的通知,多个通知可以写在一个类里面

通知类:

// 通知类:包含多种通知
public class Advice {
    // 前置通知
    public void before() {
        System.out.println("前置通知:开启事务");
    }
    // 后置通知
    public void after() {
        System.out.println("后置通知:不管方法是否出现异常,都会执行");
    }
    // 返回后通知
    public void afterReturning() {
        System.out.println("返回后通知:提交事务");
    }
    // 异常通知
    public void except() {
        System.out.println("异常通知:回滚");
    }
    // 环绕通知:日志处理
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕通知:在方法执行之前执行....");
        try {
            jp.proceed();// 调用被代理类的方法
        } catch (Throwable e) {
            // 用于拼接的字符串
            String info = "";
            // 日志处理:
            // 时间+类+方法+参数+发生了XXX事
            // 签名对象
            MethodSignature methodsignature = (MethodSignature) jp.getSignature();
            // 发生异常的方法
            Method method = methodsignature.getMethod();
            // 获取发生异常的时间+类+方法名
            info = new Date().toString() + jp.getTarget().getClass() + "." + method.getName();
            // 参数
            Object[] args = jp.getArgs();
            // 遍历拼接
            for (Object object : args) {
                info+=".";
                info+=object;
            }
            System.out.println(info + "-" + e.getMessage());
            // 最后将异常信息写入日志文件、日志表格

            // 继续抛异常
            throw e;
        }
        System.out.println("环绕通知:在方法执行之后执行....");
    }
}

在spring的配置文件中配置通知类bean、切面及被代理对象
三步:

  1. 配置通知类bean
  2. 配置切面
  3. 配置被代理类对象(得到的是代理类对象,通过JDK代理生成的)

    <?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 
                       http://www.springframework.org/schema/beans/spring-beans.xsd
                       http://www.springframework.org/schema/context
                       http://www.springframework.org/schema/context/spring-context.xsd
                       http://www.springframework.org/schema/aop
                       http://www.springframework.org/schema/aop/spring-aop.xsd">
     <!-- 1.通知类bean -->
     <bean id="advice" class="com.woniuxy.aspectj.Advice"></bean>
    
     <!-- 2.配置切面 -->
     <aop:config>
         <!-- 2.1 配置切点 -->
         <aop:pointcut expression="execution(* com.woniuxy.service.Impl.*.*(..))" 
         id="pc"/>
         <!-- 2.2 配置通知 -->
         <aop:aspect ref="advice">
             <aop:before method="before" pointcut-ref="pc"/>
             <aop:after-returning method="afterReturning" pointcut-ref="pc"/>
             <aop:after-throwing method="except" pointcut-ref="pc"/>
             <aop:after method="after" pointcut-ref="pc"/>
             <aop:around method="around" pointcut-ref="pc"/>
         </aop:aspect>
     </aop:config>
    
     <!-- 3.配置被代理类对象(得到的是代理类对象,通过JDK代理生成的) -->
     <bean id="bankService" class="com.woniuxy.service.Impl.BankServiceImpl"></bean>
    </beans>
    

    测试类:

    public class AspectjTest {
     @Test
     public void test() {
         ApplicationContext context = 
                 new ClassPathXmlApplicationContext("spring-context-aspectj.xml");
         // 从容器中获取对象(代理类对象)
         Object object = context.getBean("bankService");
         if(object instanceof BankService) {
             BankService bankService = (BankService)object;
    //            bankService.transfer();
    //            bankService.save();
             bankService.get(0);
         }
     }
    }
    

三、注解实现AOP(aspectj方案)

3.1 通知类

// 通知类:包含多种通知
@Component    //加入到IOC容器中
@Aspect        //告知IOC容器这是一个切面类
public class Advice {

    //配置切点
    @Pointcut("execution(* com.woniuxy.service.Impl.*.*(..))")
    public void _pc() {}

    // 前置通知
    @Before("_pc()")
    public void before() {
        System.out.println("前置通知:开启事务");
    }

    // 后置通知
    @After("_pc()")
    public void after() {
        System.out.println("后置通知:不管方法是否出现异常,都会执行");
    }

    // 返回后通知
    @AfterReturning("_pc()")
    public void afterReturning() {
        System.out.println("返回后通知:提交事务");
    }

    // 异常通知
    @AfterThrowing("_pc()")
    public void except() {
        System.out.println("异常通知:回滚");
    }

    // 环绕通知:日志处理
    @Around("_pc()")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕通知:在方法执行之前执行....");
        try {
            jp.proceed();// 调用被代理类的方法
        } catch (Throwable e) {
            // 用于拼接的字符串
            String info = "";
            // 日志处理:
            // 时间+类+方法+参数+发生了XXX事
            // 签名对象
            MethodSignature methodsignature = (MethodSignature) jp.getSignature();
            // 发生异常的方法
            Method method = methodsignature.getMethod();
            // 获取发生异常的时间+类+方法名
            info = new Date().toString() + jp.getTarget().getClass() + "." + method.getName();
            // 参数
            Object[] args = jp.getArgs();
            // 遍历拼接
            for (Object object : args) {
                info+=".";
                info+=object;
            }
            System.out.println(info + "-" + e.getMessage());
            // 最后将异常信息写入日志文件、日志表格

            // 继续抛异常
            throw e;
        }
        System.out.println("环绕通知:在方法执行之后执行....");
    }
}

3.2 Service实现类

@Service
public class BankServiceImpl implements BankService{

    @Override
    public void transfer() {
        System.out.println("调用mapper执行转账sql");
    }

    @Override
    public void save() {
        System.out.println("调用mapper执行存钱sql");
    }

    @Override
    public void get(int money) {
        System.out.println("调用mapper执行取钱sql");
        // 手动制造异常,触发异常通知,环绕通知记录异常
//        System.out.println(1/money);
    }
}

3.3 Spring配置文件

<?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 
                      http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.springframework.org/schema/context
                      http://www.springframework.org/schema/context/spring-context.xsd
                      http://www.springframework.org/schema/aop
                      http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 1.开启spring的注解扫描
        context:component-scan扫描:component、controller、service、repository
    -->
    <context:component-scan base-package="com.woniuxy"></context:component-scan>
    <!-- 2.开启AOP扫描 
        aop:aspectj-autoproxy扫描:aspect、before、after等等
    -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

四、切入点表达式

//拦截com.woniuxy.service包下所有类的所有方法
execution(* com.woniuxy.service.*.*(..))        
//拦截所有public方法
execution(public * *(..))
//save开头的方法
execution(* save*(..))
//拦截指定类的指定方法, 拦截时候一定要定位到方法
execution(public com.woniuxy.g_pointcut.OrderDao.save(..))
//拦截指定类的所有方法
execution(* com.woniuxy.g_pointcut.UserDao.*(..))
//拦截指定包,以及其子包下所有类的所有方法
execution(* com..*.*(..))
//多个表达式
// ||和or表示两种满足其一即可,取两个表达式的并集
execution(* com.woniuxy.g_pointcut.UserDao.save()) || execution(* com.woniuxy.g_pointcut.OrderDao.save())
execution(* com.woniuxy.g_pointcut.UserDao.save()) or execution(* com.woniuxy.g_pointcut.OrderDao.save())
// &&和and 表示两种都同时满足才行,取交集
//下面2个且关系的,没有意义
execution(* com.woniuxy.g_pointcut.UserDao.save()) && execution(* com.woniuxy.g_pointcut.OrderDao.save())
execution(* com.woniuxy.g_pointcut.UserDao.save()) and execution(* com.woniuxy.g_pointcut.OrderDao.save())
//取非值   !和not表示不在该范围内的作为切点
!execution(* com.woniuxy.g_pointcut.OrderDao.save())
not execution(* com.woniuxy.g_pointcut.OrderDao.save())