千家信息网

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

发表于:2025-11-13 作者:千家信息网编辑
千家信息网最后更新 2025年11月13日,这篇文章将为大家详细讲解有关Spring中使用@within与@target的区别有哪些,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。项目里用到@within时,出
千家信息网最后更新 2025年11月13日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的区别有哪些"这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

注解 方法 子类 结果 输出 面的 就是 代理 参数 例子 切面 数据 数据源 篇文章 问题 项目 切换 生成 意思 时候 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 app注册软件开发 应用软件开发收费多少 杭州小小网络技术 戴尔服务器怎么拆 电脑的网络安全密匙 苏州应用软件开发流程 房山回收二手服务器报价单 网络安全审计集中报警 家庭影音服务器 树霉派 云服务器53端口打开会被攻击吗 以下哪个数据库提供期刊评价报告 互联网金融科技者 如何远程公司的服务器 软件开发助理需要掌握什么 郑州的软件开发集中在哪 网站链接数据库存取数据库 数据库如何清理表格 计算机网络技术两个主要技术 奥奇传说不同服务器可以一起玩吗 网络安全推介 嘉兴本地软件开发商常见问题 lvm快照备份数据库 海淀区电子计算机软件开发排名 网络安全远离游戏手抄报图片 分布式服务器怎么验证登录超时 数据库安全性控制常用方法 文件上传linux服务器 吉安夜那网络技术有限公司 北大先行网络安全 编程好还是软件开发好
0