SpringAOP深入学习

Posted Qiao_Zhi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringAOP深入学习相关的知识,希望对你有一定的参考价值。

----------------------Spring AOP介绍------------------

1.编程范式概念

面向过程编程:C

面向对象编程:c++,Java

函数式编程

事件驱动编程:GUI编程

面向切面编程(AOP)

2.AOP是什么

 (1)是一种编程范式,不是编程语言

(2)解决特定问题,不能解决所有问题

(3是OOP的补充,不是替代。

3.AOP初衷:

1.解决代码重复问题,增加代码的可读性与可维护性

2.关注点分离,使程序员可以将更多的精力放在开发主要功能中。

4.SpringAOP的两种实现方式

1.基于xml配置的方式

2.基于注解的方式(重点)

 

5.AOP中名词:

Aspect:切面(切入点+通知)

join point:连接点,所有可以织入通知的方法

point cut:切入点,需要|已经织入通知的方法

advice:通知。需要增强的代码

weaving:织入,动词,将通知应用到切点的过程

target:目标对象

proxy:代理对象

 

注解AOP:

 

 表达式:

 

  •  选择器类型

 

 

  •  wildcards通配符:

*  匹配任意数量的字符

+  匹配定指定类及其子类

..  一般用于匹配任意数的子包或参数

 

  • operators:运算符

&& 与操作符

||    或操作符

!     非操作符

6.将通知织入切点的时机

1.编译时期(Aspect)

2.类加载时期(Aspect5+)

 3.运行时期(Spring AOP)

  从静态代理到动态代理(基于接口的代理与基于继承的代理)。

   

 

 

 ---------------------------Spring AOP实战--------------------

1.基于xml配置方式的AOP实现

 1.编写通知类:(通过反射精确的定位到每个方法可以做一些详细的处理,两种方式定位到方法)

 

package cn.qlq.XMLaspect;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;

//xml切面
public class MyAdvice {
    // 前置通知
    public void before(JoinPoint joinPoint) throws Exception {
        System.out.println("---------------前置通知开始~~~~~~~~~~~");
        // 获取到类名
        String targetName = joinPoint.getTarget().getClass().getName();
        System.out.println("代理的类是:" + targetName);
        // 获取到方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("增强的方法是:" + methodName);
        // 获取到参数
        Object[] parameter = joinPoint.getArgs();
        System.out.println("传入的参数是:" + Arrays.toString(parameter));
        // 获取字节码对象
        Class<?> targetClass = Class.forName(targetName);
        // 获取所有的方法
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == parameter.length) {
                    System.out.println("找到这个方法");
                    //处理一些业务逻辑
                    break;
                }
            }
        }
        System.out.println("---------------前置通知结束~~~~~~~~~~~");
    }

    // 后置通知(异常发生后不会调用)
    public void afterRunning() {
        System.out.println("这是后置通知(异常发生后不会调用)");
    }

    // 环绕通知(推荐下面这种方式获取方法)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("----------------环绕通知之前 的部分----------------");
        // 获取到类名
        String targetName = pjp.getTarget().getClass().getName();
        System.out.println("代理的类是:" + targetName);
        // 获取到参数
        Object[] parameter = pjp.getArgs();
        System.out.println("传入的参数是:" + Arrays.toString(parameter));
        // 获取到方法签名,进而获得方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        System.out.println("增强的方法名字是:" + method.getName());
        //处理一些业务逻辑
        
        
        // 获取参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        System.out.println("参数类型是:" + parameterTypes.toString());
        
        //让方法执行(proceed是方法的返回结果,可以针对返回结果处理一下事情)
        System.out.println("--------------方法开始执行-----------------");
        Object proceed = pjp.proceed();
        
        //环绕通知之后的业务逻辑部分
        System.out.println("----------------环绕通知之后的部分----------------");
        return proceed;
    }
    // 异常通知
    public void afterException() {
        System.out.println("这是异常通知(发生异常后调用)~~~~~~~~~~~");
    }

    // 最终通知(发生异常也会在最终调用)
    public void after() {
        System.out.println("这是后置通知(发生异常也会在最终调用)");
    }
}

 

 

 

2.编写service接口和实现类(需要被增强的类)

  • UserService.java
package cn.qlq.service;

import java.sql.SQLException;

public interface UserService {

    void save();
    void save(String userId)throws Exception;
    void update();
    void delete();
    void find();
}

 

 

 

 

  • UserServiceImpl.java
package cn.qlq.service;

import org.springframework.stereotype.Service;

@Service("userService")
public class UserServiceImpl implements UserService {

    @Override
    public void save() {
        System.out.println("保存用户。。。无参数");
    }

    @Override
    public void save(String userId) throws Exception {
        int i = 1 / 0;
        System.out.println("保存用户.....(有参数)");
    }

    @Override
    public void update() {
        System.out.println("更新用户....");
    }

    @Override
    public void delete() {
        System.out.println("删除用户....");
    }

    @Override
    public void find() {
        System.out.println("查找用户");
    }
}

 

 

 

 

3.xml配置切入点+通知形成切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
    <!-- 1.首先导入aop约束,在beans元素添加命名空间 -->
    <context:component-scan base-package="cn.qlq.service"></context:component-scan>
    <!-- 3.配置通知对象 -->
    <bean name="advice" class="cn.qlq.XMLaspect.MyAdvice"></bean>
    <!-- 4.通知对象织入目标对象 .expression:切入点表达式。id:切入点名字
    public void cn.qlq.service.UserServiceImpl.save()    对指定类型指定返回值的空参方法进行增强
    void cn.qlq.service.UserServiceImpl.save()          public可以省去,默认public
    * cn.qlq.service.UserServiceImpl.save()        对返回类型不做要求,可以对任何返回类型织入
    * cn.qlq.service.UserServiceImpl.*()        对返回类型不做要求,对方法名字不做要求
    * cn.qlq.service.UserServiceImpl.*(..)        对返回类型不做要求,对方法名字不做要求,对参数也不做要求
    * cn.qlq.service.*ServiceImpl.*(..)        对service包下所有以ServiceImpl结尾的类中的任意参数的任意方法增强
    * cn.qlq.service..*ServiceImpl.*(..)        与上面不同的是对service包的子包也要进行增强(一般不用)    -->
<aop:config>
    <!-- 配置切点 -->
   <aop:pointcut expression="execution(* cn.qlq.service.*ServiceImpl.*(..))" id="pc"/>
   <!-- 将通知织入切点形成切面 -->
   <aop:aspect ref="advice">
           <aop:before method="before" pointcut-ref="pc"/>
           <aop:after-returning method="afterRunning" pointcut-ref="pc"/>
           <aop:after method="after" pointcut-ref="pc"/>
           <aop:around method="around" pointcut-ref="pc"/>
           <aop:after-throwing method="afterException" pointcut-ref="pc"/>
   </aop:aspect>
</aop:config>

</beans>

 

4.编写测试类:

package cn.qlq.test;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.qlq.service.UserService;

//帮我们创建容器
@RunWith(SpringJUnit4ClassRunner.class)
//指定创建容器时使用的配置文件
@ContextConfiguration("classpath:cn/qlq/XMLaspect/applicationContext.xml")
public class SpringXMLAopTest {

    @Resource(name="userService")
    private UserService us;
    
    @Test
    public void fun1() throws Exception{
        us.save("111");
    }
    
}

结果:

---------------前置通知开始~~~~~~~~~~~
代理的类是:cn.qlq.service.UserServiceImpl
增强的方法是:save
传入的参数是:[111]
找到这个方法
---------------前置通知结束~~~~~~~~~~~
----------------环绕通知之前 的部分----------------
代理的类是:cn.qlq.service.UserServiceImpl
传入的参数是:[111]
增强的方法名字是:save
参数类型是:[Ljava.lang.Class;@5eebd82d
--------------方法开始执行-----------------
保存用户.....(有参数)
这是后置通知(异常发生后不会调用)
这是后置通知(发生异常也会在最终调用)
----------------环绕通知之后的部分----------------

 

 

 

现在在程序中抛出一个异常,查看执行结果

    @Override
    public void save(String userId) throws Exception {
        int i = 1 / 0;
        System.out.println("保存用户.....(有参数)");
    }

 

结果:

---------------前置通知开始~~~~~~~~~~~
代理的类是:cn.qlq.service.UserServiceImpl
增强的方法是:save
传入的参数是:[111]
找到这个方法
---------------前置通知结束~~~~~~~~~~~
----------------环绕通知之前 的部分----------------
代理的类是:cn.qlq.service.UserServiceImpl
传入的参数是:[111]
增强的方法名字是:save
参数类型是:[Ljava.lang.Class;@7022c24e
--------------方法开始执行-----------------
这是后置通知(发生异常也会在最终调用)
这是异常通知(发生异常后调用)~~~~~~~~~~~

 

 

通知执行顺序:

(1)没有异常(不会走异常通知)

  前置通知->环绕通知之前部分->后置通知(异常不调用)->最终通知(异常也会调用)->环绕通知之后的部分

(2)有异常(不会走环绕通知的后半部分和后置通知)

  前置通知->环绕通知之前部分->最终通知(异常也会调用)->异常通知

 

 

 

2.基于注解方式的AOP实现(重点)

 1.applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p" xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd ">
    <!-- 1.开启注解AOP -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <!-- 2.配置扫描的包 -->
    <context:component-scan base-package="cn.qlq"></context:component-scan>
</beans>

 

2.通知类:

MyAdvice.java 
package cn.qlq.annotationAOP;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect // 表示该类是一个通知类
@Component // 交给spring管理
public class MyAdvice {

    // 定义一个空方法,借用其注解抽取切点表达式
    @Pointcut("execution(* cn.qlq.service.*ServiceImpl.*(..))")
    public void pc() {
    }

    // 前置通知
    @Before("MyAdvice.pc()")
    public void before(JoinPoint joinPoint) throws Exception {
        System.out.println("---------------前置通知开始(注解)~~~~~~~~~~~");
        // 获取到类名
        String targetName = joinPoint.getTarget().getClass().getName();
        System.out.println("代理的类是:" + targetName);
        // 获取到方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("增强的方法是:" + methodName);
        // 获取到参数
        Object[] parameter = joinPoint.getArgs();
        System.out.println("传入的参数是:" + Arrays.toString(parameter));
        // 获取字节码对象
        Class<?> targetClass = Class.forName(targetName);
        // 获取所有的方法
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == parameter.length) {
                    System.out.println("找到这个方法");
                    break;
                }
            }
        }
        System.out.println("---------------前置通知结束(注解)~~~~~~~~~~~");
    }

    // 后置通知(异常发生后不会调用)
    @AfterReturning("MyAdvice.pc()")
    public void afterRunning() {
        System.out.println("这是后置通知(异常发生后不会调用)。。。。(注解)");
    }

    // 环绕通知
    @Around("MyAdvice.pc()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("----------------环绕通知之前 的部分(注解)----------------");
        // 获取到类名
        String targetName = pjp.getTarget().getClass().getName();
        System.out.println("代理的类是:" + targetName);
        // 获取到参数
        Object[] parameter = pjp.getArgs();
        System.out.println("传入的参数是:" + Arrays.toString(parameter));
        // 获取到方法签名,进而获得方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        System.out.println("增强的方法名字是:" + method.getName());
        // 获取参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        System.out.println("参数类型是:" + parameterTypes.toString());
        
        //让方法执行(proceed是拦截的方法的执行返回值,可以针对返回值做一些处理)
        System.out.println("--------------方法开始执行-----------------");
        Object proceed = pjp.proceed();
        
        //环绕通知之后的业务逻辑部分
        System.out.println("----------------环绕通知之后的部分(注解)----------------");
        return proceed;
    }

    // 异常通知
    @AfterThrowing("MyAdvice.pc()")
    public void afterException() {
        System.out.println("这是异常通知(发生异常后调用)~~~~~~~~~~~(注解)");
    }

    // 最终通知(发生异常也会在最终调用)
    @After("MyAdvice.pc()")
    public void after() {
        System.out.println("这是最终通知(发生异常也会在最终调用)........(注解)");
    }
}

 

 

3.UserService.java与UserServiceImpl.java同上

4.测试类:

package cn.qlq.test;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import cn.qlq.service.UserService;

//帮我们创建容器
@RunWith(SpringJUnit4ClassRunner.class)
//指定创建容器时使用的配置文件
@ContextConfiguration("classpath:cn/qlq/annotationAOP/applicationContext.xml")
public class SpringAnnotationAopTest {

    @Resource(name="userService")
    private UserService us;
    
    @Test
    public void fun1(){
        us.save("111");
    }
    
}

 

结果:

----------------环绕通知之前 的部分(注解)----------------
代理的类是:cn.qlq.service.UserServiceImpl
传入的参数是:[111]
增强的方法名字是:save
参数类型是:[Ljava.lang.Class;@61d60df3
--------------方法开始执行(注解)------------------
---------------前置通知开始(注解)~~~~~~~~~~~
代理的类是:cn.qlq.service.UserServiceImpl
增强的方法是:save
传入的参数是:[111]
找到这个方法
---------------前置通知结束(注解)~~~~~~~~~~~
保存用户.....(有参数)
----------------环绕通知之后的部分(注解)-----------------
这是最终通知(发生异常也会在最终调用)........(注解)
这是后置通知(异常发生后不会调用)。。。。(注解)

 

 

 

 

注意,为了抽取切点表达式,我们将上面的通知类改为如下:

 

package cn.qlq.annotationAOP;

import java.lang.reflect.Method;
import java.util.Arrays;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect // 表示该类是一个通知类
@Component // 交给spring管理
public class MyAdvice {

    // 定义一个空方法,借用其注解抽取切点表达式
    @Pointcut("execution(* cn.qlq.service.*ServiceImpl.*(..))")
    public void pc() {
    }

    // 前置通知(下面两种方式均可以)
    // @Before("MyAdvice.pc()")
    @Before("pc()")
    public void before(JoinPoint joinPoint) throws Exception {
        System.out.println("---------------前置通知开始(注解)~~~~~~~~~~~");
        // 获取到类名
        String targetName = joinPoint.getTarget().getClass().getName();
        System.out.println("代理的类是:" + targetName);
        // 获取到方法名
        String methodName = joinPoint.getSignature().getName();
        System.out.println("增强的方法是:" + methodName);
        // 获取到参数
        Object[] parameter = joinPoint.getArgs();
        System.out.println("传入的参数是:" + Arrays.toString(parameter));
        // 获取字节码对象
        Class<?> targetClass = Class.forName(targetName);
        // 获取所有的方法
        Method[] methods = targetClass.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == parameter.length) {
                    System.out.println("找到这个方法");
                    break;
                }
            }
        }
        System.out.println("---------------前置通知结束(注解)~~~~~~~~~~~");
    }

    // 后置通知(异常发生后不会调用)
    @AfterReturning("MyAdvice.pc()")
    public void afterRunning() {
        System.out.println("这是后置通知(异常发生后不会调用)。。。。(注解)");
    }

    // 环绕通知
    @Around("MyAdvice.pc()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("----------------环绕通知之前 的部分(注解)----------------");
        // 获取到类名
        String targetName = pjp.getTarget().getClass().getName();
        System.out.println("代理的类是:" + targetName);
        // 获取到参数
        Object[] parameter = pjp.getArgs();
        System.out.println("传入的参数是:" + Arrays.toString(parameter));
        // 获取到方法签名,进而获得方法
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        System.out.println("增强的方法名字是:" + method.getName());
        // 处理一些业务逻辑

        // 获取参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        System.out.println("参数类型是:" + parameterTypes.toString());

        // 让方法执行(proceed是方法的返回结果,可以针对返回结果做一些处理)
        System.out.println("--------------方法开始执行(注解)------------------");
        Object proceed = pjp.proceed();

        // 环绕通知之后的业务逻辑部分
        System.out.println("----------------环绕通知之后的部分(注解)-----------------");
        return proceed;
    }

    // 异常通知
    @AfterThrowing("MyAdvice.pc()")
    public void afterException() {
        System.out.println("这是异常通知(发生异常后调用)~~~~~~~~~~~(注解)");
    }

    // 最终通知(发生异常也会在最终调用)
    @After("MyAdvice.pc()")
    public void after() {
        System.out.println("这是最终通知(发生异常也会在最终调用)........(注解)");
    }
}

 

 

 

 

 

 

现在serviceImpl模拟抛出异常:

    @Override
    public void save(String userId) throws Exception {
        int i = 1 / 0;
        System.out.println("保存用户.....(有参数)");
    }

 

结果:

----------------环绕通知之前 的部分(注解)----------------
代理的类是:cn.qlq.service.UserServiceImpl
传入的参数是:[111]
增强的方法名字是:save
参数类型是:[Ljava.lang.Class;@54b2a02f
--------------方法开始执行(注解)------------------
---------------前置通知开始(注解)~~~~~~~~~~~
代理的类是:cn.qlq.service.UserServiceImpl
增强的方法是:save
传入的参数是:[111]
找到这个方法
---------------前置通知结束(注解)~~~~~~~~~~~
这是最终通知(发生异常也会在最终调用)........(注解)
这是异常通知(发生异常后调用)~~~~~~~~~~~(注解)

 

 

总结:

1.注解解释

  @Aspect:指明当前类是通知类

  @Before:前置通知

  @After:最终通知,发生异常也会调用,方法执行完之后执行

  @AfterReturning:后置通知(异常发生不会调用),方法成功执行之后。

  @Around:环绕通知

  @AfterThrowing:异常通知,抛出异常之后

  @Pointcut:抽取切点表达式,便于修改切点表达式

2.通知执行顺序(顺序和xml方式有点不一样)

  没异常:(不会执行异常通知)

     环绕通知之前部分->前置通知->环绕通知之后部分->最终通知->后置通知  

  有异常(不会执行后置通知和环绕通知后半部分)

    环绕通知之前部分->前置通知->最终通知->异常通知 

 

3.注解AOP几种切点表达式选择的使用:

1.within("xxx")表达式

    // 匹配UserServiceImpl下面的所有方法
    @Pointcut("within(cn.qlq.service.UserServiceImpl)")
    public void pc() {
    }

 

 

    // 匹配cn.qlq包及其子包下面所有类的所有方法
    @Pointcut("within(cn.qlq..*)")
    public void pc() {
    }

 

2.对象匹配:

  • 1.this

    // 匹配AOP对象的目标对象为指定类型的方法,即cn.qlq.service.UserService的AOP代理对象的方法
    @Pointcut("target(cn.qlq.service.UserService)")
    public void pc() {
    }

 

 

 

 

  • 2.target

    // 匹配实现UserService的目标对象,而不是代理对象,这里就是UserService的方法
    @Pointcut("target(cn.qlq.service.UserService)")
    public void pc() {
    }

 

 

  • 3.bean

    // 匹配注入到spring中所有以Service结尾的类的方法
    @Pointcut("bean(*Service)")
    public void pc() {
    }

 

 

3.参数匹配

    // 匹配所有以方法名save开头且只有一个String类型参数的方法
    @Pointcut("execution(* *..save*(String))")
    public void pc() {
    }

 

 

    // 匹配所有只有一个String类型参数的方法
    @Pointcut("args(String)")
    public void pc() {
    }

 

 

    // 匹配所有以方法名save开头且第一个参数类型为String类型的方法
    @Pointcut("args(* *..save*(String,..))")
    public void pc() {
    }

 

 

    // 匹配第一个参数类型为String类型的方法
    @Pointcut("args(String,..)")
    public void pc() {
    }

 

 

4.注解匹配:

(1)自定义注解:

package cn.qlq.annotationAOP;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
以上是关于SpringAOP深入学习的主要内容,如果未能解决你的问题,请参考以下文章

SpringAOP+注解实现简单的日志管理

cglib源码学习交流

Spring AOP深入理解之拦截器调用

博客笔记索引

一步一步学习springaop概述

Spring学习(二十五)Spring AOP之增强介绍