• Spring AOP的解读


    一、为什么会有AOP

    在日常的开发中经常会有这样一种场景,支付相关的业务中经常需要记录日志,而记录的日志大体相同;这样就会造成支付组件和日志组件强关联,耦合在一起了。而AOP的出现就是为了解决这种问题的。

    二、什么是AOP

    AOP里面有这样几个概念:目标对象、切点、切面支持类、切面、通知。AOP为开发者提供一种横切关注点(如日志组件横切了支付组件)分离并织入的机制,把横切关注点分离,然后通过某种技术织入到系统中,从而解耦了相关组件。

    三、认识AOP

     使用AOP功能需要的jar包如下:

    1、接口类:

    public interface IHelloWorldService
    {
        public boolean sayHello();
    }

    2、接口实现类:

    public class HelloWorldService implements IHelloWorldService
    {
        public boolean sayHello()
        {
            System.out.println("=======Hello World:");    
            return true;
        }
    }

    3、切面支持类:该类就是通知的具体实现,切面是切点+通知的组合。

    public class HelloWorldAspect
    {
        public void beforeAdvice()
        {
            System.out.println("=======before advice:");
        }
        
        public void afterFinalAdvice()
        {
            System.out.println("========after advice" );
        }
    }
    HelloWorldAspect并不是真正的切面实现,而是定义通知实现的类,我们可以将它看做是缺少了切入点的切面。

    4、Spring配置文件

    <bean id="helloWorldService" class="aop.HelloWorldService"/>
       
       <bean id="aspect" class="aop.HelloWorldAspect"/>
       <aop:config>
       <aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
               <aop:aspect ref="aspect">
                   <aop:before pointcut-ref="pointcut" method="beforeAdvice()"/>
                   <aop:after pointcut="execution(* aop..*.*(..))" method="afterFinalAdvice"/>
               </aop:aspect>
       </aop:config>

    1、<aop:config>:AOP相关的功能都在该标签下面

    2、<aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>

    切点的定义,这种定义的方式可以让多个切面通过id的形式重复引用,expression表达式表示的是匹配aop包(包含子包)下任何类的任何方法。

    3、<aop:aspect ref="aspect">

    标签<aop:aspect>用来定义切面,其中的ref用来引用切面支持类的方法。

    4、<aop:before pointcut-ref="pointcut" method="beforeAdvice()"/>

    这里定义的是前置通知,包含切点和通知里面的具体方法。其中定义切点有两种方式:pointcut-ref="pointcut"或者pointcut="execution(* aop..*.*(..))",两者只能选择其一。而method用来引用切面通知实现类中的方法。

    5、测试类:

    public class AopTest
    {
        public static void main(String[] args) throws Exception
        {
            ApplicationContext ctx =
                new ClassPathXmlApplicationContext("applicationContext.xml");
            IHelloWorldService bean =
               ctx.getBean("helloWorldService",IHelloWorldService.class);
            bean.sayHello();
        }
    }

    6、执行结果:

    =======before advice:
    =======Hello World:
    ========after advice

    从结果可以看出来,尽管DEMO很简单,但是实现了关联组件的解耦。可以通过定义切面的方式,让目标方法在执行前后,执行我们的通知方法。

    四、Spring的通知类型

    在前面的实例中,用到了前置通知和后置最终通知两种类型。在现实情况中,组件组合的方式不仅要有很多种,而且还要很灵活。这就要求Spring对切面有不同形式的定义,所以Spring给我们带来了如下几种通知:

    1、前置通知:在切入点选择的连接点处的方法执行之前的通知,该通知不影响正常程序的执行流程。

    2、后置最终通知:在切入点选择的连接点处的方法返回时执行的通知,不管业务方法是否抛出异常,都会调用该通知,相当于finally块。

    3、后置返回通知:在切入点选择的连接点处的方法正常执行完毕时执行的通知(连接点处方法执行完毕后没有抛出任何异常才会去执行该通知)

    4、后置异常通知:在切入点选择的连接点处的方法抛出异常返回时执行的通知。(连接点处的方法抛异常才会执行该通知)

    5、环绕通知:环绕着在切入点选择的连接点处的方法所执行的通知,环绕通知可以在方法调用之前和之后自定义任何行为,并且可以决定是否执行连接点处的方法、替换返回值、抛出异常等等。

    我们现在看来下这几种通知的具体实现:

    1)前置通知

    这次演示前置通知如何传递参数:

    目标类:

    public class HelloWorldService implements IHelloWorldService
    {
        public boolean sayHello(String str)
        {
            System.out.println("=======Hello World: str=" + str);    
            return true;
        }
    }

    切面支持类:

    public class HelloWorldAspect
    {
        public void beforeAdvice(String str)
        {
            System.out.println("=======before advice: str=" +  str);
        }
        
        public void afterFinalAdvice()
        {
            System.out.println("========after advice" );
        }
    }

    切面定义:

    <bean id="helloWorldService" class="aop.HelloWorldService"/>
       <bean id="aspect" class="aop.HelloWorldAspect"/>
       <aop:config>
       <aop:pointcut id="pointcut" expression="execution(* aop..*.*(..)) and args(param)"/>
               <aop:aspect ref="aspect">
                   <aop:before pointcut-ref="pointcut" method="beforeAdvice(java.lang.String)" arg-names="param"/>
               </aop:aspect>
       </aop:config>

    切面的配置最关键,这里的切点的表达式定义在原有的基础上新增了一个参数,参数的类型与通知实现方式中的类型相同。

    而前置通知里面方法的类型为String,arg-names="param"的作用是切入点中使用“args(param)”匹配的目标方法参数将自动传递给通知实现方法同名参数。

    测试类:

    public class AopTest
    {
        public static void main(String[] args) throws Exception
        {
            ApplicationContext ctx =
                new ClassPathXmlApplicationContext("applicationContext.xml");
            IHelloWorldService bean =
               ctx.getBean("helloWorldService",IHelloWorldService.class);
            bean.sayHello("hangzhou");
        }
    }

    执行结果:

    =======before advice: str=hangzhou
    =======Hello World: str=hangzhou

     2)后置返回通知

    目标类:目标方法返回值为true

    public class HelloWorldService implements IHelloWorldService
    {
        public boolean sayHello()
        {
            System.out.println("=======Hello World");    
            return true;
        }
    }

    通知实现类,这里接收

    public class HelloWorldAspect
    {
        public void beforeAdvice(String str)
        {
            System.out.println("=======before advice: str=" +  str);
        }
        
        public void afterFinalAdvice(Object arg)
        {
            System.out.println("========after advice.arg=" + arg);
        }
    }

    切面配置:其中arg-names="retVal"表示指定通知方法的参数名为“retVal”;returning="retVal"用于将目标方法的返回值赋给通知实现方法中参数名为“retVal”的参数

     <bean id="helloWorldService" class="aop.HelloWorldService"/>
       <bean id="aspect" class="aop.HelloWorldAspect"/>
       <aop:config>
       <aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
               <aop:aspect ref="aspect">
                   <aop:after-returning pointcut-ref="pointcut" 
                      method="afterFinalAdvice" arg-names="retVal" returning="retVal"/>
               </aop:aspect>
       </aop:config>

    执行结果:

    =======Hello World
    ========after advice.arg=true

    这里可以看到目标方法的返回值赋给了通知实现方法中的参数。

    3)后置异常通知

    目标类:

    public class HelloWorldService implements IHelloWorldService
    {
        public boolean sayHello()
        {
            System.out.println("=======Hello World");    
            throw new RuntimeException();
        }
    }

    通知实现类:

    public class HelloWorldAspect
    {
        public void beforeAdvice(String str)
        {
            System.out.println("=======before advice: str=" +  str);
        }
        
        public void afterFinalAdvice(Exception arg)
        {
            System.out.println("========after advice.arg=" + arg );
        }
    }

    切面定义部分:这里与参数传递类似,目标类抛出异常,通知方法才会被执行。且这里通过参数传递的方式,实现了将目标方法抛出的异常作为参数传递到通知实现方法中

    <aop:config>
       <aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
               <aop:aspect ref="aspect">
                   <aop:after-throwing pointcut-ref="pointcut" 
                      method="afterFinalAdvice" arg-names="retVal" throwing="retVal"/>
               </aop:aspect>
       </aop:config>

    执行结果:

    =======Hello World
    ========after advice.arg=java.lang.RuntimeException

     4)环绕通知:环绕通知时十分强大,可以决定目标方法是否执行、什么时候执行、执行时是否需要替换方法参数、执行完毕后是否需要替换返回参数。

    环绕通知方法中的第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型,在通知实现方法内部使用ProceedingJoinPoint的proceed()方法使目标方法执行,proceed方法可以传入可选的Object[]数组,该数组的值将作为目标方法执行时的参数。

    目标类:

    public class HelloWorldService implements IHelloWorldService
    {
        public boolean sayHello()
        {
            System.out.println("=======Hello World");    
            throw new RuntimeException();
        }
    
        @Override
        public void around(String param)
        {
            System.out.println("======Hello Around param:" + param);
        }
    }

    通知实现类片段:

    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable
        {
            Object retVal = pjp.proceed(new Object[] {"hangzhou"});  
            System.out.println("===========around after advice"); 
            return retVal;
        }

    切面定义部分:

    <aop:config>
       <aop:pointcut id="pointcut" expression="execution(* aop..*.*(..))"/>
               <aop:aspect ref="aspect">
                   <aop:around pointcut-ref="pointcut" 
                      method="aroundAdvice"/>
               </aop:aspect>
       </aop:config>

    测试类:

      public static void main(String[] args) throws Exception
        {
            ApplicationContext ctx =
                new ClassPathXmlApplicationContext("applicationContext.xml");
            IHelloWorldService bean =
               ctx.getBean("helloWorldService",IHelloWorldService.class);
            bean.around("haha");
        }

    执行结果:

    ======Hello Around param:hangzhou
    ===========around after advice

    从结果看到,我们在测试类中传入的参数“haha”被通知实现类中的方法替换成了“hangzhou”。

    这里要注意的是:Object retVal = pjp.proceed(new Object[] {"hangzhou"}); ,proceed方法用于执行目标方法,且目标方法的参数被new Object[]该数组替换,最终返回retVal字段。

    引入

    Spring允许为目标类引入新的接口,通过在< aop:aspect>标签内使用< aop:declare-parents>标签进行引入

    引入新增接口:

    public interface IProductService
    {
        public void product();
    }

    接口实现类:

    public class ProductService implements IProductService
    {
        @Override
        public void product()
        {
            System.out.println("==========hello product");
        }
    }

    配置文件:types-matching表示匹配接口IHelloWorldService的实现类,如HelloWorldService;implement-interface表示引入的接口;default-impl表示引入接口的实现。

    <aop:config>
               <aop:aspect>
                   <aop:declare-parents types-matching="aop.IHelloWorldService+" 
                         implement-interface="aop.IProductService" 
                          default-impl="aop.ProductService"/>
               </aop:aspect>
       </aop:config>

    测试类:ctx.getBean("helloWorldService", IProductService.class);可以直接获取到引入的接口。

    public static void main(String[] args) throws Exception
        {
            ApplicationContext ctx =
                new ClassPathXmlApplicationContext("applicationContext.xml");
            // IHelloWorldService bean =
            // ctx.getBean("helloWorldService",IHelloWorldService.class);
            // bean.around("haha");
            IProductService productService =
                ctx.getBean("helloWorldService", IProductService.class);
            productService.product();
        }

    执行结果:

    ==========hello product
  • 相关阅读:
    运算符重载
    简单函数template max
    const static extern
    python 关闭垃圾回收
    Easy and cheap cluster building on AWS backup
    [转] Maven更新父子模块的版本号, mvn versions:set
    [转] ansible批量执行命令展示
    HBase 批量删除表 disable_all drop_all
    自动添加 ssh key 到远程主机的脚本,应用sshpass和ssh-copy-id
    MongoDB ver 4 几个常用命令
  • 原文地址:https://www.cnblogs.com/dongguacai/p/6484821.html
Copyright © 2020-2023  润新知