一、前言
有时我们需要通过使用Aop的方式对我们的程序进行切面监控,比如Service执行时间,记录log等。常用的两种方式一个是写execution表达式,还一种是自定义注解。但是在使用的过程中,有时我们会发现原本没问题的Service,现在使用里面注入的bean对象变为null了,针对这种情况我们分析一下。
二、情景再现
自定义注解Monitor
public @interface Monitor {
}
自定义Advice
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true,exposeProxy = true)
public class ServiceAdvice {
@Pointcut("@annotation(aoptest.Monitor)")
public void annotation() {
}
@Before("annotation()")
public void beforeMethodAdviceA(JoinPoint joinPoint){
System.out.println("beforeMethodAdviceA");
}
}
ServiceA,在ServiceA中注入ServiceB,并在a()方法和c()方法上加上注解。
@Component
public class ServiceA extends SuperA implements InterfaceA{
@Autowired
private ServiceB serviceB;
@Monitor
public void a(){
System.out.println("invoked method a");
System.out.println(serviceB);
System.out.println("---------------------");
((ServiceA) AopContext.currentProxy()).c();
}
@Monitor
private void c() {
System.out.println(serviceB);
System.out.println("invoked method c");
}
}
Main方法。
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("aoptest");
ctx.refresh();
ServiceA serviceA = (ServiceA) ctx.getBean("serviceA");
serviceA.a();
}
}
执行结果:
可以看到,在方法a中,serviceB是有值的,在调用方法c就为null了。这里((ServiceA) AopContext.currentProxy()).c()这样调用是为了c方法也保证有切面的功能,如果我们在a方法中直接this.c(),那么c方法是不会执行增强的方法。那为什么两个方法添加了同样的注解,ServiceA中注入的Bean对象ServiceB一个有值,一个就为null了呢。
先说结论:
在执行Spring Aop中,对方法进行增强时,不论时执行execution表达式,还是自定义注解,都不能对private方法进行。
比如我们对一个service的private方法添加@Transaction注解,你会发现不仅失效,Service里注入的Bean也为null了。
三、源码解析
不论是@Transaction还是自定义注解,其实本质都是实现Spring Aop的自定义增强功能。Spring Aop匹配的源码解析可以看这篇文章。
这里我们主要看Spring Aop在找到应该被增强的对象后,创建的代理类是什么样。Spring创建代理类是通过AbstractAutoProxyCreator的postProcessAfterInitialization方法,在Bean对象创建完成并执行完populateBean注入所需的Bean对象后,在initializeBean方法中执行。
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (this.earlyProxyReferences.remove(cacheKey) != bean) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// 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对象是否需要进行增强,现在我们直接看createProxy方法,看看Spring是如何创建。
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());
}
createProxy方法其实就是创建一个proxyFactory,然后进行一些set,将需要的代理Bean和增强的方法先设置好。我们直接看getProxy方法。
getProxy方法有两种代理方式,Spring默认使用cglib方式,jdk方式同理。
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource());
}
try {
Class<?> rootClass = this.advised.getTargetClass();
Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");
Class<?> proxySuperClass = rootClass;
if (ClassUtils.isCglibProxyClass(rootClass)) {
proxySuperClass = rootClass.getSuperclass();
Class<?>[] additionalInterfaces = rootClass.getInterfaces();
for (Class<?> additionalInterface : additionalInterfaces) {
this.advised.addInterface(additionalInterface);
}
}
// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass, classLoader);
// Configure CGLIB Enhancer...
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
enhancer.setClassLoader(classLoader);
if (classLoader instanceof SmartClassLoader &&
((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
enhancer.setUseCache(false);
}
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
types[x] = callbacks[x].getClass();
}
// fixedInterceptorMap only populated at this point, after getCallbacks call above
enhancer.setCallbackFilter(new ProxyCallbackFilter(
this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);
// Generate the proxy class and create a proxy instance.
return createProxyClassAndInstance(enhancer, callbacks);
}
}
我们知道cglib代理主要是通过继承子类方式,这里设置了callbacks和setCallbackFilter来确认不同方法走哪种。
比如getCallbacks方法中有一段
Callback[] mainCallbacks = new Callback[] {
aopInterceptor, // for normal advice
targetInterceptor, // invoke target without considering advice, if optimized
new SerializableNoOp(), // no override for methods mapped to this
targetDispatcher, this.advisedDispatcher,
new EqualsInterceptor(this.advised),
new HashCodeInterceptor(this.advised)
};
ProxyCallbackFilter类的accept方法判断执行走哪个。
public int accept(Method method) {
if (AopUtils.isFinalizeMethod(method)) {
logger.trace("Found finalize() method - using NO_OVERRIDE");
return NO_OVERRIDE;
}
if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
if (logger.isTraceEnabled()) {
logger.trace("Method is declared on Advised interface: " + method);
}
return DISPATCH_ADVISED;
}
// We must always proxy equals, to direct calls to this.
if (AopUtils.isEqualsMethod(method)) {
if (logger.isTraceEnabled()) {
logger.trace("Found 'equals' method: " + method);
}
return INVOKE_EQUALS;
}
// We must always calculate hashCode based on the proxy.
if (AopUtils.isHashCodeMethod(method)) {
if (logger.isTraceEnabled()) {
logger.trace("Found 'hashCode' method: " + method);
}
return INVOKE_HASHCODE;
}
Class<?> targetClass = this.advised.getTargetClass();
// Proxy is not yet available, but that shouldn't matter.
List<?> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
boolean haveAdvice = !chain.isEmpty();
boolean exposeProxy = this.advised.isExposeProxy();
boolean isStatic = this.advised.getTargetSource().isStatic();
boolean isFrozen = this.advised.isFrozen();
if (haveAdvice || !isFrozen) {
// If exposing the proxy, then AOP_PROXY must be used.
if (exposeProxy) {
if (logger.isTraceEnabled()) {
logger.trace("Must expose proxy on advised method: " + method);
}
return AOP_PROXY;
}
String key = method.toString();
// Check to see if we have fixed interceptor to serve this method.
// Else use the AOP_PROXY.
if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {
if (logger.isTraceEnabled()) {
logger.trace("Method has advice and optimizations are enabled: " + method);
}
// We know that we are optimizing so we can use the FixedStaticChainInterceptors.
int index = this.fixedInterceptorMap.get(key);
return (index + this.fixedInterceptorOffset);
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Unable to apply any optimizations to advised method: " + method);
}
return AOP_PROXY;
}
}
else {
if (exposeProxy || !isStatic) {
return INVOKE_TARGET;
}
Class<?> returnType = method.getReturnType();
if (targetClass != null && returnType.isAssignableFrom(targetClass)) {
if (logger.isTraceEnabled()) {
logger.trace("Method return type is assignable from target type and " +
"may therefore return 'this' - using INVOKE_TARGET: " + method);
}
return INVOKE_TARGET;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Method return type ensures 'this' cannot be returned - " +
"using DISPATCH_TARGET: " + method);
}
return DISPATCH_TARGET;
}
}
}
设置完成后,执行createProxyClassAndInstance方法进行动态代理类的创建。
protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
enhancer.setInterceptDuringConstruction(false);
enhancer.setCallbacks(callbacks);
return (this.constructorArgs != null && this.constructorArgTypes != null ?
enhancer.create(this.constructorArgTypes, this.constructorArgs) :
enhancer.create());
}
最后执行Enhancer的create方法。create方法执行时最后会调用Enhancer的generateClass方法。
public void generateClass(ClassVisitor v) throws Exception {
Class sc = (superclass == null) ? Object.class : superclass;
if (TypeUtils.isFinal(sc.getModifiers()))
throw new IllegalArgumentException("Cannot subclass final class " + sc.getName());
List constructors = new ArrayList(Arrays.asList(sc.getDeclaredConstructors()));
filterConstructors(sc, constructors);
// Order is very important: must add superclass, then
// its superclass chain, then each interface and
// its superinterfaces.
List actualMethods = new ArrayList();
List interfaceMethods = new ArrayList();
final Set forcePublic = new HashSet();
getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);
.................
}
进入getMethods方法。
private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic) {
ReflectUtils.addAllMethods(superclass, methods);
List target = (interfaceMethods != null) ? interfaceMethods : methods;
if (interfaces != null) {
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i] != Factory.class) {
ReflectUtils.addAllMethods(interfaces[i], target);
}
}
}
if (interfaceMethods != null) {
if (forcePublic != null) {
forcePublic.addAll(MethodWrapper.createSet(interfaceMethods));
}
methods.addAll(interfaceMethods);
}
CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC));
CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));
CollectionUtils.filter(methods, new DuplicatesPredicate());
CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL));
}
可以看到是先将所有的方法都先找到,但是在执行CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));
public class VisibilityPredicate implements Predicate {
private boolean protectedOk;
private String pkg;
private boolean samePackageOk;
public VisibilityPredicate(Class source, boolean protectedOk) {
this.protectedOk = protectedOk;
this.samePackageOk = source.getClassLoader() != null;
this.pkg = TypeUtils.getPackageName(Type.getType(source));
}
public boolean evaluate(Object arg) {
Member member = (Member)arg;
int mod = member.getModifiers();
if (Modifier.isPrivate(mod)) {
return false;
} else if (Modifier.isPublic(mod)) {
return true;
} else if (Modifier.isProtected(mod) && this.protectedOk) {
return true;
} else {
return this.samePackageOk && this.pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass())));
}
}
将该Bean的private私有方法过滤掉了。之后创建一个代理类子类。
既示例中生成的就是 代理类 ServiceA@EnhancerCglib,他的父类是ServiceA。
那么既然是实例化的动态代理子类,里面的所有注入的Bean对象都应该为null,为什么public的方法注入的Bean对象确有值呢。
回到CglibAopProxy类的DynamicAdvisedInterceptor类中的intercept方法
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
Object target = null;
TargetSource targetSource = this.advised.getTargetSource();
try {
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// Get as late as possible to minimize the time we "own" the target, in case it comes from a pool...
target = targetSource.getTarget();
Class<?> targetClass = (target != null ? target.getClass() : null);
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
// 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.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
}
else {
// We need to create a method invocation...
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
}
retVal = processReturnType(proxy, target, method, retVal);
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
我们可以看到,如果不是私有的方法,符合匹配增强的方法中,target = targetSource.getTarget();这行代码获取的是Spring 生成的Bean对象,该对象在执行postProcessAfterInitialization前已经完成自身所需Bean对象的注入。之后在执行增强方法和自己的方法时,传入的是Spring生成的Bean对象,而非通过Cglib创建的代理对象。
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
也就是说在执行示例中ServiceA的a()方法时,调用的生成动态代理类serviceA@cglib类中的targetBean,即正常的Spring生成的ServiceA类。
在调用ServiceA的c()方法时,因为生成的代理类serviceA@cglib类中没有c这个私有方法,所以代理类直接调用父类的c方法,但是代理类中的需要注入的Bean又没有,所以在执行c方法时就报null了。
我们将之前的代码打印出hashcode看一下。
@Component
public class ServiceA extends SuperA implements InterfaceA{
@Autowired
private ServiceB serviceB;
@Monitor
public void a(){
System.out.println("invoked method a");
System.out.println(serviceB);
System.out.println("hashcode:"+this.hashCode());
System.out.println( ((ServiceA) AopContext.currentProxy()).hashCode());
System.out.println("---------------------");
((ServiceA) AopContext.currentProxy()).c();
}
@Monitor
private void c() {
System.out.println(serviceB);
System.out.println("hashcode:"+this.hashCode());
System.out.println("invoked method c");
}
}
执行结果:
可以看到,在执行a方法时,因为时spring生成的Bean,hashcode是547201549,而下一行当前cglib代理对象的hashcode是-675809067。在执行c方法时,他的hashcode是-675809067。说明调用c方法和a方法时,已经不是同一个Bean对象,所以注入的Bean对象serviceB没有值。
如果改成this.c
@Monitor
public void a(){
System.out.println("invoked method a");
System.out.println(serviceB);
System.out.println("hashcode:"+this.hashCode());
System.out.println( ((ServiceA) AopContext.currentProxy()).hashCode());
System.out.println("---------------------");
this.c();
}
@Monitor
private void c() {
System.out.println(serviceB);
System.out.println("hashcode:"+this.hashCode());
System.out.println("invoked method c");
}
执行结果:
可以看到是同一个hashcode,serviceB也有值了。
四、总结
1、Spring自定义实现Aop或使用@Transaction时,目标方法不能为private。
2、Spring Aop创建的动态代理类一般使用cglib方式,cglib通过继承父类方式。将需要增强的类作为父类,创建的代理类为子类,并重写目标方法,equals方法、hashcode方法等。
3、调用匹配的增强方法时,实际入参是Spring生成的Bean对象。
通过target = targetSource.getTarget();
new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
4、调用动态代理类中没有的方法时,则是生成的代理类对象直接调用父类的方法。因为代理类只是通过无参构造方法生成,所以他的所有成员变量和需要注入的Bean对象都为null。
5、equals方法和hashcode方法被重写,所以值肯定不一样。但是如果直接打印 this,会发现是一样的。原因是会调用Object类的toString方法。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
因为创建的动态代理类会把ServiceA、ServiceA的父类中(Object)所有public方法都会创建,所以会调用Object类的toString。虽然不执行增强,但是也是会进入DynamicAdvisedInterceptor类的intercept方法进行判断,看是否执行增强。一旦进行intercept方法,那么传入的就是target对象,也就是ServiceA对象本身了。所以不管在a方法还是c方法打印this,结果都是spring创建的serviceA对象。