面向切面编程(二)之Spring和Guice
AOP是依赖注入框架的一个完善,本文将会对Spring和Guice的AOP部分进行详细分析。
Spring AOP
我们直接看看AOP的使用示例。
- 写一个服务接口和实现
public interface PersonService {
String get();
}
public class PersonServiceImpl implements PersonService {
@Log
@Override
public String get() {
System.out.println("execute get method");
return "Sayi";
}
}
@Log表明这个方法会被切入,使用注解标识了切入点,可以不选择注解切入,直接切入某个类的某个方法,具体配置参见Spring官方文档。
package com.deepoove.diexample.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
}
- 写切面拦截方法,切面类需要用注解@Aspect标识。
package com.deepoove.diexample.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
public class LogAspect {
@Around("@annotation(com.deepoove.diexample.annotation.Log)")
public Object doLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(System.currentTimeMillis() + " Log Aspect before");
Object obj = pjp.proceed();
System.out.println(System.currentTimeMillis() + " Log Aspect after");
return obj;
}
}
@Around方法会包裹这个方法的执行,同时还提供了@Before方法执行前的注解和@After方法执行后的注解。
3. 使用XML打开切面代理(<aop:aspectj-autoproxy />)、配置服务和切面拦截器Bean
<?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:mvc="http://www.springframework.org/schema/mvc" 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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<aop:aspectj-autoproxy />
<bean id="personService" class="com.deepoove.diexample.service.PersonServiceImpl">
</bean>
<bean id="myAspect" class="com.deepoove.diexample.aop.LogAspect">
</bean>
</beans>
一切完毕,可以写个单元测试检验下:
@Test
public void testXMLAOPConifg() {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("person.xml");
PersonService personService = context.getBean(PersonService.class);
Assert.assertEquals(personService.get(), "Sayi");
context.close();
}
// 输出结果为:
1533176773600 Log Aspect before
execute get method
1533176773600 Log Aspect after
AOP源码解析
在《依赖注入(二)Spring Dependency injection》文章中详细说明过Bean的初始化过程,我们知道AOP其实是Spring的一个扩展,而BeanPostProcessor的设计为实现这个扩展提供了便捷。关于如何扫描XML配置,如何解析@Around注解生成拦截器Advice这里不作介绍,我们直接看为Bean生成代理的源码AbstractAutoProxyCreator,处理链为先调用postProcessAfterInitialization方法再调用wrapIfNecessary方法,wrapIfNecessary方法核心代码如下:
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
我们对上面代码做个简单解释:
- getAdvicesAndAdvisorsForBean就是获取当前Bean对应的拦截器,拦截器包含了切入点和具体拦截的方法,返回值类似为
InstantiationModelAwarePointcutAdvisor: expression [@annotation(com.deepoove.diexample.annotation.Log)]; advice method [public java.lang.Object com.deepoove.diexample.aop.LogAspect.doLog(org.aspectj.lang.ProceedingJoinPoint) throws java.lang.Throwable]; perClauseKind=SINGLETON
- 如果拦截器不为NULL,则会创建代理
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
@Nullable Object[] specificInterceptors, TargetSource targetSource) {
if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
}
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this);
if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
}
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
proxyFactory.addAdvisors(advisors);
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(getProxyClassLoader());
}
可以从源码看出来,生成代理的核心类为ProxyFactory,接下来会详细阐述它的细节。
ProxyFatory编程
Spring为代理模式提供了一个工厂类ProxyFatory,支持对象的DK动态代理和Cglib代理。如果目标对象至少实现了一个接口,那么优先使用JDK动态代理所有接口,否则会使用Cglib,如果需要强制使用Cglib,可以通过配置实现:
<aop:config proxy-target-class="true" />
正如上篇文章说过,cglib无法对final进行代理。
我们先来看看如何使用ProxyFatory编程:
@Test
public void testAOP() {
ProxyFactory factory = new ProxyFactory(new MyService());
factory.addAdvice(new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("AOP Spring: " + invocation.getMethod());
return invocation.proceed();
}
});
MyService tb = (MyService) factory.getProxy();
tb.toString();
}
ProxyFactory构造器支持传入Object对象或者接口Class,通过addAdvice方法增加拦截器org.aopalliance.intercept.Interceptor的实现,AopProxy接口定义了获取代理类的方法,获取AopProxy实例的源码在DefaultAopProxyFactory中,如下:
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
AopProxy有两个实现,分别是JdkDynamicAopProxy和CglibAopProxy,我们可以实现接口,实现自己的代理策略。
ObjenesisCglibAopProxy继承了CglibAopProxy,使用objenesis技术实例化对象,objenesis技术参见官网http://objenesis.org/
深入JdkDynamicAopProxy的实现,我们发现Spring也是通过实现AOP盟约的MethodInvocation,完成对拦截器链的调用,具体实现类是ReflectiveMethodInvocation,构造完ReflectiveMethodInvocation后,通过其核心方法递归遍历拦截器:
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}
Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}
通过源码我们发现,所有JDK动态代理的对象除了代理对象接口外,还实现了接口: SpringProxy.class、Advised.class和DecoratingProxy.class,我们可以利用这一特性,获得更多的代理信息。
ProxyFatory应用之Spring-remoting
RPC框架客户端依赖服务接口,调用远程服务实现,HttpInvoker是Spring的一个基于HTTP协议和Java序列化的远程调用框架。参见《写一个极简的RPC和Hessian的设计 》
Spring Http Invoker客户端的实现原理是基于动态代理,把接口的调用代理至HTTP服务,同时利用了FactoryBean的扩展技术。
public class HttpInvokerProxyFactoryBean extends HttpInvokerClientInterceptor implements FactoryBean<Object> {
@Nullable
private Object serviceProxy;
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
Class<?> ifc = getServiceInterface();
Assert.notNull(ifc, "Property 'serviceInterface' is required");
this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
}
@Override
@Nullable
public Object getObject() {
return this.serviceProxy;
}
@Override
public Class<?> getObjectType() {
return getServiceInterface();
}
@Override
public boolean isSingleton() {
return true;
}
}
- getObject()方法暴露具体代理对象Bean
- afterPropertiesSet方法通过ProxyFactory初始化具体代理对象
- HttpInvokerClientInterceptor实现了MethodInterceptor拦截器的invoke方法,执行远程调用
AOP应用
Spring中AOP的应用还有很多,比如@Transcation、@Cache等,后面有时间会作为一个主题单独分析。
Guice AOP
Guice AOP的设计相对比较简单:切入点匹配和拦截器绑定。 我们先看一个示例,首先在Module中指定切入点和拦截器(这里采用了官方文档的代码,Matchers类用来生成匹配逻辑或者不匹配逻辑),所有使用了注解NotOnWeekends的方法都将会被WeekendBlocker拦截。
public class NotOnWeekendsModule extends AbstractModule {
protected void configure() {
bindInterceptor(Matchers.any(), Matchers.annotatedWith(NotOnWeekends.class),
new WeekendBlocker());
}
}
```java
接着就可以实现拦截器了,实现AOP盟约的接口MethodInterceptor:
```java
public class WeekendBlocker implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
Calendar today = new GregorianCalendar();
if (today.getDisplayName(DAY_OF_WEEK, LONG, ENGLISH).startsWith("S")) {
throw new IllegalStateException(
invocation.getMethod().getName() + " not allowed on weekends!");
}
return invocation.proceed();
}
}
如果有某个Bean的方法加上了注解NotOnWeekends,那么在周末执行的时候就会抛错。
源码解析
我们直接看ConstructorBindingImpl代码来分析如何实现Bean的生成,Guice是在构造Bean的时候直接生成代理的,com.google.inject.internal.ConstructorBindingImpl.initialize方法初始化了ConstructorInjector对象,这个对象包含了ConstructionProxy对象,而ConstructionProxy对象是由内部隐藏的一个代理工厂com.google.inject.internal.ProxyFactory<T>类生成的。
public ConstructionProxy<T> create() throws ErrorsException {
if (interceptors.isEmpty()) {
return new DefaultConstructionProxyFactory<T>(injectionPoint).create();
}
@SuppressWarnings("unchecked")
Class<? extends Callback>[] callbackTypes = new Class[callbacks.length];
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] == net.sf.cglib.proxy.NoOp.INSTANCE) {
callbackTypes[i] = net.sf.cglib.proxy.NoOp.class;
} else {
callbackTypes[i] = net.sf.cglib.proxy.MethodInterceptor.class;
}
}
// Create the proxied class. We're careful to ensure that all enhancer state is not-specific
// to this injector. Otherwise, the proxies for each injector will waste PermGen memory
try {
Enhancer enhancer = BytecodeGen.newEnhancer(declaringClass, visibility);
enhancer.setCallbackFilter(new IndicesCallbackFilter(methods));
enhancer.setCallbackTypes(callbackTypes);
return new ProxyConstructor<T>(enhancer, injectionPoint, callbacks, interceptors);
} catch (Throwable e) {
throw new Errors().errorEnhancingClass(declaringClass, e).toException();
}
}
ConstructionProxy有三个实现FastClassProxy、ReflectiveProxy和ProxyConstructor,在有拦截器时,采用ProxyConstructor实例化对象。
//com.google.inject.internal.ProxyFactory.ProxyConstructor.newInstance(Object...)
public T newInstance(Object... arguments) throws InvocationTargetException {
Enhancer.registerCallbacks(enhanced, callbacks);
try {
return (T) fastClass.newInstance(constructorIndex, arguments);
} finally {
Enhancer.registerCallbacks(enhanced, null);
}
}
总结
Spring很好的利用扩展机制实现了AOP,Guice采用了简单优雅的方式使用AOP。
在研究实现代理模式的源码,我们发现基本上所有的框架都会有一个类ProxyFactory,它隐藏了JDK动态代理和Cglib动态代理的实现,对外提供一个代理对象。