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

阿里的泰山 2021-09-15 07:33:26
java spring 区别 CSDN 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,然后从这个类上得到对应的注解即可

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

[email protected] execution(void cn.eagleli.spring.aop.demo.Father.hello()) father father
[email protected] execution(void cn.eagleli.spring.aop.demo.Father.hello2()) father father
[email protected] execution(void cn.eagleli.spring.aop.demo.Son.hello()) son son
[email protected] execution(void cn.eagleli.spring.aop.demo.Father.hello2()) father son

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

版权声明
本文为[阿里的泰山]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_46388795/article/details/120265883

  1. Design pattern -- agent pattern
  2. Huawei Java Senior Engineer interview question, Byte Jumping thousand Selected Interview Question is still not Brushed!
  3. 双非本科字节跳动Java面试题分享,一篇文章教你搞定计算机网络面试,
  4. 又到一年金三银四,2021最新Java笔试题及答案,
  5. Huawei Finance and Economics 2021 Spring Recruitment interview, what is distributed Lock?Pourquoi utiliser des serrures distribuées?
  6. Un an plus tard, Golden, Silver, Silver, and Silver, 2021, the latest Java written Examination Questions and Answers,
  7. Partage de questions d'entrevue Java à double saut d'octets non - premier cycle, un article vous apprend à gérer l'entrevue réseau informatique,
  8. Computer graduation project java online voting system based on SSM
  9. K8s gestion des ressources (opérations de base)
  10. 又到一年金三銀四,2021最新Java筆試題及答案,
  11. Collection de code de base JavaScript (1)
  12. Vérification des permissions d'interface pour le démarrage du printemps à l'aide d'annotations personnalisées AOP +.
  13. Vérification des permissions d'interface pour le démarrage du printemps à l'aide d'annotations personnalisées AOP +.
  14. Cloud Security Daily 210914: Red Hat Jboss Middleware Platform found important Security Vulnerability and needs to be upgraded as soon as possible
  15. 吐血整理,腾讯团队实力打造spring入门教程,
  16. 史上最全的微服务专业术语面试50问,字节跳动Java岗经典面试真题,
  17. Absolument!C'est l'analyse la plus détaillée du code source de hashtap que j'ai jamais vu!
  18. Computer graduation project Java logistics order management system of logistics company based on SSM
  19. Talk about Kafka: source code analysis of producer
  20. [springboot2 starts from 0] development tips - Lombok, devtools, spring initailizr
  21. 国内一线互联网公司面试题汇总,2021年大厂Java岗面试必问,
  22. 啃完吃透保你涨薪5K,熬夜整理小米Java面试题,
  23. 和字节跳动大佬的技术面谈,Redis成神之路电子版教程已问世,
  24. Le terme professionnel le plus complet de l'histoire des micro - services interview 50 questions, Byte Jumping Java post Classic interview vrai problème,
  25. After using mybatisplus, I haven't written SQL for a long time
  26. [springboot2 starts from 0] how to write a springboot application?
  27. Huawei cloud guassdb (for redis) released a new version, and the two core features were officially unveiled
  28. 和字節跳動大佬的技術面談,Redis成神之路電子版教程已問世,
  29. 啃完吃透保你漲薪5K,熬夜整理小米Java面試題,
  30. Avec l'interview technique du gigolo, le tutoriel électronique redis est sorti.
  31. Après avoir mangé, assurez - vous d'augmenter votre salaire de 5K et de rester debout tard pour trier les questions d'entrevue Java de millet.
  32. Résumé des questions d'entrevue pour les entreprises Internet nationales de première ligne, qui doivent être posées lors de l'entrevue d'emploi Java de la grande usine en 2021,
  33. Le tri des crachats de sang, la force de l'équipe Tencent pour créer le tutoriel d'introduction au printemps,
  34. Java and scala concurrency Fundamentals
  35. Analysis of java thread source code based on Hotspot
  36. 國內一線互聯網公司面試題匯總,2021年大廠Java崗面試必問,
  37. Introduction au module de contrôle de Connexion MySQL
  38. 大厂高级测试面试题,Java面试基础技能罗列,
  39. Comprendre l'architecture sous - jacente d'InnoDB en exécutant une instruction
  40. Chargeur de classe 1 Tomcat
  41. 小白也能看懂的dubbo3应用级服务发现详解
  42. SpringBoot异步使用@Async原理及线程池配置
  43. Questions d'entrevue de test avancé de Dachang, liste des compétences de base de l'entrevue Java,
  44. SpringBoot异步使用@Async原理及線程池配置
  45. Springboot utilise asynchrone le principe @ async et la configuration du pool de threads
  46. Détails de la découverte du Service d'application Dubbo 3 que Xiaobai peut également comprendre
  47. Springboot utilise asynchrone le principe @ async et la configuration du pool de threads
  48. 如何强大且优雅的搞定Linux文件系统,算法题 JVM,
  49. 太牛了,阿里P7架构师带你看透maven的来龙去脉,
  50. Oracle central et Oracle décentralisé
  51. java JavaBean
  52. Java wrapper type
  53. Java super keyword
  54. Java static keyword
  55. Java this keyword
  56. Java interface
  57. 太牛了,阿裏P7架構師帶你看透maven的來龍去脈,
  58. C'est génial, l'architecte Ali p7 vous montre à travers Maven.
  59. Comment traiter le système de fichiers Linux avec puissance et élégance, algorithme JVM,
  60. Usage of Java scanner