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

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

 

原文地址:https://www.cnblogs.com/sunduge/p/8335823.html