PointCut
AOP标准中的Joinpoit
可以有很多类型:构造方法调用、字段的设置和获取、方法调用和执行等。而Spring AOP中只支持方法执行类型的Joinpoint
,不过这已经够我们用了。
Spring AOP中通过接口org.springframework.aop.Pointcut
来表示所有连接点Joinpoit
的抽象。Pointcut
接口的代码定义如下:
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
ClassFilter
将用来匹配目标对象,MethodMatcher
用来匹配将被执行织入操作的相应方法。TruePointcut
表示匹配所有对象。
public interface ClassFilter {
/**
* 当织入的目标对象的Class类型和Pointcut所规定的类型相同时,
* 该方法返回true
*/
boolean matches(Class<?> clazz);
/**
* 匹配所以类的ClassFilter实例.
*/
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
MethodMatcher
接口的代码定义如下:
public interface MethodMatcher {
/**
* 判断方法是否匹配,静态的MethodMatcher调用
*/
boolean matches(Method method, Class<?> targetClass);
/**
* 判断MethodMatcher是否是动态的,如果是动态的该方法返回TRUE,将会调用3个参数的matches方法。
* 如果是静态的,该方法返回FALSE,将会调用2个参数的matches方法。
*/
boolean isRuntime();
/**
* 判断是否匹配方法,动态的MethodMatcher调用
*
*/
boolean matches(Method method, Class<?> targetClass, Object... args);
/**
* 匹配所有方法的MethodMatcher实例
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
根据是否需要捕捉目标方法执行时的参数,可以将MethodMatcher
分为动态和静态两种。在MethodMatcher
类型的基础上,Pointcut
可以分为两类,即StaticMethodMatcherPointcut
和DynamicMethodMatcherPointcut
。因为StaticMethodMatcherPointcut
具有明显的性能优势,所以,Spring为其提供了更多支持。
如果想要自定义PointCut我们可以根据实现需求 ,可以选择继承
StaticMethodMatcherPointcut
或者继承DynamicMethodMatcherPointcut
Joinpoint和Pointcut的区别:这两个概念差不多,可以把他们当成一回事。一个Pointcut可以包含多个Joinpoint.
Advice(增强)
Spring中的Advice
实现全部基于AOP Alliance规定的接口。
按照增强(advice)在目标对象方法连接点的位置,可以将增强分为以下五类:
- 前置增强:
org.springframework.aop.BeforeAdvice
,在目标方法执行前执行; - 后置增强:
org.springframework.aop.AfterReturningAdvice
,在目标方法调用后执行; - 环绕增强:
org.aopalliance.intercept.MethodInterceptor
,截取目标类方法的执行,并在前后添加横切逻辑; - 抛出异常增强:
org.springframework.aop.ThrowsAdvice
,目标方法抛出异常后执行; - Introduction增强:
org.springframework.aop.introductioninterceptor
Spring AOP中的AfterReturningAdvice
只有在方法正常返回时才会执行,且不能更改方法的返回值。所以要想实现类似资源清理的横切工作,无法使用AfterReturningAdvice
,而Spring AOP并没有提供After Finally Advice。如果要想实现资源清理的工作我们可以借助Around Advice,它在Spring AOP的API编程实现中没有对应的实现类,不过可以借助MethodInterceptor
来实现Around Advice。下面来看看如何定义一个Around Advice
/**
* 通过MethodInterceptor来实现Around Advice
*/
public class PerformanceMethodInterceptor implements MethodInterceptor{
private final Logger logger = LoggerFactory.getLogger(PerformanceMethodInterceptor.class);
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch stopWatch = new StopWatch();
try {
stopWatch.start();
return invocation.proceed();
} catch (Exception e){
// do nothing
} finally {
stopWatch.stop();
if (logger.isInfoEnabled()){
logger.info(stopWatch.toString());
}
}
return null;
}
}
异常抛出增强类的定义接口是ThrowsAdvice
,它是一个标志接口,内部没有定义任何方法。不过我们在编写ThrowsAdvice
的实现类时,必须要定义如下方法:
/**
* 1. 方法名必须是afterThrowing
* 2. 前三个参数(method,args,target)是可选的,不过必须是要么同时存在,要么同时不存在
* 3. 第四个参数必须存在,可以是Throwable或者其任何子类
* 4. 可以存在多个符合规则的afterThrowing,Spring会自动选择最匹配的
*/
public void afterThrowing(Method method,Object[] args,Object target,Throwable t)
ThrowsAdvice
的实现如下:
public class MyThrowsAdvice implements ThrowsAdvice {
private Logger logger = LoggerFactory.getLogger(MyThrowsAdvice.class);
public void afterThrowing(Method method, Object[] args, Object target, Throwable t) {
logger.error("发送异常啦",t);
}
public void afterThrowing(RuntimeException t) {
logger.error("发生了运行时异常,异常信息:",t);
}
}
Introduction
除了常见的Advice之外,还有一种特殊的Advice--Introduction。Introduction可以在不改变目标类的情况下,为目标类添加新的属性以及行为。要想为目标对象添加新的属性和行为,必须要先声明对应的接口和实现类,然后可以通过拦截器IntroductionInterceptor
实现添加。
下面来演示一下
DelegatingIntroductionInterceptor
的用法。
public class DelegatingIntroductionInterceptorSample {
public static void main(String[] args) {
IDancer dancer = new Dancer();
DelegatingIntroductionInterceptor interceptor = new DelegatingIntroductionInterceptor(dancer);
ProxyFactory weaver = new ProxyFactory(new Singer());
weaver.setInterfaces(new Class[]{IDancer.class,ISinger.class});
weaver.addAdvice(interceptor);
Object proxy = weaver.getProxy();
((IDancer)proxy).dance();
((ISinger)proxy).sing();
}
}
Aspect
我们知道@Aspect可以用来表示Aspect。不过在针对面向API编程的Spring AOP中,Advisor
用来表示Spring中的Aspect。Advisor
只能看成是一种特殊的Aspect
,因为在Advisor
中通常只持有一个Pointcut和一个Advice(实际的Aspect定义中可以有多个Pointcut和多个Advice)。
Advisor可以分为两种:
- PointcutAdvisor
- IntroductionAdvisor
织入
织入就是为了创建代理对象。当有了切入点和横切逻辑(advice)之后,如何在目标对象(或方法)中加入横切逻辑呢?这个时候我们需要借助织入器将横切逻辑织入目标对象当中。
在Spring AOP中,根据一次创建代理的个数,可以分为创建单个代理的织入器和创建多个代理的织入器(即自动代理)。
Spring AOP中创建单个代理的织入器的类有:
- ProxyFactory
- ProxyFactoryBean
- AspectJProxyFactory
在介绍织入相关的内容之前,我们先来看一下相关类的继承图。理解了这张图,织入的原理(其实差不多也就是Spring AOP的原理)便能了然于心。 Spring AOP织入(单个)相关类图AspectJ使用ajc编译器作为织入器;Jboss aop使用自定义的ClassLoader作为织入器。
ProxyFactory
ProxyFactory
的使用示例:
// 创建目标对象
IService target = new ServiceOne();
// 创建增强类对象(advice)
MethodInterceptor advice = new PerformanceMethodInterceptor();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(advice);
/* 或者
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(advisor);
*/
IService proxy = (IService) proxyFactory.getProxy();
System.out.println(target.getClass());
System.out.println(proxy.getClass());
Spring AOP借助动态代理技术,可以创建基于接口的代理;借助CGLIB,可以创建基于类的代理。一般根据代理对象class的输出System.out.println(proxy.getClass());
,可以观察出代理对象是通过什么技术生成的。
class $Proxy0 表示代理对象是通过动态代理生成的;
$$EnhancerByCGLIB$$9e62fc83 表示代理对象是通过cglib生成的;
默认情况下,Spring AOP会使用动态代理基于接口生成代理对象,当出现下列情况会使用CGLIB基于类生成代理对象。
- 目标类没有实现任何接口;
- ProxyFactory的proxyTargetClass属性值被设置为true;
- ProxyFactory的optimize属性值被设置为true。
ProxyFactoryBean
ProxyFactoryBean和ProxyFactory的使用没有太大的区别。一般ProxyFactory
是脱离IOC
容器来使用,而ProxyFactoryBean
则与IOC容器结合使用。
<!-- 目标对象 -->
<bean id="targetObject" class="cn.zgc.aop.apis.advice.advices.TargetObject"/>
<!-- 前置增强 -->
<bean id="myBeforeAdvice" class="cn.zgc.aop.apis.advice.advices.MyBeforeAdvice"/>
<!-- 后置增强 -->
<bean id="myAfterReturningAdvice" class="cn.zgc.aop.apis.advice.advices.MyAfterReturningAdvice"/>
<!-- 抛出异常增强 -->
<bean id="myThrowsAdvice" class="cn.zgc.aop.apis.advice.advices.MyThrowsAdvice"/>
<!-- 环绕增强 -->
<bean id="myAroundAdvice" class="cn.zgc.aop.apis.advice.advices.MyAroundAdvice"/>
<bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean"
p:proxyTargetClass="true"
p:target-ref="targetObject"
p:interceptorNames="myAroundAdvice,myBeforeAdvice,myAfterReturningAdvice"
/>
自动代理
Spring AOP为我们提供了自动代理机制,让容器为我们自动生成代理,这样我们就不用针对每个目标对象若想生成代理对象,都需要配置相应的ProxyFactoryBean。在Spring内部,通过借助BeanPostProcessor完成自动代理这项工作。
BeanPostProcessor类可以在对象实例化前为其生成代理对象并返回,而不是实例化后的目标对象本身,从而达到代理对象自动生成的目的
常用的自动代理类
- BeanNameAutoProxyCreator
- DefaultAdvisorAutoProxyCreator
- AnnotationAwareAspectJAutoProxyCreator
看看下面的类继承图,可以发现自动代理类都实现了BeanPostProcessor
BeanNameAutoProxyCreator
BeanNameAutoProxyCreator
通过beanNames
属性指定目标对象集合,通过interceptorNames
属性指定advice
。只要在IOC容器中注册BeanNameAutoProxyCreator
,就能为目标对象创建出代理。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过Bean名称自动创建代理 -->
<!-- 目标Bean -->
<bean id="serviceOne" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceOne"/>
<bean id="serviceTwo" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceTwo"/>
<!-- 增强 -->
<bean id="privilegeDetectionAdvice" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.PrivilegeDetectionAdvice"/>
<!-- 自动代理-->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>service*</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>privilegeDetectionAdvice</value>
</list>
</property>
</bean>
</beans>
DefaultAdvisorAutoProxyCreator
要想使用DefaultAdvisorAutoProxyCreator
的话,需要在配置文件中注册(配置)DefaultAdvisorAutoProxyCreator
和相关Advisor
的bean,并且要想DefaultAdvisorAutoProxyCreator
的自动配置生效的话,必须得配置Advisor
,因为只有Advisor
当中才即包含Pointcut
又包含Advice
,有了这两个信息之后,DefaultAdvisorAutoProxyCreator
就能通过Advisor
的信息自动为目标对象生成代理。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过Advisor自动创建代理 -->
<!-- 目标Bean -->
<bean id="serviceOne" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceOne"/>
<bean id="serviceTwo" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.ServiceTwo"/>
<!-- 增强 -->
<bean id="privilegeDetectionAdvice" class="cn.zgc.aop.apis.weave.autoCreateProxy.BeanNameAutoProxyCreator.PrivilegeDetectionAdvice"/>
<!-- Aspect-->
<bean id="privilegeAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut">
<bean class="org.springframework.aop.support.NameMatchMethodPointcut">
<property name="mappedNames">
<list>
<value>service*</value>
</list>
</property>
</bean>
</property>
<property name="advice" ref="privilegeDetectionAdvice"/>
</bean>
<!-- 自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>
AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator
可以自动将@AspectJ注解切面类织入目标Bean中。注意,要想使用@AspectJ
需要导入aspectjweaver
的jar包。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过配置使用@AspectJ -->
<!-- 目标Bean -->
<bean id="service" class="cn.zgc.aop.apis.weave.autoCreateProxy.AnnotationAwareAspectJAutoProxyCreator.ServiceOne"/>
<!-- 使用了@AspectJ注解的切面类 -->
<bean class="cn.zgc.aop.apis.weave.autoCreateProxy.AnnotationAwareAspectJAutoProxyCreator.PrivilegeDetectionAspect"/>
<!-- 自动代理创建器,自动将@AspectJ注解切面类织入目标Bean中 -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator">
<!-- true:使用cglib生成代理对象,false(默认):使用动态代理生成代理对象 -->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
建议
基于API编程的方式对于我们理解Spring AOP的原理很有帮助,我们应该对其要有所了解,但是不推荐再使用这种方式了。
参考
《Spring揭秘 》.王福强
网友评论