Spring AOP 之 通知、连接点、切点、切面:https://blog.csdn.net/github_34889651/article/details/51321499
aop深度解析:https://blog.csdn.net/c_unclezhang/article/details/78769426
使用注解声明切面:https://www.cnblogs.com/chopper-zx/p/7886166.html
什么是AOP
AOP是Aspect-Oriented Programming,指的是面向切面编程。指的是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP就实现了把这些业务需求与系统需求分开来做。这种解决的方式也称代理机制。
先来了解一下AOP的相关概念
切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在ApplicationContext中aop:aspect来配置。
连接点(Joinpoint):程序执行过程中的某一行为,例如,MemberService.get的调用或者MemberService.delete抛出异常等行为。
通知(Advice):“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“Advice”。
切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
目标对象(TargetObject):被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,SpringAOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
AOP代理(AOPProxy):在SpringAOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将aop:config的proxy-target-class属性设为true。
通知(Advice)类型:前置通知(Beforeadvice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在aop:aspect里面使用aop:before元素进行声明。例如,TestAspect中的doBefore方法。
后置通知(Afteradvice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在aop:aspect里面使用aop:after元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。
返回后通知(Afterreturnadvice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在aop:aspect里面使用元素进行声明。
环绕通知(Aroundadvice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在aop:aspect里面使用aop:around元素进行声明。例如,ServiceAspect中的around方法。抛出异常后通知(Afterthrowingadvice):在方法抛出异常退出时执行的通知。ApplicationContext中在aop:aspect里面使用aop:after-throwing元素进行声明。例如,ServiceAspect中的returnThrow方法。
注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。
使用SpringAOP可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的xml配置方式。
以下是一个简单的实例:
首先要再xml文档中配置切面信息:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"
>
<bean id="student" class="com.test.Student"></bean>
<bean id="teacher" class="com.test.Teacher"></bean>
<bean id="aspect" class="com.test.Aspect"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.test.*.introduct(..))" id="intro"/>
<aop:aspect id="order" ref="aspect">
<aop:before method="handShake()" pointcut-ref="intro"/>
<aop:after method="eat()" pointcut-ref="intro"/>
</aop:aspect>
</aop:config>
</beans>
然后是接口和实体类:
package com.test;
public interface IPerson {
public void introduct();
}
package com.test;
public class Student implements IPerson{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void introduct() {
// TODO Auto-generated method stub
System.out.println("我是一名学生");
}
}
package com.test;
public class Teacher implements IPerson {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void introduct() {
// TODO Auto-generated method stub
System.out.println("我是一名老师");
}
}
这是切面Aspect类:
package com.test;
public class Aspect {
public void handShake(){
System.out.println("先握手");
}
public void eat(){
System.out.println("后用餐");
}
}
最后是test类:
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext app=new ClassPathXmlApplicationContext("applicationContext.xml");
IPerson st=(IPerson) app.getBean("student");
st.introduct();
}
}
运行结果如下:
为什么使用AOP
按照我的理解:在以往的编程中,开发人员的注意力集中在面对对象上,但是当我们需要对多个并没有太大关联的对象进行相似的逻辑处理的时候,就可能产生大量的重复逻辑代码。面向切面编程,正是通过关注这些对象相同的部分,将这些部分看做链接点,复数的链接点就可以构成一个切面,通过面向切面编程,我们可以聚焦影响应用多处的功能(安全、事务、日志),完成模块化开发。
AOP的具体实现
首先让我们看一看Spring中主要的AOP组件:
Spring提供了两种方式来生成代理对象:JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。下面我们来研究一下Spring如何使用JDK来生成代理对象,具体的生成代码放在JdkDynamicAopProxy这个类中,直接上相关代码:
那这个其实很明了,注释上我也已经写清楚了,不再赘述。
下面的问题是,代理对象生成了,那切面是如何织入的?
我们知道InvocationHandler是JDK动态代理的核心,生成的代理对象的方法调用都会委托到InvocationHandler.invoke()方法。而通过JdkDynamicAopProxy的签名我们可以看到这个类其实也实现了InvocationHandler,下面我们就通过分析这个类中实现的invoke()方法来具体看下SpringAOP是如何织入切面的。
主流程可以简述为:获取可以应用到此方法上的通知链(InterceptorChain),如果有,则应用通知,并执行joinpoint;如果没有,则直接反射执行joinpoint。而这里的关键是通知链是如何获取的以及它又是如何执行的,下面逐一分析下。
首先,从上面的代码可以看到,通知链是通过Advised.getInterceptorsAndDynamicInterceptionAdvice()这个方法来获取的,我们来看下这个方法的实现:
可以看到实际的获取工作其实是由AdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()这个方法来完成的,获取到的结果会被缓存。下面来分析下这个方法的实现:
这个方法执行完成后,Advised中配置能够应用到连接点或者目标类的Advisor全部被转化成了MethodInterceptor.
接下来我们再看下得到的拦截器链是怎么起作用的。
从这段代码可以看出,如果得到的拦截器链为空,则直接反射调用目标方法,否则创建MethodInvocation,调用其proceed方法,触发拦截器链的执行,来看下具体代码