前言
Aspectj是面向切面编程中的java实现类库,Spring可以更方便的集成aspectj. SpringAOP的底层是基于代理模式来实现的。默认情况下有两种实现策略: 一是基于JDK的动态代理,二是基于CGLIB字节码生产库来实现。
Spring AOP从类库层面集成了Cglib与AOP联盟(org.aopalliance),如果要使用aspectj.需要引入aspectjweaver.jar,maven依赖如下:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency>
Spring的AOP设计
开启AspectJ定义切面
一是采用@Aspectj的注解,二是基于XML的配置方式。推荐使用注解风格来定义切面。
如通过注解开启aspectj
@Configuration@EnableAspectJAutoProxypublic class AppConfig {}
通过xml配置开启:
aop:aspectj-autoproxy/
添加@EnableAspectJAutoProxy或<aop:aspectj-autoproxy/>后会隐式注册如下BeanPostProcessor到容器中:
AnnotationAwareAspectJAutoProxyCreator
该类在Spring中的类图如下:
可以看到该类的作用主要与创建代理有关,也就是说启用aspect后,都会作用到相应的bean上,并给对应的bean生成对应的代理,如果启用了proxyTargetClass则强制生成cglib代理。
AnnotationAwareAspectJAutoProxyCreator的postProcessBeforeInstantiation方法或wrapIfNecessary方法在容器的Refresh阶段会被调用,因此可以容器启动阶段对满足条件的bean调用createProxy方法,最后返回的bean实例即为被代理后的bean.
AspectJ使用
Aspect中的常用概念
Aspect(切面):一个模块化的关注点,并可用于横切多个类,例如日志记录,事物处理,异常捕获等。JoinPoint(连接点):执行程序的切入点,表示一次方法执行。Pointcut(横切点): 表示匹配连接点的前置条件,并关联到相应的Advice上。Advice(通知): 英文直意为通知,建议等,我理解为在调用目标方法时,需要做某些相关的操作,可以是前置操作,后置操作,这些操作需要告知目标对象。有的翻译为增强处理,也可行,重在理解即可。
支持的横切点语法
Spring AOP supports the following AspectJ pointcut designators (PCD) for use in pointcut expressions:
execution: For matching method execution join points. This is the primary pointcut designator to use when working with Spring AOP.
within: Limits matching to join points within certain types (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 types.
@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 (the method being executed in Spring AOP) has the given annotation.
常见示例
Combining Pointcut Expressions
@Pointcut("execution(public * *(..))")private void anyPublicOperation() {}@Pointcut("within(com.xyz.someapp.trading..*)")private void inTrading() {}@Pointcut("anyPublicOperation() && inTrading()")private void tradingOperation() {}
Combining Pointcut Expressions
@Aspectpublic class SystemArchitecture {/*** A join point is in the web layer if the method is defined* in a type in the com.xyz.someapp.web package or any sub-package* under that.*/@Pointcut("within(com.xyz.someapp.web..*)")public void inWebLayer() {}/*** A join point is in the service layer if the method is defined* in a type in the com.xyz.someapp.service package or any sub-package* under that.*/@Pointcut("within(com.xyz.someapp.service..*)")public void inServiceLayer() {}/*** A join point is in the data access layer if the method is defined* in a type in the com.xyz.someapp.dao package or any sub-package* under that.*/@Pointcut("within(com.xyz.someapp.dao..*)")public void inDataAccessLayer() {}/*** A business service is the execution of any method defined on a service* interface. This definition assumes that interfaces are placed in the* "service" package, and that implementation types are in sub-packages.** If you group service interfaces by functional area (for example,* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"* could be used instead.** Alternatively, you can write the expression using the 'bean'* PCD, like so "bean(*Service)". (This assumes that you have* named your Spring service beans in a consistent fashion.)*/@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")public void businessService() {}/*** A data access operation is the execution of any method defined on a* dao interface. This definition assumes that interfaces are placed in the* "dao" package, and that implementation types are in sub-packages.*/@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")public void dataAccessOperation() {}}
exection 表达式
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
Declaring Advice
@Aspectpublic class BeforeExample {@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")public void doAccessCheck() {// ...}@Before("execution(* com.xyz.myapp.dao.*.*(..))")public void doAccessCheck() {// ...}@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")public void doAccessCheck() {// ...}@Around("com.xyz.myapp.SystemArchitecture.businessService()")public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {// start stopwatchObject retVal = pjp.proceed();// stop stopwatchreturn retVal;}}
编程API使用AspectJ
@Testpublic void testAspect() {HelloSpring hs = new HelloSpring();// create a factory that can generate a proxy for the given target objectAspectJProxyFactory factory = new AspectJProxyFactory(hs);// add an aspect, the class must be an @AspectJ aspect// you can call this as many times as you need with different aspects// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspectfactory.addAspect(BeforeAspect.class);factory.addAspect(AfterAspect.class);factory.addAspect(AroundAspect.class);// now get the proxy object...HelloSpring proxy = factory.getProxy();proxy.show();}
获取被代理后的实例
getBean() -> doCreateBean() -> beanPostProcessor -> AbstractAutoProxyCreator#getEarlyBeanReference -> ProxyFactory.getProxy
-> createAopProxy().getProxy(classLoader)
ProxyFactory的类图如下:(以AspectJ为例)
分析如下:
传入到构造方法中的bean或target对象会通过ProxyCreatorSupport记录对象的所有interface并把bean或target包装为SingletonTargetSource.
factory.addAspect的切面会记录到Creator的List<Advisor> advisors属性里。
调用getProxy时,则执行getAopProxyFactory().createAopProxy(ProxyCreatorSupport)
最后通过返回的AopProxy.getProxy()拿到被代理后的对象。
获取代理Bean背后的原始Bean
Class<?> targetClass = AopUtils.getTargetClass(bean);
查找@Aspect
AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisorsprotected List<Advisor> findCandidateAdvisors() {// Add all the Spring advisors found according to superclass rules.List<Advisor> advisors = super.findCandidateAdvisors();// Build Advisors for all AspectJ aspects in the bean factory.if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}return advisors;}BeanFactoryAspectJAdvisorsBuilder#buildAspectJAdvisorsAbstractAspectJAdvisorFactory#hasAspectAnnotation/*** We consider something to be an AspectJ aspect suitable for use by the Spring AOP system* if it has the @Aspect annotation, and was not compiled by ajc. The reason for this latter test* is that aspects written in the code-style (AspectJ language) also have the annotation present* when compiled by ajc with the -1.5 flag, yet they cannot be consumed by Spring AOP.*/@Overridepublic boolean isAspect(Class<?> clazz) {return (hasAspectAnnotation(clazz) && !compiledByAjc(clazz));}private boolean hasAspectAnnotation(Class<?> clazz) {return (AnnotationUtils.findAnnotation(clazz, Aspect.class) != null);}for (String beanName : beanNames) {if (!isEligibleBean(beanName)) {continue;}// We must be careful not to instantiate beans eagerly as in this case they// would be cached by the Spring container but would not have been weaved.Class<?> beanType = this.beanFactory.getType(beanName);if (beanType == null) {continue;}if (this.advisorFactory.isAspect(beanType)) {aspectNames.add(beanName);AspectMetadata amd = new AspectMetadata(beanType, beanName);if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {MetadataAwareAspectInstanceFactory factory =new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);if (this.beanFactory.isSingleton(beanName)) {this.advisorsCache.put(beanName, classAdvisors);}else {this.aspectFactoryCache.put(beanName, factory);}advisors.addAll(classAdvisors);}}
AOP联盟与代理
如果配置了增强Advisor(切面),Spring AOP采用AOP联盟提供的API(如MethodInterceptor)来封装代理的调用流程,主要是把切面封装为一个调用链,方法调用链最终执行的还是底层的代理逻辑。
DefaultAopProxyFactory -> createAopProxy -> ObjenesisCglibAopProxy or JdkDynamicAopProxy -> getProxy -> invoke -> MethodInterceptor -> MethodInvocation -> Method.invoke
如果没有任何切面,则执行JDK动态代理或CGLIB代理的原始调用逻辑。
JDK代理执行InvocationHandler#invoke方法,代码如下:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 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...MethodInvocation invocation =new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain.retVal = invocation.proceed();}}
CGlib则执行org.springframework.cglib.proxy.MethodInterceptor#intercept,代码如下:
/*** General purpose AOP callback. Used when the target is dynamic or when the* proxy is not frozen.*/private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {private final AdvisedSupport advised;public DynamicAdvisedInterceptor(AdvisedSupport advised) {this.advised = advised;}@Override@Nullablepublic Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {Object oldProxy = null;boolean setProxyContext = false;Object target = null;TargetSource targetSource = this.advised.getTargetSource();try {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);List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);Object retVal;// Check whether we only have one InvokerInterceptor: that is,// no real advice, but just reflective invocation of the target.if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {// 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 = methodProxy.invoke(target, argsToUse);}else {// We need to create a method invocation...retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();}retVal = processReturnType(proxy, target, method, retVal);return retVal;}finally {if (target != null && !targetSource.isStatic()) {targetSource.releaseTarget(target);}if (setProxyContext) {// Restore old proxy.AopContext.setCurrentProxy(oldProxy);}}}}
