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

Spring中@within与@target的一些区别

@target

Spring中@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> 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

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

Original: https://www.cnblogs.com/eaglelihh/p/15201208.html
Author: eaglelihh
Title: Spring中@within与@target的一些区别

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/587211/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 解决.net mvc session超时的问题

    在.NET MVC中session的默认有效期是20分钟 调整的方式是在项目的Web.config中进行配置,如下方式可以调整为120分钟。 <system.web> …

    Java 2023年5月29日
    066
  • 修饰符-final

    Java是由C/C++泛生的,其也保留了C/C++的部分特性,如关键字。在C/C++中,关键字有着特殊的含义。 在编程中,一般会存在一些变量或方法,程序员不让其数据”发…

    Java 2023年6月5日
    078
  • 获取Spring中@PathVariable注解里带点的完整参数

    背景 原因 解决 参考 背景 spring-boot&#x7684;&#x7248;&#x672C;&#x662F;2.1.4.RELEASE&am…

    Java 2023年6月8日
    0106
  • Error:(5, 25) java: 程序包javax.servlet.jsp不存在

    1.首先选择file 2.再选择file目录下的Project Structure 3.选择Modules这个选项,再选择到你的项目中,点击绿色的”+”号 …

    Java 2023年5月29日
    068
  • Java开发环境搭建

    Java开发环境搭建 JDK下载安装 配置环境变量 JDK目录介绍 Hello world及简单语法介绍 Notepad++安装及使用 如何卸载JDK 找到JDK安装目录 右键我的…

    Java 2023年6月9日
    084
  • IK-Analyzer(5.3.1)动态配置自定义词典

    参考文献:http://blog.csdn.net/fatpanda/article/details/37911079 jar包: IK-Analyzer-extra-5.3.1….

    Java 2023年6月7日
    077
  • 在web.xml配置springmvc过滤器解决乱码

    代码: <!–配置过滤器–> <filter> <filter-name>characterEncodingFilter</filte…

    Java 2023年6月9日
    060
  • java基础篇—-数组

    数组是有序数据的集合,数组中的每个元素具有相同的数据类型,可以用一个统一的数组名和不同的下标来唯一确定数组中的元素. 一、一维数组 1.声明 数据类型[] 数组名; 或者是 数据类…

    Java 2023年6月8日
    087
  • Spring注解开发_Spring容器创建概述

    浅尝Spring注解开发,基于Spring 4.3.12概述Spring容器创建的过程,包括12个方法的执行 概述12个方法 //获取ioc容器 AnnotationConfigA…

    Java 2023年6月5日
    089
  • == 和 equals 的区别是什么?

    ==:基本类型比较的是值的大小,引用类型比较的是内存地址,是不是同一个对象,equals:默认比较同一个对象的内容 == 和 equals 的区别是什么? == : 它的作用是判断…

    Java 2023年6月13日
    078
  • Java之控制反转和依赖注入

    1.简介 依赖注入和控制反转,目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性,下面通过一个例子来引入这一概念。 2.案例 1)一般情况下的类耦合 Main.java 通…

    Java 2023年5月29日
    089
  • CentOS7.4下使用Nginx配置Asp.net Core和添加Https证书步骤

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/zxtceq/p/14173558.htmlAuthor…

    Java 2023年5月30日
    079
  • Macbook中VMWare的Centos7虚拟机配置静态IP并允许上网的配置方法

    一、检查Macbook本身的配置 1、打开【系统偏好设置】-【网络】- 选中【Wi-Fi】项(如果您是WIFI上网请选择此项)- 点右侧【高级】 选择【TCP/IP】选项卡,记录好…

    Java 2023年5月30日
    0100
  • java-多线程之间的通信

    线程通信的例子:使用两个线程打印 1-100。线程1, 线程2 交替打印 涉及到的三个方法:* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。* noti…

    Java 2023年5月29日
    086
  • 从双重校验锁进一步理解synchronized和volatile

    并发编程中的四个问题:可见性、原子性、有序性、指令重排对于 synchronized和 volatile首先我们知道: synchronized可以保证原子性、有序性、可见性; v…

    Java 2023年6月13日
    080
  • jsp中写java代码的方法

    Original: https://www.cnblogs.com/muhy/p/14827695.htmlAuthor: 永恒的回忆Title: jsp中写java代码的方法

    Java 2023年5月29日
    079
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球