一、基础概念
1 aop定义
AOP技术利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即切面。
所谓“切面”,简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。
AOP代表的是一个横向的关系,相比于oop的垂直关系而言。
如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;
那么面向方面编程的方法,就仿佛把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。然后它又以巧夺天功的妙手将这些剖开的切面复原,不留痕迹。
自己的理解:其实就是一种面向切面的编程思想。对应原来面向对象的方法,通过代理的方式(将不会影响原来的逻辑)将公用的部分抽离出来,进行功能增强如前置或后置,已达到减少代码重复冗余,解耦合的目的。
2 AOP体系
3.AOP的核心组件
1.@Aspect注解
作用是把当前类标识为一个切面供容器读取。如果没有声明,将不会生成代理。
2.JoinPoint :连接点。
即代表被切入的具体方法,可以通过它来获取对应方法的一些信息。
如常用的方法如下:
public interface JoinPoint {
/**
* <p> Returns the target object. This will always be
* the same object as that matched by the <code>target</code> pointcut
* designator. Unless you specifically need this reflective access,
* you should use the <code>target</code> pointcut designator to
* get at this object for better static typing and performance.</p>
*
* <p> Returns null when there is no target object.</p>
*/
Object getTarget();
/**
* <p>Returns the arguments at this join point.</p>
*/
Object[] getArgs();
/** Returns the signature at the join point.
*
* <code>getStaticPart().getSignature()</code> returns the same object
*/
Signature getSignature();
}
3.切入点(Pointcut)
指定一个通知将被引发的一系列连接点的集合,用了匹配要影响的类及方法。即配置要被代理的类
如:
@Pointcut("execution (* com.dw.backstage.service..*.*(..))")
public void pointcut(){
}
4.通知(Advice): 在特定的连接点,AOP框架执行的动作。常用的通知组件有:
1.前置通知: @Before
执行目标方法前拦截到的方法。没有特殊注意的地方,只需要一个连接点,JoinPoint,即可获取拦截目标方法以及请求参数。
2.后置通知:@After
在方法执行之后执行的代码. 无论该方法是否出现异常,类似于finally,且在@AfterReturning返回值之前
3.返回通知:@AfterReturning
在方法正常结束后才会执行的代码(有异常则不会执行),返回通知是可以访问到方法的返回值的!
4.异常通知:@AfterThrowing
在目标方法出现异常时才会执行的代码,可以访问到异常对象; 且可以指定在出现特定异常时才会执行通知代码
5.环绕通知:@Around
* 环绕通知需要携带 ProceedingJoinPoint 类型的参数.
* 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.
* 且环绕通知必须有返回值, 返回值即为目标方法的返回值
4.应用场景
1.参数校验如校验是否存在sql注入,参数为空判断等
2.日志管理,如参数打印,接口访问耗时等
3.读写分离
4.事务管理
5.设置缓存如Spring-cache
二、动态代理
1.JDK动态代理
JDK动态代理的实现机制是创建一个继承java.lang.reflect.Proxy类,并实现了要代理目标接口的class文件,即通过实现接口来完成代理。
JDK动态代理具体实现思路:
通过实现InvocationHandlet接口创建自己的调用处理器;
通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理
通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
Proxy 创建代理对象时序图:
2.Cglib原理
CGLib动态代理是通过字节码底层技术继承要代理的类,来并重写要代理的类的所有方法来实现代理功能。
思考
1.JDK 和 CGLib动态代理主要区别
1.JDK动态代理是面向接口的,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。
2.Cglib原理是针对目标类生成一个子类,覆盖其中的所有方法,所以目标类和方法不能声明为final类型,而jdk可以代理被声明为final的代理类。
2.JDK动态代理为什么只能代理接口?
参看jdk动态代理生成的代理对象字节码文件
public final class $Proxy0 extends Proxy implements ProxyInterface {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void targetMethod() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.gupao.designpattern.proxy.jdk.ProxyInterface").getMethod("targetMethod", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
生成的代理对象已经继承了java.lang.reflect.Proxy类,不能再通过继承的方式来实现多态特性,故使用接口的方式来实现多态特性。(成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个目标类)
三、Spring AOP底层实现原理
Spring aop中 @Around注解中的使用其实就是动态代理模式的一种实现。
aop使用到的设计模式有:
1.代理模式
2.工厂模式
代理工厂来创建的具体的代理类
3.责任链模式
bean在调用时,会把pointcut匹配的方法加入拦截器链。然后在链中通过反射依次匹配并调用方法
使用AspectJ的编译时增强实现AOP的方式参考:
Spring AOP的实现原理 http://www.importnew.com/24305.html
3.1 Spring AOP的实现原理
与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象。
spring借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,该对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
个人理解:就是在运行期被切入点(Pointcut)声明的类或类中的方法,在执行时将调用的是代理对象,并且持有原来的对象来进行功能增强处理,如前置通知,后置通知等
3.2 流程设计分析
可以将 AOP 分成 2 个部分来分析
第一:代理的创建;
第二:代理的调用。
3.2.1 创建代理对象流程
1.首先,需要创建代理工厂,代理工厂需要 3 个重要的信息:拦截器数组,目标对象接口数组,目标对象。
2.创建代理工厂时,默认会在拦截器数组尾部再增加一个默认拦截器 —— 用于最终的调用目标方法。
3.当调用 getProxy 方法的时候,会判断bean是接口还是类,然后确定使用JDK or Cglib
注意:创建代理对象时,同时会创建一个外层拦截器,这个拦截器就是 Spring 内核的拦截器。用于控制整个 AOP 的流程。
Spring aop创建的代理对象的时序图如下:
源码分析
代理的创建思路
代理的创建分为两个过程即匹配和创建。
而对应在spring的生命周期里是在实例化Bean和完成依赖注入后,会判断当前的Bean是否需要代理,如果需要,就生成代理类把原始类替换掉。
核心思想为:通过循环beanNames实例化Bean对象,判断此对象是否与pointcut表达式匹配。如果匹配就根据advice生成不同的advisor对象,然后调用JDK或者CGLIB的方法生成代理类返回。
代码分析:
创建代理的入口是AbstractAutoProxyCreator类它实现了BeanPostProcessor接口,会在bean的生命周期类执行创建代理流程。
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
wrapIfNecessary方法则是真正产生代理的地方,我们先看下它的内部实现。
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// Create proxy if we have advice.
//先看它的注释,大意说:如果有通知,就创建代理。
//这里面其实又分为两个步骤:
//第一,在Bean工厂找到所有的Advisor 第二,根据beanClass和Pointcut去做匹配
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//真正创建代理并返回,这时候返回的就是代理类了,把真实的bean替换掉
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
看到上面的源码,整个过程就分为了两步,匹配和创建返回。
1. 匹配
先是找到所有的Advisor,这个比较简单。因为我们知道,在解析的时候就把配置的通知封装成advisor注册了进去。首先拿到beanDefinitionNames容器所有beanName,循环判断bean的类型是不是advisor接口的类型,符合条件返回。
public List<Advisor> findAdvisorBeans() {
// Determine list of advisor bean names, if not cached already.
String[] advisorNames = null;
//先拿到beanName
advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.beanFactory, Advisor.class, true, false);
List<Advisor> advisors = new LinkedList<Advisor>();
for (String name : advisorNames) {
if (isEligibleBean(name)) {
try {
//再从Bean工厂中拿bean的实例
advisors.add(this.beanFactory.getBean(name, Advisor.class));
}
}
}
//返回的advisors就是配置文件中所有的advice和advisor
return advisors;
}
找到advisors并未结束,还要跟pointcut做匹配,看这些advisor符不符合表达式条件。它最终调用到Pointcut的match方法。这个方法嵌套的太深,就不贴代码了,核心思想就是判断class和class对应的method是否与pointcut表达式匹配。
2. 创建
设置代理工厂
经过上面查找匹配后,确定当前的bean确实需要代理,就调用createProxy方法。
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy源码如下
protected Object createProxy(Class<?> beanClass,
String beanName, Object[] specificInterceptors, TargetSource targetSource) {
//创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
//将beanClass上的接口设置到代理工厂
evaluateProxyInterfaces(beanClass, proxyFactory);
//设置Advisor到代理工厂
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
//设置目标对象
proxyFactory.setTargetSource(targetSource);
return proxyFactory.getProxy(this.proxyClassLoader);
}
创建代理对象
创建代理分为JDK的代理和Cglib的代理,这里我们先关注JDK的代理。getProxy方法就调用到JdkDynamicAopProxy类的方法。这个类还实现了InvocationHandler接口,说明它同时还是调用处理程序。即在调用代理类的invoke方法时,实际上就会调用到JdkDynamicAopProxy.invoke()。
public Object getProxy(ClassLoader classLoader) {
//这里又给代理工厂加了两个接口 SpringProxy和Advised
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
//这个方法就比较熟悉了,正是JDK动态代理的方法
//这里的this就是JdkDynamicAopProxy实例。
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
3.2.2 代理对象的调用流程
1.当对代理对象进行调用时,就会触发去获取目标方法外层拦截器。
2.代理对象会将拦截器构造成拦截器执行链。执行链中执行处理器时,会根据表达式判断当前拦截是否匹配这个拦截器。而这个执行链所用的设计模式就是职责链模式。
3.当整个链条执行到最后时,就会触发创建代理时那个尾部的默认拦截器,从而调用目标方法。最后返回。
设计流程图如下:
Spring 创建了代理对象后,当你调用目标对象上的方法时,将都会被代理到实现InvocationHandler接口的invoke 方法中执行。在这里 JdkDynamicAopProxy 类实现了 InvocationHandler 接口。
Spring aop调用拦截器的时序图
源码分析
代理类的调用
在业务方法里面调用的时候,就会调用到JdkDynamicAopProxy.invoke()。在执行invoke的时候,我们又可以分为两个步骤:
- 获取方法的拦截链
- 执行拦截链中处理器
核心思想为:调用JDK或者CGLIB的invoke方法,查询advisor的调用链。链式调用,根据通知类型调用不同的advice实现增强。
org.springframework.aop.framework.JdkDynamicAopProxy#invoke
源码如下:
/**
* Implementation of {@code InvocationHandler.invoke}.
* <p>Callers will see exactly the exception thrown by the target,
* unless a hook method throws an exception.
*/
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
//(这里开始获取拦截器链)
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target &&
returnType != Object.class && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
1. 获取并设置方法的拦截链
首先从代理工厂中拿到所有的advisor,然后判断是不是PointcutAdvisor类型,其次先matches一下targetClass,再matches一下method,证明这个类的方法在pointcut范围内,加入interceptorList,最后返回。
org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice源码如下:
/**
* Determine a list of {@link org.aopalliance.intercept.MethodInterceptor} objects
* for the given method, based on this configuration.
* @param method the proxied method
* @param targetClass the target class
* @return a List of MethodInterceptors (may also include InterceptorAndDynamicMethodMatchers)
*/
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, @Nullable Class<?> targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
org.springframework.aop.framework.DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice源码如下
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class<?> targetClass) {
List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
//config就是代理工厂的实例
for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
return interceptorList;
}
2. 调用
拿到方法的拦截链,然后调用。
调用的时候有个地方比较有意思。它先创建了一个对象ReflectiveMethodInvocation。这个对象还实现了org.aopalliance.intercept.Joinpoint方法。
这个对象有两个参数
org.springframework.aop.framework.ReflectiveMethodInvocation源码如下:
/**
* List of MethodInterceptor and InterceptorAndDynamicMethodMatcher
* that need dynamic checks.
*/
protected final List<?> interceptorsAndDynamicMethodMatchers;
/**
* Index from 0 of the current interceptor we're invoking.
* -1 until we invoke: then the current interceptor.
*/
private int currentInterceptorIndex = -1;
然后看它的调用方法。
org.springframework.aop.framework.ReflectiveMethodInvocation#proceed源码如下:
@Override
@Nullable
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.(没有匹配则找下一个链)
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
基于注解的实现与上面类似,可参考其他或类似文章
@see Spring源码分析(七)SpringAOP生成代理类及执行的过程 https://www.jianshu.com/p/cd1e8537c035
思考
1.为何会有拦截器链?
答:一个目标对象,还有可能被其他方式拦截,或者其他方式代理,就像你写应用代码时,你也可能会写多个切面,而其中可能会有部分会代理同一个类或者方法。使用拦截器器链可以将这些代理统一管理起来,并按对应的顺序依次执行。
四、使用总结
AspectJ 切面获取参数名称和参数
Map<String, Object> nameAndArgs=new HashMap<>();
Object[] args = joinPoint.getArgs();// 参数值
String[] parameterNames = ((MethodSignature) signature).getParameterNames();// 参数名称
@see AspectJ 切面获取参数名称和参数https://blog.csdn.net/qq_36020545/article/details/53191822
获取参数上的注解值
Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
if (parameters!=null){
Integer k=null;
for (int i = 0; i <parameters.length ; i++) {
Parameter parameter = parameters[i];
String name = parameter.getName();
Annotation[] annotations = parameter.getAnnotations();
Param param = parameter.getAnnotation(Param.class);
System.out.println(name);
if (param!=null){
String value = param.value();
System.out.println(value);
if ("startId".equals(value)){
Long startId = DataUtil.dateToShardingJdbcKey(DataUtil.getLastAmountMonthDayStart(--count));
k=i;
args[i]=startId;
continue;
}
if ("endId".equals(value)){
Long endId = DataUtil.dateToShardingJdbcKey(DataUtil.getMonthDayEnd());
args[i]=endId;
continue;
}
}
}
参考资料
1.spring AOP的实现原理 http://www.importnew.com/24305.html
2.Spring AOP源码解析——AOP动态代理原理和实现方式 https://my.oschina.net/zhaojia/blog/778455
3.《深入分析Java Web技术内幕》许令波著
4.Spring之AOP原理详解 https://yq.aliyun.com/ziliao/352300
5.问烂的 Spring AOP 原理、SpringMVC 过程 https://www.jianshu.com/p/e18fd44964eb