Spring — AOP

Posted 朱呀朱-

tags:

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

Spring — AOP

AOP 简介

  • 面向切面的编程,是 OOP 的扩展与补充,可以对业务逻辑的各部分进行隔离,降低各部分之间的耦合度,提高程序的可重用性,提高开发效率。

  • 在不修改源码的情况下,对业务功能进行增强。AOP 适用于具有横切逻辑的场合,如日志记录、性能检测、访问控制、事务控制等

  • 常用来做增强测试:

    Date d = new Date(); // 获取当前日期
    SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss:SSSS”);
    sdf.format(d);  // 格式化过日期结构的日期显示
    // 做控制台输出在所需地方,就可以充当日志作用————横切逻辑
    
    • 就像是在进超市时,核心业务是走进超市,由 AOP 来执行开门和关门操作

AOP 相关概念

  • 连接点,即被 [ 拦截 ] 到的点,业务层接口中的所有方法都是连接点

    • 类里面的方法其实可以说都是连接点
  • 织入,悄无声息的,在影响较小的情况下加入功能

    • 侵略性不强
  • 目标对象,没有被增强的那些对象,接口 / 实现类

  • 切入点,即被 [ 拦截并增强 ] 的连接点

  • 通知,又称增强,即指拦截到切入点后所做的事情

  • 切面,即切入点和通知的结合

  • 代理,即一个类被 AOP 织入通知后产生的新类

    • 代理对象就是通过后面讲的三种方法实现了增强的接收的对象,可以是接口 / 实现类

    • 代理类和实际业务类:应该实现相同的功能,这是代理机制实现的根本

    • 代理机制的实现方式:接口方式 + 继承方式

代理

静态代理 ( StaticProxy )

  • 业务接口与业务实现类 ( 实现方法 )

  • 定义增强类并编写增强方法

  • 静态代理类,包含业务类和增强类,是手动对所有方法进行增强 ( 直接在现有方法里编写 )

    public class StaticMathProxy implements MathInter // 继承接口
        MathInter mathInter; //业务类
    	TimeTool timeTool;   //增强类
    	public StaticMathProxy(MathInter mi, TimeTool tt)
    		this.mathInter = mi;  // 用外面传进来的MathInter来完成方法,而不是所在类自己写,方法就return mathInter.xxxx就可(如下面的“方法里:”),即代理来做,工具timeTool也可以进行代理
    		this.timeTool = tt;				
            
    	// ... 方法里:
        timeTool.before();
    	int r = mathInter.add(i, j);
    	timeTool.after();
        //...
            
        
    
    
    • 在上述代理类中做方法的增强 ( timeTool 操作 ),这样源码:接口、实现类、工具类三个都没改,改的话就是改代理
  • 测试 Test

    MathInter mi = new MathInterImpl();//业务类
    TimeTool tt = new TimeTool();      //增强类
    StaticMathProxy proxy = new StaticMathProxy(mi,tt);//代理类
    

动态代理 ( Proxy )

基于 [ 接口 ] 的动态代理,即 jdk 动态代理

过程
  • 同静态定义业务接口与业务实现类

  • 同静态定义增强类及增强方法

  • 定义 JDK 动态代理类 ( 包含了业务类 + 增强类 ) — implements InvocationHandler

    // 注:接口导入的是java.lang.reflect,实现invoke()
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable 
        Object o;
        timeTool.before();  // 在所有的方法执行前进行增强(before就是工具类的方法)
        o = method.invoke(mathInter, args);  // 这样就对所有的方法都集中进行了执行前后的增强操作,使用静态方法的话,要在每个方法前都调用增强方法(方法多了会很麻烦)
        timeTool.after();
        return o;
    
    
    // 如果想对个别方法进行增强操作:
            If(method.getName().equals(“sub”))   // 就只对sub方法进行增强了
                a.before();
                o = method.invoke(mathInter, args); 
                a.after();
            
    
  • Test 测试

    MathInter mi = new MathInterImpl();//业务类
    TimeTool tt = new TimeTool();      //增强类	
    JdkMathProxy mathProxy = new JdkMathProxy(mi,tt);//代理类,作为Proxy.newProxyInstance的第3个参数
    
    MathInter mi2 = (MathInter)Proxy.newProxyInstance(  // 强转成:接口 (实现类不可以, JDK Proxy工作原理如此,即:不能用接口的实现类来转换Proxy的实现类,它们是同级,应该用共同的接口来转换 —— 可以理解为狗不能强转为猫,但都可以强转成动物——向上转型)
        mi.getClass().getClassLoader(),  // 传入三个参数
        mi.getClass().getInterfaces(),  // 前两个参数都是固定的
        mathProxy);
    
    // mi.add(1, 2);  //业务对象执行方法,方法未增强
    mi2..add(1, 2);//使用Proxy+JdkMathProxy创建的代理对象执行方法,方法已增强
    
补充
  • 使用 java 的 [ Proxy ] 类,要求被代理类至少实现一个 [ 接口 ]

  • 提供者:JDK

  • 涉及的类:Proxy

  • 要点:实现 InvocationHandler 接口

  • 创建代理对象的方法:Proxy.newProxyInstance,方法参数:

    • 参数1:ClassLoader,类加载器,和被代理对象使用相同的 [ 类加载器 ],固定写法 —— 似先前说的类模板
    • 参数2:Class[],字节码数组,和被代理对象具有相同的 [ 行为 ]、实现相同的 [ 接口 ],固定写法
    • 参数3:InvocationHandler,接口,代理的具体内容即 [ 增强代码 ],使用匿名内部类实现

基于 [ 子类 ] 的动态代理,即 cglib 动态代理

过程
  • 编写业务实现类不继承接口

  • 定义增强类及增强方

  • 代理类的继承的接口改变了:implements MethodInterceptor

    // 注:接口导入的是net.cglib.proxy,实现intercept()
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable  // 接口必须实现的方法
            Object o1 = null;
            rt.before();  //前置通知
            try
    //            环绕开始
                o1 = methodProxy.invoke(mi, objects);
    //            环绕结束
            catch(Exception e)
    //            异常
            
            finally 
    //            后置返回
            
            rt.after();   //后置通知
            return o1;
    
    
  • Test 测试

    remathimpl_2 mi = new remathimpl_2();
    reTool_3 rt = new reTool_3();
    CglibMathProxy_4 proxy = new CglibMathProxy_4(mi, rt); //代理类,作为Enhancer的setCallback()的参数
    
    //        Enhancer:cglib里一个重要的类
    Enhancer e = new Enhancer();       //定义Enhancer对象,cglib里面带的第三方的类,e是Enhancer的实例——其实感觉就是代理
    e.setSuperclass(mi.getClass());    //设置Enhancer对象的父类
    e.setCallback(proxy);              //设置Enhancer对象的回调/如何处理方法(用的proxy)
    
    //e.create返回的是一个Object(ctrl查看),所以需要强转成自己所需要的类
    remathimpl_2 mi2 = (remathimpl_2)e.create();    //e来创建代理mi2
    
    // mi.add(1,2);                 //业务对象执行方法,方法未增强
    mi2.add(1,2);                //使用Enhancer+CglibMathProxy创建的代理对象执行方法,方法已增强
    

Spring 使用 ProxyFactoryBean 创建 AOP 代理

  • ProxyFactoryBean就像是先前方法中的Enhancer、Proxy —— 创建代理
  • 需要导入一个依赖 —— aspectjweaver
    • 若缺少 aspectjweaver,则测试类 new AspectJExpressionPointcut() 编译不报错,运行报错

原始编码方式

过程
  • 业务接口与业务实现类 ( 实现方法 )

  • 定义通知类

    // 注:接口导入的是org.aop.......实现invoke()
    //  实现环绕通知
    public class MyAdvice_3 implements MethodInterceptor  // 通知
    
        public Object invoke(MethodInvocation methodInvocation) throws Throwable 
            Object o;
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSSS");
            Date d1 = new Date();
            System.out.println("at:" + sdf.format(d1) + ", 开始执行"); // 工具
    
            o = methodInvocation.proceed(); // 代理
            
            Date d2 = new Date();
            System.out.println("at:" + sdf.format(d1) + ", 执行结束");
            return 0;
         // 就像是综合了工具类和动态代理类,做增强
    
    
  • 定义切面 = 切入点 + 通知

    // 注意:继承了 PointcutAdvisor 接口,实现 isPerInstance()
    public class FilterAdvice_4 implements PointcutAdvisor 
    
        Pointcut pointcut;  //切入点
        Advice advice;   //通知
    
        public FilterAdvice_4(Pointcut pointcut, Advice advice)
            this.pointcut = pointcut;
            this.advice = advice;
        
    
        public Pointcut getPointcut() 
            return this.pointcut;
        
        public Advice getAdvice() 
            return this.advice;
        
    
        public boolean isPerInstance() 
            return false;
        
    
    
  • Test 测试

    public class Test_Aop 
        public static void main(String[] args) 
    //        1.定义被代理对象
            Mathimpl_2 mi = new Mathimpl_2();
    //        2.定义通知(定义增强类)
            Advice advice = new MyAdvice_3();
    //        3.定义创建被代理对象mi 的代理的对象pfb(用pfb创建mi的代理对象m2)
            ProxyFactoryBean pfb = new ProxyFactoryBean();
    //        设置接口
            pfb.setInterfaces(mi.getClass().getInterfaces());
    //        设置要代理的对象
            pfb.setTarget(mi);
    //        因为mi 的 Mathimpl_2可能实现了接口也可能没有,所以:
    //        如果使用jdk基于接口实现动态代理则采用默认(不用写),如果使用cglib则需要设置参数为true
            pfb.setProxyTargetClass(true);
    //        4.定义一个切入点
            AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    //        切入点匹配一个方法:写入类方法的全包名,注意:方法后的括号里面是两个点!!
            pointcut.setExpression("execution (public int com.qut.aop_4.Mathimpl_2.add(..))");
    //        5.定义切面,关联切入点和通知
            FilterAdvice_4 advisor = new FilterAdvice_4(pointcut, advice);
    //        6.切面加入到pfb中
            pfb.addAdvisor(advisor);
    //        7.创建代理对象,强转
            Mathimpl_2 mi2 = (Mathimpl_2)pfb.getObject();
            System.out.println(mi2.add(3, 2));
        
    
    

Spring 式 —— 配置文件

  • 业务类和实现类

  • 增强类里定义增强方法

  • 注解方式:需要 xml 里加上 aop 的 xmlns 和 xsi

    注意:需要导入一个依赖 —— aspectjweaver

    <!--  1.定义被代理对象  -->
        <bean id="Mathimpl" class="com.qut.aop_bean_5.Mathimpl_1"></bean>
        <!--  2.定义增强(配置文件的话不能使Advice,只能用Tool)  -->
        <bean id="reTool" class="com.qut.aop_bean_5.reTool_2"></bean>
        <aop:config>
            <!--  3.定义切入点(明确指出对谁做增强)  -->
            <aop:pointcut id="addPointCut" expression="execution(public int com.qut.*.Mathimpl_1.add(..))"/>
            <!--  4.定义切面(切点 + 通知)  -->
            <aop:aspect ref="reTool">
                <!--  4.1 配置切面的before方法为切入点的前置通知  -->
                <aop:before method="before" pointcut-ref="addPointCut"></aop:before>
                <!--  4.2 配置切面的after方法为切入点的后置通知  -->
                <aop:after method="after" pointcut-ref="addPointCut"></aop:after>
            </aop:aspect>
    </aop:config>
    
  • Test 测试

    public class Test_AopBean 
        public static void main(String[] args) 
    // 读配置文件,取bean,代理类执行方法
            ApplicationContext ctx = new ClassPathXmlApplicationContext("3.xml");
            Mathimpl_1 mi = (Mathimpl_1)ctx.getBean("Mathimpl");
            System.out.println(mi.add(3, 2));
        
    
    
  • 切入点表达式写法

    • execution (public int com.qst.pkg4AopXML.A.sub(..))
    • 全匹配方式:public void com.qst.bean.User.add()
    • 省略访问修饰符:void com.qst.bean.User.add()
    • 星号表示任意返回类型,不能省:* com.qst.bean.User.add()
    • 星号表示任意包,有几级包写几个* :* * . * . * .User.add()*
    • 星号表示任意类,不能省:* * . * . * . *.add()
    • 星号表示任意方法,不能省:* * . * . * . * . *()
    • .. 表示参数列表中任意参数:* * . * . * . * . *(..)
    • 注意:星号不能跨包,只能是一层一个

Spring 式 —— Aspect 注解

  • 业务接口和业务类 / 实现类

  • 注解业务类 @Component + 配置文件扫描 + 导入依赖 aspectjweaver

    // 实现类前
    @Component(value = "xxx")
    
    // xml中
    <!-- 1、配置包自动扫描 -->
    <context:component-scan base-package="com.qst"></context:component-scan>
    <!-- 2、配置自动生成aop代理 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • 定义增强类 / 通知类

  • 注解增强类 @Component + 切面 @Aspect

    • 对增强类的方法做注解
    @Component
    @Aspect
    public class TimeTool 	
    	@Before(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public void before()  // 只要@注解对,方法名都随意
    		System.out.println("前置通知");
    	
    	@After(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public void after()
    		System.out.println("后置(最终)通知");
    	
    	@AfterReturning(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public void afterReturning()  
    		System.out.println("返回通知"); 
    	 
    	@AfterThrowing(value = "execution(public int com.qst.*.MathInterImpl.sub(..))")  
    	public void afterThrowing()  
    		System.out.println("异常通知"); 
    	 
    	@Around(value = "execution(public int com.qst.*.MathInterImpl.sub(..))") 
    	public Object arounds(ProceedingJoinPoint jp) throws Throwable 
            System.out.println("环绕之前");
            Object obj=jp.proceed();
            System.out.println("环绕之后");
            return obj;
        	
     // ———— 即把xml中的一大堆改成了此样式
    
  • Test 测试 ( 1 读配置文件,2 取 bean,3 代理类执行方法 )

    ApplicationContext ctx = new ClassPathXmlApplicationContext("4.xml");
    //        remath_1 mi = (remath_1) ctx.getBean("xxx");
    remath_1 mi = ctx.getBean("xxx", Mathimpl_2.class);
    System.out.println(mi.add(2,3));
    
  • 采用此方式,就可以在其余地方编写增强 / 通知类(带上 @ 的),然后直接拷贝到项目中,项目就会自行扫描并执行 ——— 典型的 AOP 应用 —— 在外部写增强并添加,不加就不增强

    • 感觉就像是:传统方式 — 改动源码 < 配置文件 — 差点灵活性 < 注解方式 — 更便利
  • 由于上述方法的切入点表达式稍复杂,所以做了优化 ( 了解 ):

    // 类中:
        @Pointcut(“execution 切入点表达式”) // 切入点表达式就只写一次就够了
        Public void 空方法名subPointCut()  空 
    
        @Before(value = “subPointCut()”)
        F1方法。。。。。
        @After(value = “subPointCut()”)
        F2方法。。。。。
    

代码

  • 重:最后的 Spring 使用 ProxyFactoryBean 创建 AOP 代理中的 Aspect 注解方式
  • 动态代理 ( Proxy ) —— 基于 [ 接口 ] 的动态代理,即 jdk 动态代理

以上是关于Spring — AOP的主要内容,如果未能解决你的问题,请参考以下文章

spring aop能监听到修改之前的值吗

spring的aop怎么获取切点参数

spring aop 问题,不能进入切点

spring aop配置出错 帮忙检查

Spring中的AOP

Spring中的AOP