Spring AOP是Spring的一项很强大的功能。它的“面向切面编程”的思想是面向对象编程OOP的补充和完善。AOP允许我们从横向的角度关注我们的程序,使得程序在安全处理,日志,性能采集等代码能在一个地方统一进行处理,而不用分布在各个方法和调用点。
这些代码集合在一起就称为“切面”。
连接点:被拦截到的点,Spring只支持方法类型的连接点,所以在Spring中,连接点就是指被拦截到的方法。
通知:拦截到连接点之后的要通知的代码。通知分为前置、后置、异常、最终、环绕五种。
此处省略了一些定义,个人感觉这些定义很绕,其实是一件很简单的事:
哪些方法需要被处理?
在方法的什么地方处理?
做哪些处理?
动态代理和Spring AOP有着很大的联系。可以说Spring的AOP机制就是建立在动态代理的基础上的。
之前已经描述过动态代理,这里把重点放在Spring AOP的实现上。
来看一个例子:
创建一个接口,并实现:
public interface IConnect {
void connect(String name);
}
public class ConnectImpl implements IConnect{
@Override
public void connect(String name) {
System.out.println("创建连接---"+name);
}
}
如果需要在方法执行之前调用某个逻辑,那么可以实现Spring提供的接口:MethodBeforeAdvice
public class BeforeConnect implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String name = (String)args[0];
System.out.println("连接前处理...");
}
}
下面是测试类:
public class BeforeConnectTest {
public static void main(String[] args) {
IConnect target = new ConnectImpl();
BeforeConnect advice = new BeforeConnect();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAdvice(advice);
IConnect iConnectProxy = (IConnect) proxyFactory.getProxy();
iConnectProxy.connect("Irving");
}
}
ProxyFactory:用来创建编程式的Spring AOP应用。
创建代理工厂,设置代理对象;并且为代理目标添加上前置通知。
生成代理实例,然后调用代理的方法。
将关注点放在ProxyFactory这个代理工厂上,它可以生成一个代理对象。
创建Proxy的方法,最后调用到了JdkDynamicAopProxy的getProxy(ClassLoader classLoader)方法:
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
可以看到最后,调用到的,是JDK的Proxy类的创建代理的方式。
同样,我们也可以通过Spring的配置方式来实现这样的功能。
<?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 id="target" class="ConnectImpl" />
<bean id="beforeConnectAdvice" class="BeforeConnect"/>
<!--定义Spring代理工厂 -->
<bean id="connectProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetName" value="target" />
<property name="proxyInterfaces" value="IConnect"/>
<property name="interceptorNames" value="beforeConnectAdvice" />
</bean>
</beans>
public class TestAOP {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aopConfig.xml");
IConnect connect = (IConnect) context.getBean("connectProxy");
connect.connect("IS");
}
}
运行结果如下:
连接前处理…
创建连接—IS
ProxyFactoryBean:用来创建声明式的Spring AOP应用。
由上面的配置可以发现,这种方式其实是将Proxy代理生成的方式挪到了XML的配置中。
和上面的配置方式相似,还有另外一种配置方式,XML的配置如下:
<?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 id="target" class="ConnectImpl" />
<bean id="beforeConnectAdvice" class="BeforeConnect"/>
<!--定义Spring代理处理器 -->
<bean id="connectProxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<value>target</value>
</property>
<property name="interceptorNames">
<value>beforeConnectAdvice</value>
</property>
</bean>
</beans>
使用方式如下:
public class TestAOP2 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aopConfig2.xml");
IConnect connect = (IConnect)context.getBean("target") ;
connect.connect("IS2");
}
}
输出如下:
连接前处理…
创建连接—IS2
这种方式,直接将target实现成了代理对象,而不是返回动态生成的代理。
回头想想前面的两种方式,很直观明了地将切面所需要的几个要素都包含在了配置里面:
哪些方法?在什么地方调用?做哪些操作?
这种配置很清楚很明了,但是也有一些不好的地方:
第一点是关于切面:需要实现特定的接口,MethodBeforeAdvice,一个普通的类是无法被当做切面的。
第二就是我们把连接点的声明放在了切面本身,由切面来决定它是before还是after或者是其他地方来调用切面方法。如果需要在一个方法之前之后都调用某个切面方法,那么需要将这个方法分别写在MethodBeforeAdvice和MethodAfterAdvice接口实现中。
总的来说,这两种配置虽然很直接,但是还不够灵活。
除此之外,还有一种比较常见的实现方式:通过aop:config配置。
可以通过配置pointcut和aspect来实现;可以通过配置pointcut和advisor来实现。
这两种方式都允许以表达式的形式来定义切点。
先看第一种方式:
接口和实现同上例。
看下切面方法:
public class ConnectAspect {
public void beforeConn(){
System.out.println("This is before connect-------");
}
public void afterConn(){
System.out.println("This is after connect-------");
}
}
这是一个很平常的Java类。
配置文件中引入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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="target" class="ConnectImpl" />
<bean id="connAspect" class="ConnectAspect"/>
<aop:config>
<aop:pointcut id="connectPointcut" expression="execution(* ConnectImpl.*(..))"/>
<aop:aspect ref="connAspect">
<aop:before pointcut-ref="connectPointcut" method="beforeConn"/>
<aop:after pointcut-ref="connectPointcut" method="afterConn"/>
</aop:aspect>
</aop:config>
</beans>
首先定义被代理的目标类,然后定义切面,然后将切面、切点结合起来。很容易理解的是。分别在方法的before和after位置进行切面方法调用。
至于在哪些方法调用,是由expression表达式决定的,
在执行任意包的ConnectImpl方法的任意方法时调用。
这种方法,可以将任意的方法变成切面方法,而且可以动态指定方法的过滤表达式。不过这种方法将类的方法写死在了配置文件中,这样不利于以后的修改和维护,而且这种方式也无法获取到运行时的方法信息。
还有一种方法是:
通过配置pointcut和advisor来实现
这种方式下,切面需要实现接口MethodInterceptor。
public class ConnectAdvice implements MethodInterceptor{
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("ConnectAdvice----before");
Object obj = invocation.proceed();
System.out.println("ConnectAdvice----before");
return obj;
}
}
实现invoke方法,这个方法稍后分析,先来看配置文件:
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<bean id="target" class="ConnectImpl" />
<bean id="connectAdvice" class="ConnectAdvice"/>
<aop:config>
<aop:pointcut id="connectPointcut" expression="execution(* ConnectImpl.*(..))"/>
<aop:advisor advice-ref="connectAdvice" pointcut-ref="connectPointcut"></aop:advisor>
</aop:config>
</beans>
测试类:
public class TestAOP4 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:aopConfig4.xml");
IConnect connect = (IConnect)context.getBean("target") ;
connect.connect("IS4");
}
}
输出结果:
ConnectAdvice—-before
创建连接—IS4
ConnectAdvice—-before
对于实现接口的目标类,Spring AOP使用JdkProxy来生成代理,对于其他的,Spring使用Cglib生成代理。
本例子使用的是接口类型的目标类。
而我们此处并没有显式地指定切面实现InvocationHandler,事实上,ConnectAdvice这个类和InvocationHandler没有任何的父子关系。那这个切面是如何应用起来的呢?
代理对象关系紧密的是InvocationHandler对象,调用代理对象的方法,其实就是调用target对象的方法,调用InvocationHandler对象的invoke方法。
JdkDynamicAopProxy自己实现了InvocationHandler,并且构造方法:
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
传入了一个名为AdvisedSupport的对象,该对象包括了AOP的配置。其中就包括了连接点的配置(包装成ExposeInvocationInterceptor),advisor的配置(包装成DefaultBeanFactoryPointcutAdvisor)。
查看其invoke方法的实现,
// Get the interception chain for this method.
//获取当前方法的 interception列表
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.
//如果没有interception列表,就直接调用目标方法
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.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
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.
//调用interceptor链
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && 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;
}
invocation的proceed方法,将每个advisor中的方法分别依次调用。
可见,虽然没有直接定义InvocationHandler,但是内部使用的还是动态代理的方式来实现面向切面编程。