• Spring之AOP详解


    1. AOP概念

    1. 面向切面编程,利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
    2. 通俗描述:不通过修改源代码方式,在主干功能里面添加新功能

    以登录功能为例:
    在这里插入图片描述


    2. AOP底层原理

    1. JDK动态代理—有接口情况

    使用 JDK 动态代理,使用 Proxy 类里面的方法创建代理对象
    在这里插入图片描述

    创建接口,定义方法:

    package cn.edu.xd.dao;
    
    public interface UserDao {
        public int add(int a,int b);
        public void printMsg(String msg);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    创建接口实现类,实现接口方法:

    package cn.edu.xd.dao;
    
    public class UserDaoImpl implements UserDao{
        @Override
        public int add(int a, int b) {
            System.out.println("add()方法执行...");
            return a+b;
        }
    
        @Override
        public void printMsg(String msg) {
            System.out.println("update()方法执行...");
            System.out.println("msg:"+msg);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    使用 Proxy 类创建接口代理对象:

    package cn.edu.xd.proxy;
    
    import cn.edu.xd.dao.UserDao;
    import cn.edu.xd.dao.UserDaoImpl;
    import cn.edu.xd.dao.UserDaoProxy;
    
    import java.lang.reflect.Proxy;
    
    public class JDKProxy {
        public static void main(String[] args) {
            Class[] interfaces={UserDao.class};//要实现的接口代理对象 即增强UserDao接口中的方法
            UserDaoImpl userDaoImpl=new UserDaoImpl();
            UserDao userDao= (UserDao) Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDaoImpl));
            int result=userDao.add(1,2);
            System.out.println("result:"+result);
            userDao.printMsg("hello");
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    package cn.edu.xd.dao;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class UserDaoProxy implements InvocationHandler {
        private Object object;
    
        //为谁进行代理就把谁作为参数传递过来(有参构造函数传递)  比如这里给userDaoImpl代理 传入的参数就是userDaoImpl对象
        public UserDaoProxy(Object object) {
            this.object = object;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("方法执行之前...."+method.getName()+" 参数:"+ Arrays.toString(args));
            Object result=method.invoke(object,args);
            System.out.println("方法执行之后....");
            return result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    2. CGLIB动态代理—无接口情况

    需要导入两个包:
    在这里插入图片描述

    被代理类:

    package cn.edu.xd.dao;
    
    public class UserDaoImpl{
    
        public int add(int a, int b) {
            System.out.println("add()方法执行...");
            return a+b;
        }
    
        public void printMsg(String msg) {
            System.out.println("update()方法执行...");
            System.out.println("msg:"+msg);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    代理类:

    package cn.edu.xd.dao;
    
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    public class UserDaoImplInterceptor implements MethodInterceptor {
    
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
           System.out.println("方法执行之前...."+method.getName()+" 参数:"+ Arrays.toString(objects));
            Object result=methodProxy.invokeSuper(o,objects);
            System.out.println("方法执行之后....");
            return result;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    使用:

    package cn.edu.xd.proxy;
    
    import cn.edu.xd.dao.UserDaoImpl;
    import cn.edu.xd.dao.UserDaoImplInterceptor;
    import net.sf.cglib.proxy.Enhancer;
    
    public class CglibProxy {
        public static void main(String[] args) {
            UserDaoImpl userDao= (UserDaoImpl) Enhancer.create(UserDaoImpl.class,new UserDaoImplInterceptor());
            int result=userDao.add(1,2);
            System.out.println("result:"+result);
            userDao.printMsg("hello");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述


    3. AOP术语

    1. 连接点
      类里面哪些方法可以被增强,这些可以被增强的方法称为连接点

    2. 切入点
      实际被真正增强的方法称为切入点

    3. 通知
      实际增强的逻辑部分称为通知
      通知分为5种:前置通知,后置通知,环绕通知,异常通知,最终通知

    4. 切面
      是一个动作,把通知应用到切入点的过程

    5. 切入点表达式
      确定哪个切入点,切入点表达式作用:知道对哪个类里面的哪个方法进行增强
      语法结构: execution([权限修饰符] [返回类型] [类全路径] [[方法名称]([参数列表])
      权限修饰符可以省略,其他的不能省略

    eg1: 对 cn.edu.xd.dao.UserDao 类里面的 add 进行增强(…表示参数列表)
    execution(* cn.edu.xd.dao.UserDao.add(..))

    eg2: 对 cn.edu.xd.dao.UserDao类里面的所有的方法进行增强
    execution(* cn.edu.xd.dao.UserDao.*(..))

    eg3:对 cn.edu.xd.dao包里面所有类,类里面所有方法进行增强
    execution(* cn.edu.xd.dao.*.*(..))


    4. 基于注解的AOP使用

    导入依赖包:
    在这里插入图片描述

    bean.xml配置:
    在这里插入图片描述

    创建被代理类(需要被增强的类),加上注解@Component

    package cn.edu.xd.bean;
    
    import org.springframework.stereotype.Component;
    
    @Component
    public class User {
        public void add(){
            System.out.println("add()...");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建代理类(增强类),加上注解@Component和@Aspect

    package cn.edu.xd.proxy;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class UserProxy {
        //前置通知
        @Before(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void before(){
            System.out.println("before()...");
        }
        //最终通知 即使出现异常也会执行
        @After(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void after(){
            System.out.println("after()...");
        }
        //后置通知(返回通知) 出现异常不会执行
        @AfterReturning(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void afterReturning(){
            System.out.println("afterReturning()...");
        }
        //异常通知  出现异常时会执行
        @AfterThrowing(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void afterThrowing(){
            System.out.println("afterThrowing()...");
        }
        //环绕通知
        @Around(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void around(ProceedingJoinPoint point) throws Throwable {
    
            System.out.println("环绕之前");
            point.proceed();
            System.out.println("环绕之后");
        }
    }
    
    
    • 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

    在这里插入图片描述

    为了使异常通知出现,修改代码如下:

     public void add(){
            int a=10/0;//设置除零错误
            System.out.println("add()...");
        }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    相同的切入点抽取
    上面代码中的value = "execution(* cn.edu.xd.bean.User.add(..))"出现了多次,可以进行如下提取:

     @Pointcut(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void pointCommon(){
    
        }
        //前置通知
        @Before(value = "pointCommon()")
        public void before(){
            System.out.println("before()...");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    设置多个增强类的增强顺序

    与前面的增强类UserProxy类似,现在又加入一个增强类UserProxy2,代码如下:

    package cn.edu.xd.proxy;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    @Order(1)
    public class UserProxy {
        @Pointcut(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void pointCommon(){
    
        }
        //前置通知
        @Before(value = "pointCommon()")
        public void before(){
            System.out.println("before()...");
        }
        //最终通知 即使出现异常也会执行
        @After(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void after(){
            System.out.println("after()...");
        }
        //后置通知(返回通知) 出现异常不会执行
        @AfterReturning(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void afterReturning(){
            System.out.println("afterReturning()...");
        }
        //异常通知  出现异常时会执行
        @AfterThrowing(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void afterThrowing(){
            System.out.println("afterThrowing()...");
        }
        //环绕通知
        @Around(value = "execution(* cn.edu.xd.bean.User.add(..))")
        public void around(ProceedingJoinPoint point) throws Throwable {
    
            System.out.println("环绕之前");
            point.proceed();
            System.out.println("环绕之后");
        }
    }
    
    
    • 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

    可以使用注解@Order(数字)的方式指定增强顺序,数字从0开始,数字越小,优先级越高

    完全注解开发

    package cn.edu.xd.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration //表示是配置类 相当于bean.xml
    @ComponentScan(basePackages = {"cn.edu.xd"})  //表示对哪些包进行扫描
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    public class SpringConfig {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
     @Test
        public void test1(){
            ApplicationContext context
                    = new AnnotationConfigApplicationContext(SpringConfig.class);
            User user = context.getBean("user", User.class);
            user.add();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    5. 基于xml方式的AOP使用

    1. 创建增强类(UserProxy)和被增强类(User)
    2. 在bean.xml中给这两个类创建对象
     <bean id="user" class="cn.edu.xd.bean.User"/>
        <bean id="userProxy" class="cn.edu.xd.proxy.UserProxy"/>
    
    • 1
    • 2
    1. 在 spring 配置文件中配置切入点
    <aop:config>
            <aop:pointcut id="p" expression="execution(* cn.edu.xd.bean.User.add(..))"/>
            <aop:aspect ref="userProxy">
                <aop:before method="before" pointcut-ref="p"/>
                <aop:after method="after" pointcut-ref="p"/>
                <aop:after-returning method="afterReturning" pointcut-ref="p"/>
                <aop:around method="around" pointcut-ref="p"/>
                <aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
            </aop:aspect>
        </aop:config>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    【Linux】比Xshell更爽,用vscode连接云服务器进行开发
    什么是内存泄漏?JavaScript 垃圾回收机制原理及方式有哪些?哪些操作会造成内存泄漏?
    调试学习wasm,emscripten的各种用法 js 与 C++ 的交互
    华为云云耀云服务器L实例评测|认识redis未授权访问漏洞 & 漏洞的部分复现 & 设置连接密码 & redis其他命令学习
    vue中实现千位分隔符
    Linux【shell】 shell编程
    鲲鹏开发者创享日2022:鲲鹏全栈创新 与开发者共建数字湖南
    JVM 全面深入
    利用python数据分析——Numpy基础:通用函数、利用数组进行数据处理
    @Autowired注解推荐使用方法:用在构造方法上
  • 原文地址:https://blog.csdn.net/qq_43478694/article/details/125464534