zoukankan      html  css  js  c++  java
  • Spring AOP

    Spring AOP


     

    AOP

    Aspect orient programming

    横切关注点分离 (Cross-cut concern seperate)

    1 public class StudentService{
    2   public void insert(){
    3     sout("业务代码第一行");
    4     sout("业务代码第二行");
    5     sout("业务代码第三行");
    6   } 
    7 }

    增加需求:要把整个项目中1000个业务类的8000个业务方法,都增强其功能,就是记录方法被调用的日志。

    1 public class StudentService{
    2   public void insert(){
    3     sout("此业务方法开始被调用");
    4     sout("业务代码第一行");
    5     sout("业务代码第二行");
    6     sout("业务代码第三行");
    7   } 
    8 }

    上面的写法有这些问题

    1. 违反了单一职责的原则

    2. 代码重复,样板化

    既然这样,我们就应该分开他们

    业务类

    1 public class StudentService{
    2   public void insert(){    
    3     sout("业务代码第一行");
    4     sout("业务代码第二行");
    5     sout("业务代码第三行");
    6   } 
    7 }

    日志类

    1 public class Log{
    2   public void start(){
    3     sout("此业务方法开始被调用");   
    4   } 
    5 }

    分开之后,是解决单一职责的问题,但又引入了新的问题,就是没有完成项目的需求,需要重新把2个类整合起来。

    为了叙述的方便,像核心业务类,也就是需要被增强的类的方法,这个需要被增强的方法(也称之为连接点JointPoint),它所在的类,称之为目标类(target),还有一个是用来增强别人的方法,此方法称之为增强方法(也叫作通知Advice),增强方法所在的类,就称之切面类

    我们不可能,也没有那么傻,写8000次,一定有别的办法解决

    我们可能找代码生成器来完成这个事情,可可能利用spring这种框架来完成,但是不管怎么样,最终的结果,一定是有这么一个方法

    1  public void insert(){
    2     sout("此业务方法开始被调用")
    3     sout("业务代码第一行");
    4     sout("业务代码第二行");
    5     sout("业务代码第三行");
    6 
    7   } 

    方法放到哪里去?

    1 public class StudentServiceChild extends StudentSerivce{
    2   @Override
    3   public void insert(){
    4     new Log().start();
    5     super.insert();
    6   }
    7 }

    上面的类就称之为代理类

    1 main(){
    2   StudentService service= new StudentServiceChild();
    3   service.insert();
    4 }

    刚才通过工具或者框架也好,生成的上面的那个类型,称之为代理类,然后整个过程就做织入(weaver)

    两个类如何整合的问题,所以实质上是2个类中2个方法进行整合。

    你需要传递给工具类或者框架的信息有以下几个,它才能帮你自动生成最终的结果类型,也就是代理类

    被增强的一方

    • 类是那一个?

    • 方法是哪一个?

    用来增强别人的样

    • 类是哪一个?

    • 方法是哪一个?

    • 何时增强

    Spring AOP 原生API

     1 StudentService studentService = new StudentService();
     2 
     3         LogAspect logAspect = new LogAspect();
     4         LogEndAspect logEndAspect = new LogEndAspect();
     5 
     6         ProxyFactory factory = new ProxyFactory();
     7 
     8         factory.setTarget(studentService);
     9 
    10         factory.addAdvice(logAspect);
    11 
    12         factory.addAdvice(logEndAspect);
    13 
    14         StudentService studentService1Proxy = (StudentService) factory.getProxy();
    15 
    16         studentService1Proxy.insert();

    因为上面的写法与spring的用法不一致,也比较底层。推荐的用法就是下面的这种

    <!-- 确定目标类-->
        <bean id="target" class="springaopapi.StudentService"/>
    
        <bean id="firstAspect" class="springaopapi.LogAspect"/>
    
        <bean id="proxyGenerator" class="org.springframework.aop.framework.ProxyFactoryBean">
            <property name="target" ref="target"/>
            <property name="interceptorNames" value="firstAspect"/>
        </bean>
    1  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    2         StudentService service = applicationContext.getBean("proxyGenerator", StudentService.class);
    3         service.insert();

    上面的写法,虽然解决了统一使用的问题,但也有需要配置多次的问题(因为你有很多个目标类),同时还强制要求我们的切面类必须实现spring的特定接口(比如AfterReturningAdvice),这两点并不好。

    所以Spring推出了下面的方式,这样上面的问题就都解决了。

    业务类

     1 public class StudentService {
     2 
     3     public  void update(){
     4         System.out.println("update ");
     5     }
     6 
     7     public  void insert(){
     8         System.out.println("insert ");
     9     }
    10 }

    切面类,没有实现任何接口

    1 public class MyAspect {
    2 
    3     public  void beforeA(){
    4         System.out.println("before-----");
    5     }
    6 }

    配置文件

     <bean id="target" class="aop.StudentService"/>
        <bean id="aspect" class="aop.MyAspect"/>
        <aop:config>
            <aop:aspect ref="aspect">
                <aop:before method="beforeA" pointcut="execution( public * aop.StudentService.update())"/>
            </aop:aspect>
        </aop:config>
    <!-- 启动自动代理-->
        <aop:aspectj-autoproxy/>

    使用

    1  ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    2 
    3         StudentService studentService = applicationContext.getBean("target", StudentService.class);
    4         studentService.update();
    5         System.out.println("-------------------");
    6         studentService.insert();

    常见的术语

    1. 目标类(target):被增强类

    2. 连接点(Jointpoint):被增强的方法

    3. 切面(aspect):用来增强别人的,由切点与增强组成

    4. 通知(advice):也叫增强,用来增强别人功能的方法,在切面类中

    5. 织入(weaver): 目标类与切面整合在一起的过程,也就是把通知切入到连接点的过程

    6. 代理类:完成织入之后的结果类型

    7. 切点(Pointcut):也称之为切点表达式,用来描述需要被增强的连接点信息,所以也称之为连接点的集合

    切点表达式


     

    结构

    modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

    构成如下

    1. 访问修饰符(可选的)

    2. 返回类型(必须的)

    3. 声明的类型模式(包含所在的包与类):可选

    4. 名称(必须的)

    5. 参数(必填)

    示例

    public void com.nf.A.insert()

    可以简写为

     void  insert()

    通配符

    三个通配符

    1. * (星号)表示任意

    2. .. (两个点) 表示任意字符数量

    3. + (加号) 它放在类的后面,表示此类的子类型

      com.*.service    ->com.nf.service(ok) com.util.service(ok) com.nf.util.service(no)
      com..service    ->com.nf.service(ok) com.util.service(ok) com.nf.util.service(no)
      • the execution of any public method:

      execution(public * *(..))
      • the execution of any method with a name beginning with "set":

      execution(* set*(..))
      • the execution of any method defined by the AccountService interface:

      execution(* com.xyz.service.AccountService.*(..))
      • the execution of any method defined in the service package:

      execution(* com.xyz.service.*.*(..))
      • the execution of any method defined in the service package or a sub-package:

      execution(* com.xyz.service..*.*(..))
      • any join point (method execution only in Spring AOP) within the service package:

      within(com.xyz.service.*)
      • any join point (method execution only in Spring AOP) within the service package or a sub-package:

      within(com.xyz.service..*)
      • any join point (method execution only in Spring AOP) where the proxy implements the AccountServiceinterface:

      this(com.xyz.service.AccountService)

    逻辑操作符

    1. && (and)

    2. || (or)

    3. ! (not)

    this(com.xyz.service.AccountService) and within(com.xyz.service..*)

    指示器


     

    • execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP

    • within - limits matching to join points within certain types (simply the execution of a method declared within a matching type when using Spring AOP)

    • this - limits matching to join points (the execution of methods when using Spring AOP) where the bean reference (Spring AOP proxy) is an instance of the given type

    • target - limits matching to join points (the execution of methods when using Spring AOP) where the target object (application object being proxied) is an instance of the given type

    • args - limits matching to join points (the execution of methods when using Spring AOP) where the arguments are instances of the given types

    • @target - limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type

    • @args - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)

    • @within - limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP)

    • @annotation - limits matching to join points where the subject of the join point (method being executed in Spring AOP) has the given annotation

     

    通知


     

    通知类型

    1. before:前置通知 :在被拦截方法之前执行

    2. after-returning:后置通知 ,被拦截方法正常执行之后才会执行,如果被拦截方法抛出了异常,就不执行

    3. after通知:最终通知,不管被拦截方法有没有抛出异常,都会执行

    4. after-throw:异常通知,被拦截的方法抛出了异常才会执行,不抛出异常,不会得到执行

    5. around:环绕通知:最强大的通知,可以处理上面4种通知。

    学习研究通知考虑点

    1. 连接点方法(被拦截方法)考虑2个方面

      1. 抛出异常

      2. 不抛出异常

    2. 通知考虑以下几个方面

      1. 通知方法与连接点方法执行的顺序

      2. 理解哪些通知方法会执行,不会执行

      3. 理解同类型通知执行顺序问题

      4. 理解通知方法参数的问题

      5. 多个环绕通知执行情况

    测试:

    1. 被拦截方法如果没有执行,afterReturning通知会执行吗?

    参数处理

    看一下下面的代码

    1 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    2 
    3         StudentService studentService = applicationContext.getBean("target", StudentService.class);
    4         studentService.insert("cj");

    本质上解决参数的传递有两种方法

    1. 利用JoinPoint :所有的通知方法第一个参数是这个类型,spring框架自动帮你赋值

    2. 参数绑定

    joinPoint作为通知方法的参数传递参数

    利用JoinPoint传递参数给通知方法的重要的要求是,此类型必须 是第一个参数。下面是实现过程

    1.编写目标类:

    1 public class TeacherService {
    2 
    3     public  void update(String a,String b){
    4         System.out.println(" --" + a + "---" + b);
    5     }
    6 }

    2.编写切面类

    1 public class TeacherAspect {
    2 
    3     public  void before(JoinPoint joinPoint){
    4         System.out.println(joinPoint.getArgs().length);
    5         System.out.println(joinPoint.getArgs()[0]);
    6         System.out.println(joinPoint.getArgs()[1]);
    7       //  System.out.println("****" + aa + "***" + bb);
    8     }
    9 }

    3.编写配置

    1 <bean id="target" class="param.TeacherService"/>
    2  <bean id="asp" class="param.TeacherAspect"/>
    3     <aops:config>
    4            <!-- 切点表达式;pointcut切入点-->
    5         <aops:pointcut id="mypc" expression="args(String,String)"></aops:pointcut>
    6         <aops:aspect ref="asp">
    7             <aops:before method="before" pointcut-ref="mypc"/>
    8         </aops:aspect>
    9     </aops:config>

    上面的args(String,String) 表达式的意思是:寻找方法,并不是绑定参数,所以只需要类型就可以了

    4.使用

    1   public static void main(String[] args) {
    2         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("paramContext.xml");
    3     
    4         TeacherService studentService = applicationContext.getBean("target", TeacherService.class);
    5         studentService.update("1111","|2222");
    6 
    7     }

    这种方式,缺点是JoinPoint类型侵入到了通知方法,但优点是无需通知方法参数个数与连接点方法个数一样

    arg-names传递参数

    1.编写目标类:

    1 public class TeacherService {
    2 
    3     public  void update(String a,String b){
    4         System.out.println(" --" + a + "---" + b);
    5     }
    6 }

    2.编写切面类

    1 public class TeacherAspect {
    2     public  void before(String aa,String bb){
    3          System.out.println("****" + aa + "***" + bb );
    4     }
    5 }

    3.编写配置

    几个要点如下

    • 不是所有的切点指示器都支持参数传递的,args指示器是支持的

    • 下面切点表达式中的args(xx,yy)的xx,yy名字可以随便取,不需要与目标方法参数名一样。

    • 在配置通知的arg-names时,里面的名字必须与切点表达式的参数名一样,个数也一样,但顺序可以不一样。也不要求与切面类中的通知方法的参数名一样。

      1 <bean id="target" class="param.TeacherService"/>
      2  <bean id="asp" class="param.TeacherAspect"/>
      3     <aops:config>
      4       <!-- 切点表达式-->
      5         <aops:pointcut id="mypc" expression="args(xx,yy)"></aops:pointcut>
      6         <aops:aspect ref="asp">
      7             <aops:before method="before" pointcut-ref="mypc" arg-names="yy,xx"/>
      8         </aops:aspect>
      9     </aops:config>

    4.使用

    1   public static void main(String[] args) {
    2         ApplicationContext applicationContext = new ClassPathXmlApplicationContext("paramContext.xml");
    3     
    4         TeacherService studentService = applicationContext.getBean("target", TeacherService.class);
    5         studentService.update("1111","|2222");
    6 
    7     }

    5.输出

    ****|2222***1111
     --1111---|2222

    这种方式缺点是通知方法个数与连接点方法个数要求一样,优点是无需JoinPoint作为通知方法的参数,没有侵入

    Advisor

    通知者。是一个官方的切面类的表示,里面只有一个通知方法说法

     

  • 相关阅读:
    Spring Boot 使用 Dom4j XStream 操作 Xml
    Spring Boot 使用 JAX-WS 调用 WebService 服务
    Spring Boot 使用 CXF 调用 WebService 服务
    Spring Boot 开发 WebService 服务
    Spring Boot 中使用 HttpClient 进行 POST GET PUT DELETE
    Spring Boot Ftp Client 客户端示例支持断点续传
    Spring Boot 发送邮件
    Spring Boot 定时任务 Quartz 使用教程
    Spring Boot 缓存应用 Memcached 入门教程
    ThreadLocal,Java中特殊的线程绑定机制
  • 原文地址:https://www.cnblogs.com/sunduge/p/8335823.html
Copyright © 2011-2022 走看看