千家信息网

Spring中使用@within与@target的区别有哪些

发表于:2024-10-17 作者:千家信息网编辑
千家信息网最后更新 2024年10月17日,这篇文章将为大家详细讲解有关Spring中使用@within与@target的区别有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。项目里用到@within时,出
千家信息网最后更新 2024年10月17日Spring中使用@within与@target的区别有哪些

这篇文章将为大家详细讲解有关Spring中使用@within与@target的区别有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

项目里用到@within时,出现了一些问题,使用@target就可以解决,但又会出现一些新的问题,因此本文探讨了在spring中,使用@within和@target的一些区别。

背景

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

模拟项目例子

注解定义:@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface MyAnnotation {    String value() default "me";}切面定义:@Order(-1)@Aspect@Componentpublic 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 : sonson.hello()father.hello2()

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

before, myAnnotation.value : fatherfather.hello()before, myAnnotation.value : fatherfather.hello2()before, myAnnotation.value : fatherson.hello()before, myAnnotation.value : fatherfather.hello2()

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

before, myAnnotation.value : fatherfather.hello()before, myAnnotation.value : fatherfather.hello2()before, myAnnotation.value : sonson.hello()before, myAnnotation.value : fatherfather.hello2()

@target

把切面代码改成如下:

@Order(-1)@Aspect@Componentpublic 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 : sonson.hello()before, myAnnotation.value : sonfather.hello2()

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

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

父类有注解,子类有注解

before, myAnnotation.value : fatherfather.hello()before, myAnnotation.value : fatherfather.hello2()before, myAnnotation.value : sonson.hello()before, myAnnotation.value : sonfather.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) {                obtainPointcut_Expression();                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> 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@Componentpublic 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

关于"Spring中使用@within与@target的区别有哪些"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

0