动态AOP使用示例
面向对象编程有一些弊端,当需要为多个不具有继承关系的对象引入同一个公共行为时,例如日志,安全检测等,我们只有在每个对象里引入公共行为,这样程序中就产生了大量的重复代码,所以有了面向对象编程的补充,面向切面编程(AOP'),AOP所关注的方向是横向的,不同于OOP的纵向。下面是一个AOP的示例
(1)创建用于拦截的bean。
在实际工作中,此bean可能是满足业务需要的核心逻辑。例如test方法中可能会封装着某个核心业务,但是,如果我们想在test前后加入日志来跟踪测试,如果直接修改源码并不符合面向对象的设计方法,而且随意改动原有代码也会造成一定的风险,还好接下来的Spring帮我们做到了这一点。
public class TestBean {
private String testStr= "testStr";
public String getTestStr(){
return testStr;
}
public void setTestStr(String testStr){
this.testStr = testStr;
}
public void test(){
System.out.println("test");
}
}
(2)创建Advisor
Spring中摒弃了最原始的繁杂配置方式而采用@AspectJ注解对POJO进行标注,使AOP的工作大大简化,例如,在AspectJTest类中,我们要做的就是在所有类的test方法执行前在控制台beforeTest。而在所有类的test方法执行后打印afterTest,同时又使用环绕的方式在所有类的方法执行前后在此分别打印before1和after1.
@Aspect
public class AspectJTest {
@Pointcut("execution(* *.test(..))")
public void test(){
}
@Before("test()")
public void beforeTest(){
System.out.println("beforeTest");
}
@Around("test()")
public Object aroundTest(ProceedingJoinPoint p){
System.out.println("before1");
Object o = null;
try{
o = p.proceed();
}catch(Throwable e){
e.printStackTrace();
}
System.out.println("after1");
return o;
}
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
TestBean bean = (TestBean)bf.getBean("test");
bean.test();
}
}
(3)创建配置文件。
XML是Spring的基础。尽管Spring一再简化配置,并且大有使用注解取代XML配置之势,但是无论如何,至少现在XML还是Spring的基础。要在Spring中开启AOP功能,,还需要在配置文件中作如下声明:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<aop:aspectj-autoproxy/>
<bean id="test" class="org.tarena.note.test.TestBean"/>
<bean class="org.tarena.note.test.AspectJTest"/>
</beans>
(4)测试。
经过以上步骤后,便可以验证Spring的AOP为我们提供的神奇效果了
public static void main(String[] args) {
ApplicationContext bf = new ClassPathXmlApplicationContext("aspectTest.xml");
TestBean bean = (TestBean)bf.getBean("test");
bean.test();
}
Spring实现了对所有类的test方法进行增强,使辅助功能可以独立于核心业务之外,方便与程序的扩展和解耦。
那么,Spring是如何实现AOP的呢?首先我们知道,SPring是否支持注解的AOP是由一个配置文件控制的,也就是
<aop:aspectj-autoproxy/>
,当在配置文件中声明了这句配置的时候,Spring就会支持注解的AOP,那么我们的分析就从这句注解开始。
动态AOP自定义标签
之前讲过Spring中的自定义注解,如果声明了自定义的注解,那么就一定会在程序中的某个地方注册了对应的解析器。我们搜索这个代码,尝试找到注册的地方,全局搜索后我们发现了在AopNamespaceHandler中对应着这样一段函数:
public void init()
{
registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}
解析是使用的spirng解析自定义注解的方式,由配置文件得知,在遇到aspectj-autoproxy注解时就会使用解析器AspectJAutoProxyBeanDefinitionParser进行解析,那么我么看看AspectJAutoProxyBeanDefinitionParse的内部实现。
2.1注册AnnotationAwareAspectJAutoProxyCreator
所有解析器,因为是对BeanDefinitionParser接口的统一实现,入口都是从parse函数开始的,AspectJAutoProxyBeanDefinitionParser的parse函数如下:
public BeanDefinition parse(Element element, ParserContext parserContext)
{
//注册AnnotationAwareAspectJAutoProxyCreator
AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
//对注解中子类的处理
extendBeanDefinition(element, parserContext);
return null;
}
其中registerAspectJAnnotationAutoProxyCreatorIfNecessary函数是我们比较关心的,也是关键逻辑的实现
public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement)
{
//注册或升级AutoProxyCreator定义beanName为org.Springframework.aop.config.internalAutoProxyCreator的BeanDefinition
BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));
//对于proxy-target-class以及expose-proxy属性的处理
useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
//注册组件并通知,便于监听器进一步处理
//其中beanDefinition的className为AnnotationAwareAspectJAutoProxyCreator
registerComponentIfNecessary(beanDefinition, parserContext);
}
在registerAspectJAnnotationAutoProxyCreatorIfNeccessary方法中主要完成了3件事情,基本上每行代码都是一个完整的逻辑。
1.注册或升级AnnotationAwareAspectJAutoProxyCreator
对于AOP的实现,基本上都是靠AnnotationAwareAspectJAutoProxyCreator去完成,它可以根据@Point注解定义的切点来自动代理相匹配的bean。但是为了配置简便,Spring使用了自定义配置来帮助我们自动注册AnnotationAwareAspectJAutoProxyCreator,其注册过程就是在这里实现的。
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source)
{
return registerOrEscalateApcAsRequired(org/springframework/aop/aspectj/annotation/AnnotationAwareAspectJAutoProxyCreator, registry, source);
}
private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source)
{
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//如果已经存在了自动代理创建器且存在的自动代理创建器与现在的不一致那么需要根据优先级来判断到底需要使用哪个
if(registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"))
{
BeanDefinition apcDefinition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
if(!cls.getName().equals(apcDefinition.getBeanClassName()))
{
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if(currentPriority < requiredPriority)
//改变bean最重要的就是改变bean所对应的className属性
apcDefinition.setBeanClassName(cls.getName());
}
return null;
} else
{
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Integer.valueOf(-2147483648));
beanDefinition.setRole(2);
registry.registerBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator", beanDefinition);
return beanDefinition;
}
}
以上代码实现了自动注册AnnotationAwareAspectJAutoProxyCreator类的功能,同时这里还涉及了一个优先级的问题,如果已经存在了自动代理创建器,而且存在的自动代理创建器与现在的不一致,那么需要根据优先级来判断到底需要使用哪个。
2.处理proxy-target-class以及expose-proxy属性
useClassProxyingIfNecessary实现了proxy-target-class属性以及expose-proxy属性的处理
private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement)
{
if(sourceElement != null)
{
//对于proxy-target-class属性的处理
boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute("proxy-target-class")).booleanValue();
if(proxyTargetClass)
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
//对于expose-proxy属性的处理
boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute("expose-proxy")).booleanValue();
if(exposeProxy)
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
//强制使用的过程其实也是一个属性设置的过程
public static void forceAutoProxyCreatorToUseClassProxying(BeanDefinitionRegistry registry)
{
if(registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"))
{
BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
definition.getPropertyValues().add("proxyTargetClass", Boolean.TRUE);
}
}
static void forceAutoProxyCreatorToExposeProxy(BeanDefinitionRegistry registry)
{
if(registry.containsBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"))
{
BeanDefinition definition = registry.getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator");
definition.getPropertyValues().add("exposeProxy", Boolean.TRUE);
}
}
- proxy-target-class:SpringAOP部分使用JDK动态代理或者CGLIB来为目标对象创建代理(建议尽量使用JDK的动态代理),如果被代理的目标对象实现了至少一个接口,则会使用JDK动态代理,所有该目标类型实现的接口都将代理,若该目标对象没有实现任何接口,则创建一个CGLIB代理,如果你希望强制使用CGLIB代理,(例如看望代理目标对象的所有方法,而不只是实现自接口的方法)那也可以。但是需要考虑以下两个问题。
无法通知(advise)Final方法,因为它们不能被重写。
你需要将CGLIB二进制发行包放在classpath下面。
- JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
- CGLIB:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象时针对目标扩展的子类。CGLIB是高效的代码生成包,底层是依靠ASM(开通的Java字节码编辑类库)操作字节码实现的,性能比JDK强。