Spring AOP
Spring中所有的通知都是java类的形式编写,定义在什么地方应用通用的切入点通常编写在spring的配置文件中。需明确的几个概念:
通知(Advice):用于告知系统将有哪些新的行为。
切入点(Pointcut):定义了通知应该在应用到那些连接点。
目标对象(Target):被通知的对象。
代理(Proxy):将通知应用到目标对象后创建的对象。
代理Bean只有在第一次被应用系统需要的时候才被创建,如果使用的是ApplicationContext,代理对象在BeanFactory 载入所有Bean的时候创建。
Spring有2种代理创建方式:
1.如果目标对象实现了一个或多个接口暴露的方法,Spring将使用JDK的java.lang.reflect.Proxy类创建代理,这个类让Spring动态产生一个新的类,它实现了所有的接口,织入了通知,并且代理对目标对象的所有请求。
2.如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类,在创建子类的时候,Spring将通过织入,并且将对目标对象的调 用委托给这个子类,使用这种方式应注意两个要点:
(1)对接口创建代理优于对类创建代理,因为这样会产生更加松耦合的系统,对类生成代理是让遗留系统或者 无法实现接口的第三方类库同样的可以得到通知,这种方式是备用方案
(2)标记为final的方法不能被通知,Spring是为目标类产生子类,任何需要被 通知的方法都会被复写,将通知织入,final方法是不可能做到的。
通知有以下几种:
1.Around(周围同志)---org.aopllliance.intercept.MethodInterceptor拦截对目标对象方法调用
2.Before(前通知)---org.springframework.aop.BeforeAdvice在目标方法被调用前调用
3.After(返回后通知)---org.springframework.aop.AfterRetruningAdvice在方法被调用之后调用
4.Throws(抛出后通知)---org.springframework.aop.ThrowsAdvice在目标方法抛出异常时调用
5.Introduction(引入)--- org.springframework.aop.IntroductionInterceptor
示例:
Public class AudienceAdvice implements MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice
{
//前通知要求实现的方法
public void before(Method method,Object[] args,Object target) throws Throwable
{
}
//返回后通知要求实现的方法,Object returnValue表示被调用方法的返回值
public void afterReturning(Object returnValue,Method method,Object[] args,Object target) throws Throwable
{
}
//抛出后通知不需要实现任何方法,它只是一个标记接口,告诉spring相应的通知要求处理被抛出的异常,除最后一个参数外,其他参数都是可选的
public void afterThrowing(Method method,Object[] args,Object target,NullPointerException e) throws Throwable
{
//当发生NullPointerException 时,记录日志
LOGGER.error("NPE thrown from "+method.getName());
}
}
其中的Method method参数,表示要使用这个通知的方法,第二个参数Object[] args是方法被调用时要传递的参数。最后一个是方法调用的目标也就是被调用方法所在的对象。
周围通知相当于前通知、后通知、抛出后通知的结合。
Public class AudienceAdvice implements MethodInterceptor
{
Public Object invoke(MethodInvocation invocation) throws Throwable
{
Try
{
.............................. //在方法调用之前
Object returnValue=invocation.proceed(); 调用目标方法
............................. //在方法调用之后
}
catch(PerformanceException e)
{
............................. /在出现异常之后
}
}
}
切点
1.正则切点
切点的作用主要在于选择需要使用通知的方法,比如下面的正则表达式切点:
<bean id="performancePointcut" class="org.springframework.aop.support.JbkRegexpMethodPointcut"> <property name="pattern" value=".*perform" /> </bean> // Pattern属性用于制定方法匹配所使用的切点模板,本例中被设置为一个正则表达式,它应该匹配任何类里名为perform的方法。
定义切点后,把切点与通知关联
<bean id="audienceAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor"> <property name="advice" value="audienceAdvice" /> <property name="pointcut" value="performancePointcut" /> </bean> //DefaultPointcutAdvisor是一个通知者类,它只是把通知关联到一个切点。
也可以使用RegexpMethodPointcutAdvisor是一个特殊的通知者类,可以在一个Bean里面定义切点和通知者
<bean id="audienceAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="advice" value="audienceAdvice" /> <property name="pattern" value=".*perform" /> </bean>
2.定义AspectJ切点
正则表达式虽然可以作为切点定义语言来使用,但是它并不是针对切点而创建的,其主要用途还是文本解析。AspectJ里定义切点的方法就可以看出AspectJ的切点语言是一种真正的切点表达式语言。
<bean id="performancePointcut" class="org.springframework.aop.aspectj.AspectJExpressionPointcut"> <property name="expression" value="execution(* Performer+.perform(..))" /> </bean>
可以使用DefaultPointcutAdvisor把它和通知关联起来,就像前面一样。同样,也可以设置一个特殊的通知者
<bean id="audienceAdvisor" class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <property name="advice" value="audienceAdvice" /> <property name="expression" value="execution(* *.perform(..))" /> </bean>
execution(* *.perform(..))
execution表示执行方法时,* *表示任意返回类型,.perform表示perform方法,(..)表示任意参数设置。
使用ProxyFactoryBean
<bean id="duke" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>genericdaotest.dao.PersonDao</value>
</property>
<property name="target">
<bean parent="abstractDaoTarget">
<constructor-arg>
<value>genericdaotest.domain.Person</value>
</constructor-arg>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>audienceAdvisor</value>
</list>
</property>
</bean>
Spring里面的ProxyFactoryBean是个工厂Bean,用于生成一个代理,把一个或者多个拦截者(和通知者)应用到Bean,有target、interceptorNames、proxyInterfaces三个属性,后两个都是数组属性target表示要代理的对象。
interceptorNames属性告诉ProxyFactoryBean哪个通知者要应用于被代理的
beanproxyInterfaces告诉ProxyFactoryBean代理应该实现哪个接口。本例中代理的是genericdaotest.dao.PersonDao接口。
target告诉ProxyFactoryBean,哪个Bean是代理。
自动代理
使用ProxyFactoryBean来显式的创建AOP代理在很多场合下,会使编写配置文件的工作量大大增加;由于要从ProxyFactoryBean获得代理对象,也会使应用和Spring之间的耦合度增加。下面介绍使用Spring提供的自动代理机制来解决这类问题。
1.使用BeanNameAutoProxyCreator
Spring提供的BeanNameAutoProxyCreator类允许我们通过Bean的name属性来指定代理的Bean。它暴露了java.lang.String[]类型的beanNames和 interceptorNames属性。beanNames可以指定被代理的Bean名字列表,支持“*”通配符,例如“*DAO”表示所有名字以“DAO”结尾的Bean。interceptorNames指定通知(Advice)列表,或者通知者(Advisor)列表。
下面通过一个例程来演示如何使用BeanNameAutoProxyCreator。在例子中,有两个Bean:TestBeanA和BeanB,并在TestMain类中的main方法中调用其MyMethod()方法。自动代理将会在方法调用前自动的执行配置的前置通知,输出提示信息。
新建一个名字为AOP_Test4.10的工程,添加Spring的IoC和AOP库后,新建一aop.test包,再分别创建两个类TestBeanA和BeanB,添加MyMethod()方法,代码如下:
package aop.test;
public class TestBeanA
{
public void MyMethod()
{
System.out.println(this.getClass().getName() + ".MyMethod() is run!");
}
}
package aop.test;
public class BeanB
{
public void MyMethod()
{
System.out.println(this.getClass().getName() + ".MyMethod() is run!");
}
}
再创建前置通知类BeforeAdvice:
package aop.test;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class BeforeAdvice implements MethodBeforeAdvice
{
public void before(Method method, Object[] args, Object target) throws Throwable
{
System.out.println(method.getName() + "(),将要运行!");
}
}
最后创建含有main方法的测试类TestMain:
package aop.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestMain
{
public static void main(String[] args)
{
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
TestBeanA beanA = (TestBeanA)ac.getBean("TestBeanA");
beanA.MyMethod();
BeanB beanB = (BeanB)ac.getBean("BeanB");
beanB.MyMethod();
}
}
在配置文件中配置Bean和自动代理Bean,完成后代码如下:
<?xml version="1.0" encoding="UTF-8"?> <beans …………> <bean id="TestBeanA" class="aop.test.TestBeanA"/> <bean id="BeanB" class="aop.test.BeanB"/> <bean id="BeforeAdvice" class="aop.test.BeforeAdvice"></bean> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <property name="beanNames"> <list> <value>Test*</value> </list> </property> <property name="interceptorNames"> <list> <value>BeforeAdvice</value> </list> </property> </bean> </beans>
运行主类,输出结果如下:
可以看到,在主类TestMain中,我们是直接从Spring IoC容器中获取收管Bean而不是像以前那样从ProxyFactoryBean中获取代理,但是我们的前置通知BeforeAdvice仍然在TestBeanA对象的MyMethod()方法执行前被触发,这说明我们的自动代理正在工作。
2.使用DefaultAdvisorAutoProxyCreator
DefaultAdvisorAutoProxyCreator允许我们只需定义相应的Advisor通知者,就可以完成自动代理。配置好DefaultAdvisorAutoProxyCreator受管Bean后,它会自动查找配置文件中定义的Advisor,并将它们作用于所有的Bean。
修改例程4.10的配置文件,使用DefaultAdvisorAutoProxyCreator来完成自动代理。完成后配置文件代码如下(本例完整工程代码见例程4.11):
<?xml version="1.0" encoding="UTF-8"?>
<beans ……>
<bean id="TestBeanA" class="aop.test.TestBeanA" />
<bean id="BeanB" class="aop.test.BeanB" />
<bean id="BeforeAdvice" class="aop.test.BeforeAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<bean class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="BeforeAdvice" />
<property name="mappedNames">
<list>
<value>*Method*</value>
</list>
</property>
</bean>
</beans>
运行主类输出结果如下: