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

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

     

  • 相关阅读:
    java.lang.NoSuchMethodError: org.springframework.core.io.ResourceEditor错误
    http://blog.sina.com.cn/s/blog_6145ed810102vr8k.html
    异或巧用:Single Number
    Highcharts:X轴分组堆叠图
    Vs2012在Linux开发中的应用(5):项目属性的定义
    BZOJ 1005 明明的烦恼 Prufer序列+组合数学+高精度
    Python 点滴 I
    easyUI 验证控件应用、自己定义、扩展验证 手机号码或电话话码格式
    InnoDB: Error: io_setup() failed with EAGAIN after 5 attempts
    Java设计模式-设计模式的六种原则
  • 原文地址:https://www.cnblogs.com/sunduge/p/8335823.html
Copyright © 2011-2022 走看看