详解Spring中使用@within与@target的区别

作者:It''s my code life. 时间:2022-11-18 17:58:24 

项目里用到@within时,出现了一些问题,使用@target就可以解决,但又会出现一些新的问题,因此本文探讨了在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<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

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

来源:https://www.cnblogs.com/eaglelihh/p/15201208.html

标签:Spring,@within,@target
0
投稿

猜你喜欢

  • RecyclerView的使用之HelloWorld

    2023-10-24 08:14:13
  • Java 并发编程之ThreadLocal详解及实例

    2023-09-05 13:48:02
  • HashMap红黑树入门(实现一个简单的红黑树)

    2022-10-18 13:52:35
  • 解决Spring或SpringBoot开启事务以后无法返回自增主键的问题

    2023-09-12 18:51:45
  • c#测试反射性能示例

    2021-12-19 23:13:47
  • Java实现简单邮件发送功能

    2023-08-15 00:53:23
  • spring 自定义让@Value被解析到

    2022-12-21 13:27:49
  • Spring Boot 集成Redisson实现分布式锁详细案例

    2023-06-10 05:09:17
  • C# memcached缓存使用实例代码

    2022-01-15 02:17:11
  • 对比Java中的Comparable排序接口和Comparator比较器接口

    2023-10-29 04:29:51
  • Kotlin定义其他类的实现详解

    2022-12-18 09:29:14
  • Kotlin协程到底是如何切换线程的

    2022-03-03 13:52:39
  • 使用Spring Boot进行单元测试详情

    2023-11-10 08:01:53
  • eclipse的web项目实现Javaweb购物车的方法

    2023-07-28 15:55:53
  • SpringBoot Java后端实现okhttp3超时设置的方法实例

    2022-11-06 04:56:03
  • Android使用GridView实现表格分割线效果

    2022-05-12 06:59:54
  • JAVA实现扫描线算法(超详细)

    2023-06-30 13:33:20
  • 关于spring boot中几种注入方法的一些个人看法

    2022-09-07 10:15:14
  • Spring StopWatch使用实例详解

    2022-03-31 02:46:57
  • C#遍历文件夹获取指定后缀名文件

    2023-01-07 02:07:56
  • asp之家 软件编程 m.aspxhome.com