• SpringAOP(面向切面编程)的使用和原理



    使用一个代理对象,执行目标方法,同时代理对象会执行一些其他方法(通用操作)

    直观上的感受就是,在执行原方法的同时,在原方法的前后切入了另一些方法

    Spring AOP基于动态代理,如果代理对象有接口,使用jdk动态代理;如果没有,会使用cglib

    那AOP有什么好处呢?它将一些类似日志操作等大量在项目中重复的代码独立出来,降低模块间的耦合度,有利于未来的可拓展性和可维护性

    静态代理与动态代理

    静态代理:每一个方法都需要写一个代理方法,可以通过Impl或者子类实现

    动态代理:所有方法都可以公用一个代理方法,通过java给定的类实现

    比如,现在我有一个User接口,有一个UserImpl的实现

    public interface User {
        public void name(String str);
    }
    
    public class UserImpl implements User {
    
        @Override
        public void name(String str) {
            System.out.println(str);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    现在我想在每一个name方法调用后执行一条语句

    如果是静态代理的方式,需要使用类似装饰器模式的操作,使用一个类来继承它

    public class StaticProxyUser extends UserImpl {
    
        @Override
        public void name(String str) {
            super.name(str);
            System.out.println("代理语句执行");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    像这么写的话代码的可重用性不高,如果其他接口也像使用代理每个接口都需要写这样一段代码

    JDK动态代理

    被代理的类实现了接口的时候才能使用,生成的代理类是实现了相同接口的同级代理类

    通过Proxy的newProxyInstance生成代理类,这个代理类的方法都会视为代理增强后的方法

    class invocation implements InvocationHandler{
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //重写这个方法,然后把这个类传入newProxyInstance的第三个参数中
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    以下是代理类

    public class JDKProxyUser implements InvocationHandler {
    
        User user;
    
        public JDKProxyUser(User user) {
            this.user = user;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            method.invoke(user, args);
            System.out.println("代理语句执行");
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    主类创建

        @Test
        void proxyTest() {
            User user = new UserImpl();
            Class[] classes = {User.class};
            User user1 = (User) Proxy.newProxyInstance(User.class.getClassLoader(), classes, new JDKProxyUser(user));
            user1.name("yifanxie");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    可以看见这时候的user可以传入任何实现类对象,不必再一个个去写子类实现了,这样程序的可扩展性就大大增加了

    调用这个代理类的方法其实是调用了(事实上我们也重写了)InvocationHandler的invoke方法(Invocation:求助)

    CGlib动态代理

    被代理的类是代理类的父类,如果被代理的类有一些属性或方法被final定义,是不一定能成功代理的

    通过Proxy被手动重写的子类产生代理类,这个代理类的方法都会视为代理增强后的方法

    调用这个代理类的方法其实是调用了MethodInterceptor的intercept(拦截)方法

    相关名词

    增强(advice,也叫通知):对原方法额外进行的操作,一共有5种类型

    • 前置通知:目标对象的方法调用之前触发
    • 后置通知:目标对象的方法返回结果之后触发
    • 最终通知:无论目标对象的方法触发了异常通知还是后置通知,都会触发最终通知
    • 异常通知:目标对象的方法运行中抛出 / 触发异常后触发。异常通知和后置通知两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值
    • 环绕通知:编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

    连接点:可以被增强(进行AOP操作)的方法叫做连接点,几乎所有的方法都可以被称为连接点

    切入点:实际上被增强的方法

    切面:这不是一个名词,这是一个动词,使用AOP增强切入点的这样一个操作叫切面

    目标:被通知的对象

    AspectJ

    Spring AOP现在已经集成了AspectJ,AspectJ算的上是Spring生态系统中最完整的AOP框架

    AspectJ代理不同与Spring代理,Spring AOP属于运行时增强(基于java提供的类在运行时实现),而AspectJ是编译时增强(基于字节码操作在生成class文件时就进行改变),因此AspectJ在处理大量请求时性能上比SpringAOP好很多,因为在运行时不用读取二进制代理文件

    同时现在大多企业都使用AspectJ(那学习SpringAOP底层有什么用吗?别问,我也不知道)

    切入点表达式

    格式如下:

    execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))

    说明:

    • 这里的execution后跟着的就是切入点
    • 权限修饰符就是切入点的权限修饰符,可以省略
    • 返回值类型可以指定类型,也可以用*代替,表示所有的返回值
    • 包名可以写两个点,表示当前包里所有的类或者子包下的类。比如 com…aop;包名可以使用*,表示当前包下所有东西
    • 类名、方法名可以用*代替,表示所有的类
    • 参数类型可以指定类型。比如: String,Integer 表示第一个参数是String,第二个参数是Integer;* 表示任意类型;可以使用 … 表示任意个数、任意类型的参数

    eg:

    execution(* com.example.demo.UserImpl.*(..))
    
    • 1

    例子以及注解说明

    @Aspect:表示这是一个增强类
    @Before:前置通知,后面value跟着的是切入点表达式
    @After:最终通知
    @AfterReturning:后置通知
    @AfterThrowing:异常通知
    @Around:环绕通知,其中proceedingJoinPoint.proceed()语句代表执行被增强的方法,这就是为什么说它可以直接拿到目标对象以及要执行的方法
    @Component:原件的意思,把该类实例化放入到spring容器中

    @Component
    @Aspect
    public class ProxyUser {
    
        @Before(value = "execution(* com.example.demo.UserImpl.name(..))")
        public void before() {
            System.out.println("before......");
        }
    
        @After(value = "execution(* com.example.demo.UserImpl.name(..))")
        public void after() {
            System.out.println("after......");
        }
    
        @AfterReturning(value = "execution(* com.example.demo.UserImpl.name(..))")
        public void afterReturning() {
            System.out.println("afterReturning......");
        }
    
        @AfterThrowing(value = "execution(* com.example.demo.UserImpl.name(..))")
        public void afterThrowing() {
            System.out.println("AfterThrowing......");
        }
    
        @Around(value = "execution(* com.example.demo.UserImpl.name(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("Around before......");
            proceedingJoinPoint.proceed();
            System.out.println("Around after......");
        }
    }
    
    • 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

    PointCut

    我们发现上面的5种增强里的路径都是一样的,在修改路径时太麻烦了,有没有可以将这些路径抽取出来的方法?

    使用PointCut注解做公共切入点抽取

        @Pointcut(value = "execution(* com.example.demo.UserImpl.name(..))")
        private void pointCut() {}
    
        @Before(value = "pointCut()")
        public void before() {
            System.out.println("before......");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    优先级

    每个增强类最多有5种增强,而一个切入点可能被很多个增强类增强,我们现在想控制多个切面的执行顺序,怎么办?

    在增强类上使用Order注解,Order之中的数字越小,说明优先级越高,也就越先执行

    @Order(1)
    @Service
    @Aspect
    public class ProxyUser {...}
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    【关于block块的内部访问外部变量的问题 Objective-C语言】
    看懂这篇文章-你就懂了数据库死锁产生的场景和解决方法
    Python Selenium 基本使用(详细步骤)
    python-pdf的合并与拆分
    本科行政管理毕业论文什么题目好写点?
    React18源码: Fiber树的初次创建过程图文详解
    Linux系列之链接
    在常州“超级虚拟工厂”,中国智造正在“原力觉醒”
    基于stm32单片机BMP180气压计海拔高度温度测量Proteus仿真
    SQL数据迁移实战:从产品层级信息到AB测试表
  • 原文地址:https://blog.csdn.net/sekever/article/details/126099202