zoukankan      html  css  js  c++  java
  • Spring中@within与@target的一些区别

    背景

    项目里有一个动态切换数据源的功能,我们是用切面来实现的,是基于注解来实现的,但是父类的方法是可以切换数据源的,如果有一个类直接继承这个类,调用这个子类时,这个子类是不能够切换数据源的,除非这个子类重写父类的方法。

    模拟项目例子

    注解定义:
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface MyAnnotation {
        String value() default "me";
    }
    
    切面定义:
    @Order(-1)
    @Aspect
    @Component
    public class MyAspect {
        @Before("@within(myAnnotation)")
        public void switchDataSource(JoinPoint point, MyAnnotation myAnnotation) {
            System.out.println("before, myAnnotation.value : " + myAnnotation.value());
        }
    }
    
    父类Bean:
    @MyAnnotation("father")
    public class Father {
        public void hello() {
            System.out.println("father.hello()");
        }
        public void hello2() {
            System.out.println("father.hello2()");
        }
    }
    
    子类Bean:
    @MyAnnotation("son")
    public class Son extends Father {
        @Override
        public void hello() {
            System.out.println("son.hello()");
        }
    }
    
    配置类:
    @Configuration
    @EnableAspectJAutoProxy(exposeProxy = true)
    public class Config {
    
        @Bean
        public Father father() {
            return new Father();
        }
    
        @Bean
        public Son son() {
            return new Son();
        }
    }
    
    测试类:
    public class Main {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class,
                    MyAspect.class);
            Father father = context.getBean("father", Father.class);
            father.hello();
            father.hello2();
            Son son = context.getBean(Son.class);
            son.hello();
            son.hello2();
        }
    }
    

    我们定义了一个@Before通知,方法参数有point, myAnnotation,方法里输出了myAnnotation.value的值

    下面是输出结果:

    before, myAnnotation.value : father
    father.hello()
    before, myAnnotation.value : father
    father.hello2()
    before, myAnnotation.value : son
    son.hello()
    before, myAnnotation.value : father
    father.hello2()
    

    从上面的输出结果看出:Son类重写了hello方法,myAnnotation.value的输出的值是sonhello2方法没有重写,myAnnotation.value的输出的值是father

    根据需求,我们肯定希望调用Son类的所有方法时,都希望myAnnotation.value的输出的值是son,因此就需要重写父类的所有public方法

    那有没有办法不重写这些方法也能达到相同的效果呢,答案是可以的。

    看看使用@within@target的区别

    我们分别在父类和子类上加上注解和去掉注解,一起来看看对应的结果

    @within

    父类无注解,子类有注解:

    father.hello()
    father.hello2()
    before, myAnnotation.value : son
    son.hello()
    father.hello2()
    

    父类有注解,子类无注解:

    before, myAnnotation.value : father
    father.hello()
    before, myAnnotation.value : father
    father.hello2()
    before, myAnnotation.value : father
    son.hello()
    before, myAnnotation.value : father
    father.hello2()
    

    父类有注解,子类有注解(其实就是上面那个例子的结果):

    before, myAnnotation.value : father
    father.hello()
    before, myAnnotation.value : father
    father.hello2()
    before, myAnnotation.value : son
    son.hello()
    before, myAnnotation.value : father
    father.hello2()
    

    @target

    把切面代码改成如下:

    @Order(-1)
    @Aspect
    @Component
    public class MyAspect {
        @Before("@target(myAnnotation)")
        public void switchDataSource(JoinPoint point, MyAnnotation myAnnotation) {
            System.out.println("before, myAnnotation.value : " + myAnnotation.value());
        }
    }
    

    我们再一起来看看测试结果:

    父类无注解,子类有注解:

    father.hello()
    father.hello2()
    before, myAnnotation.value : son
    son.hello()
    before, myAnnotation.value : son
    father.hello2()
    

    父类有注解,子类无注解:

    before, myAnnotation.value : father
    father.hello()
    before, myAnnotation.value : father
    father.hello2()
    son.hello()
    father.hello2()
    

    父类有注解,子类有注解

    before, myAnnotation.value : father
    father.hello()
    before, myAnnotation.value : father
    father.hello2()
    before, myAnnotation.value : son
    son.hello()
    before, myAnnotation.value : son
    father.hello2()
    

    我们从上面总结出一套规律:
    @within@Before通知方法的myAnnotation参数指的是调用方法所在的类上面的注解,就是这个方法是在哪个类上定义的
    @target@Before通知方法的myAnnotation参数指的是调用方法运行时所属于的类上面的注解

    我们最后总结一下,如果父类和子类上都标有注解,@within@target的所得到实际注解的区别

    @within @target
    父类方法 父类注解 父类注解
    子类不重写方法 父类注解 子类注解
    子类重写方法 子类注解 子类注解

    @target 看起来跟合理一点

    从上面的分析可以看出,其实用@target更符合我们想要的结果,在某个类上面加一个注解,拦截的时候就会获取这个类上面的注解,跟父类完全没有关系了

    但这个时候会遇到一个问题,就是不相关的类都会生从代理类,

    例子如下:

    public class NormalBean {
        public void hello() {
        }
    }
    
    @Configuration
    @EnableAspectJAutoProxy(exposeProxy = true)
    public class Config {
    
        @Bean
        public Father father() {
            return new Father();
        }
    
        @Bean
        public Son son() {
            return new Son();
        }
    
        @Bean
        public NormalBean normalBean() {
            return new NormalBean();
        }
    }
    
    
    public class Main {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class,
                    MyAspect.class);
            Father father = context.getBean("father", Father.class);
            father.hello();
            father.hello2();
            Son son = context.getBean(Son.class);
            son.hello();
            son.hello2();
    
            NormalBean normalBean = context.getBean(NormalBean.class);
            System.out.println(normalBean.getClass());
        }
    }
    

    输出:

    class cn.eagleli.spring.aop.demo.NormalBean$$EnhancerBySpringCGLIB$$eebc2a39
    

    可以看出NormalBean自己什么都没做,但却被代理了

    我们再把@target换成@within

    class cn.eagleli.spring.aop.demo.NormalBean
    

    可以看出使用@within时,不相关的类没有被代理

    我们一起来看看为什么

    AbstractAutoProxyCreator类中的wrapIfNecessary方法打断点,看看什么情况:

    @within

    @target

    我们从上面的图片就可以理解为什么@target会生成代理类

    我们再深入看一下:
    @within会走到如下:

    public class ExactAnnotationTypePattern extends AnnotationTypePattern {
    	@Override
    	public FuzzyBoolean matches(AnnotatedElement annotated, ResolvedType[] parameterAnnotations) {
                // ......
            }
    }
    

    我没深入研究,大致意思就是只要这个类或者这个类的祖先们带有这个注解,即匹配成功

    @target会走到如下:

    public class ThisOrTargetAnnotationPointcut extends NameBindingPointcut {
    	@Override
    	protected FuzzyBoolean matchInternal(Shadow shadow) {
    		if (!couldMatch(shadow)) {
    			return FuzzyBoolean.NO;
    		}
    		ResolvedType toMatchAgainst = (isThis ? shadow.getThisType() : shadow.getTargetType()).resolve(shadow.getIWorld());
    		annotationTypePattern.resolve(shadow.getIWorld());
    		if (annotationTypePattern.matchesRuntimeType(toMatchAgainst).alwaysTrue()) {
    			return FuzzyBoolean.YES;
    		} else {
    			// a subtype may match at runtime
    			return FuzzyBoolean.MAYBE;
    		}
    	}
    }
    
    public class AspectJExpressionPointcut extends AbstractExpressionPointcut
    		implements ClassFilter, IntroductionAwareMethodMatcher, BeanFactoryAware {
    	@Override
    	public boolean matches(Method method, Class<?> targetClass, boolean hasIntroductions) {
    		obtainPointcutExpression();
    		ShadowMatch shadowMatch = getTargetShadowMatch(method, targetClass);
    
    		// Special handling for this, target, @this, @target, @annotation
    		// in Spring - we can optimize since we know we have exactly this class,
    		// and there will never be matching subclass at runtime.
    		if (shadowMatch.alwaysMatches()) {
    			return true;
    		}
    		else if (shadowMatch.neverMatches()) {
    			return false;
    		}
    		else {
    			// the maybe case
    			if (hasIntroductions) {
    				return true;
    			}
    			// A match test returned maybe - if there are any subtype sensitive variables
    			// involved in the test (this, target, at_this, at_target, at_annotation) then
    			// we say this is not a match as in Spring there will never be a different
    			// runtime subtype.
    			RuntimeTestWalker walker = getRuntimeTestWalker(shadowMatch);
    			return (!walker.testsSubtypeSensitiveVars() || walker.testTargetInstanceOfResidue(targetClass)); // 这里会返回true
    		}
    	}
    }
    

    我没深入研究,大致意思是匹配的话就返回YES,否则就返回MAYBE,匹配逻辑是和@within一样的

    因此所有不相关的类都会是一个MAYBE的结果,这个结果会让不相关的类最后生成代理类

    通知方法中注解参数的值为什么是不一样的

    经过调试,最终是在这里获取的:

    public final class ReflectionVar extends Var {
    	static final int THIS_VAR = 0;
    	static final int TARGET_VAR = 1;
    	static final int ARGS_VAR = 2;
    	static final int AT_THIS_VAR = 3;
    	static final int AT_TARGET_VAR = 4;
    	static final int AT_ARGS_VAR = 5;
    	static final int AT_WITHIN_VAR = 6;
    	static final int AT_WITHINCODE_VAR = 7;
    	static final int AT_ANNOTATION_VAR = 8;
    
    	public Object getBindingAtJoinPoint(
    			Object thisObject, 
    			Object targetObject, 
    			Object[] args,
    			Member subject,
    			Member withinCode,
    			Class withinType) {
    		switch( this.varType) {
    		case THIS_VAR: return thisObject;
    		case TARGET_VAR: return targetObject;
    		case ARGS_VAR:
    			if (this.argsIndex > (args.length - 1)) return null;
    			return args[argsIndex];
    		case AT_THIS_VAR:
    			if (annotationFinder != null) {
    				return annotationFinder.getAnnotation(getType(), thisObject);
    			} else return null;
    		case AT_TARGET_VAR:
    			if (annotationFinder != null) {
    				return annotationFinder.getAnnotation(getType(), targetObject);
    			} else return null;
    		case AT_ARGS_VAR:
    			if (this.argsIndex > (args.length - 1)) return null;
    			if (annotationFinder != null) {
    				return annotationFinder.getAnnotation(getType(), args[argsIndex]);
    			} else return null;
    		case AT_WITHIN_VAR:
    			if (annotationFinder != null) {
    				return annotationFinder.getAnnotationFromClass(getType(), withinType);
    			} else return null;
    		case AT_WITHINCODE_VAR:
    			if (annotationFinder != null) {
    				return annotationFinder.getAnnotationFromMember(getType(), withinCode);
    			} else return null;
    		case AT_ANNOTATION_VAR:
    			if (annotationFinder != null) {
    				return annotationFinder.getAnnotationFromMember(getType(), subject);
    			} else return null;
    		}	
    		return null;
    	}
    }
    

    @within

    case AT_WITHIN_VAR:
        if (annotationFinder != null) { 
            return annotationFinder.getAnnotationFromClass(getType(), withinType);
        } else return null;
    

    withinType追踪到如下:

    public class PointcutExpressionImpl implements PointcutExpression {
    	private ShadowMatch matchesExecution(Member aMember) {
    		Shadow s = ReflectionShadow.makeExecutionShadow(world, aMember, this.matchContext);
    		ShadowMatchImpl sm = getShadowMatch(s);
    		sm.setSubject(aMember);
    		sm.setWithinCode(null);
    		sm.setWithinType(aMember.getDeclaringClass()); // 这里设置withinType
    		return sm;
    	}
    }
    
    public abstract class AopUtils {
    	public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
    		Assert.notNull(pc, "Pointcut must not be null");
    		if (!pc.getClassFilter().matches(targetClass)) {
    			return false;
    		}
    
    		MethodMatcher methodMatcher = pc.getMethodMatcher();
    		if (methodMatcher == MethodMatcher.TRUE) {
    			// No need to iterate the methods if we're matching any method anyway...
    			return true;
    		}
    
    		IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
    		if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
    			introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
    		}
    
    		Set<Class<?>> classes = new LinkedHashSet<>();
    		if (!Proxy.isProxyClass(targetClass)) {
    			classes.add(ClassUtils.getUserClass(targetClass));
    		}
    		classes.addAll(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
    
    		for (Class<?> clazz : classes) {
    			Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
    			for (Method method : methods) { // 这里获取所有method
    				if (introductionAwareMethodMatcher != null ?
    						introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
    						methodMatcher.matches(method, targetClass)) {
    					return true;
    				}
    			}
    		}
    
    		return false;
    	}
    }
    

    @target

    case AT_TARGET_VAR:
        if (annotationFinder != null) {
            return annotationFinder.getAnnotation(getType(), targetObject);
        } else return null;
    

    targetObject 追踪到如下:

    public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
    		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    
    	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)); // 这里,targetObject就是生成的bean
    			this.proxyTypes.put(cacheKey, proxy.getClass());
    			return proxy;
    		}
    
    		this.advisedBeans.put(cacheKey, Boolean.FALSE);
    		return bean;
    	}
    
    	public SingletonTargetSource(Object target) {
    		Assert.notNull(target, "Target object must not be null");
    		this.target = target;
    	}
    }
    

    想用@within,但又想得到想要的注解

    @Order(-1)
    @Aspect
    @Component
    public class MyAspect {
        @Before("@within(myAnnotation)")
        public void switchDataSource(JoinPoint point, MyAnnotation myAnnotation) {
            System.out.println(point.getTarget() + " " + point + " " + myAnnotation.value() + " " +
                    point.getTarget().getClass().getAnnotation(MyAnnotation.class).value());
        }
    }
    

    很简单,从JoinPoint中得到target,然后从这个类上得到对应的注解即可

    此时,父类和子类都加有注解,一起来看看输出结果:

    cn.eagleli.spring.aop.demo.Father@194fad1 execution(void cn.eagleli.spring.aop.demo.Father.hello()) father father
    cn.eagleli.spring.aop.demo.Father@194fad1 execution(void cn.eagleli.spring.aop.demo.Father.hello2()) father father
    cn.eagleli.spring.aop.demo.Son@14fc5f04 execution(void cn.eagleli.spring.aop.demo.Son.hello()) son son
    cn.eagleli.spring.aop.demo.Son@14fc5f04 execution(void cn.eagleli.spring.aop.demo.Father.hello2()) father son
    

    能力有限,只能先探讨这么多了,不懂的或者有其他见解的,欢迎一起讨论呀~

  • 相关阅读:
    最受欢迎的北大通选课导读·1[精品]
    社会保险,
    养老金的计算,
    毫秒 后的一个计算,
    返回格式 的数据结构再次改造,
    阶段状态池子,
    生活,-摘
    融合,
    tableview 也可以实现这个效果,
    字体大小 一致起来,
  • 原文地址:https://www.cnblogs.com/eaglelihh/p/15201208.html
Copyright © 2011-2022 走看看