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
的输出的值是son
,hello2
方法没有重写,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
,然后从这个类上得到对应的注解即可
此时,父类和子类都加有注解,一起来看看输出结果:
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的一些区别的更多相关文章
- Spring中@Autowired注解、@Resource注解的区别 (zz)
Spring中@Autowired注解.@Resource注解的区别 Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@ ...
- Spring中Model,ModelMap以及ModelAndView之间的区别
原文链接:http://blog.csdn.net/zhangxing52077/article/details/75193948 Spring中Model,ModelMap以及ModelAndVie ...
- android 中targetSdkVersion和与target属性的区别
AndroidMenifest.xml中targetSdkVersion和project.properties中的target属性的区别 在AndroidMenifest.xml中,常常会有 ...
- Spring中BeanFactory与FactoryBean到底有什么区别?
一.BeanFactory BeanFactory是一个接口,它是Spring中工厂的顶层规范,是SpringIoc容器的核心接口,它定义了getBean().containsBean()等管理Bea ...
- Spring中的注解@Value("#{}")与@Value("${}")的区别
1 @Value("#{}") SpEL表达式 @Value("#{}") 表示SpEl表达式通常用来获取bean的属性,或者调用bean的某个方法.当然还有可 ...
- Spring中@Autowired注解、@Resource注解的区别
Spring不但支持自己定义的@Autowired注解,还支持几个由JSR-250规范定义的注解,它们分别是@Resource.@PostConstruct以及@PreDestroy. @Resour ...
- 转:Spring中@Autowired注解、@Resource注解的区别
Pay attention: When using these annotations, the object itself has to be created by Spring context. ...
- Spring中 @Autowired注解与@Resource注解的区别
Spring中 @Autowired注解与@Resource注解的区别在Spring 3.X中经常使用到@Autowired和@Resource进行装配.这两个注解的差异在何处???相同点:@Reso ...
- Spring中的注解@Service @Component @Controller @Repository区别
@Service用于标注业务层组件, @Controller用于标注控制层组件(如struts中的action), @Repository用于标注数据访问组件,即DAO组件, @Component泛指 ...
随机推荐
- PWN——ret2dl_resolve
PWN--ret2dl_resolve ret2dl_resolve是栈溢出中,底层且高级的一种利用手段,这里特此做一篇笔记学习一下. 个人认为,要掌握这种方法,自己去写demo来多次调试分析是不二法 ...
- Redis-缓存穿透/击穿/雪崩
1. 简介 如图所示,一个正常的请求 客户端请求张铁牛的博客. 服务首先会请求redis,查看请求的内容是否存在. redis将请求结果返回给服务,如果返回的结果有数据则执行7:如果没有数据则会继续往 ...
- Manage sshd Service on CentOS
Check the current sshd status: service sshd status Start sshd service: service sshd start Set sshd a ...
- 不懂Ribbon原理的可以进来看看哦,分析SpringBoot自动装配完成了Ribbon哪些核心操作
前面详细的给大家介绍了SpringBoot的核心内容,有了这部分的基础支持的话,我们再来分析SpringCloud中的相关组件就很容器了,本文我们来给大家开始介绍Ribbon的相关内容,首先来介绍 ...
- Java文件和Java包结构
Java中的包概念 Java中的包是封装一组类,子包和接口的机制.软件包用于: 防止命名冲突.例如,可以有两个名称分别为Employee的类,college.staff.cse.Employee和co ...
- systemd.service — 服务单元配置
转载:http://www.jinbuguo.com/systemd/systemd.service.html 名称 systemd.service - 服务单元配置 大纲 service.servi ...
- 一篇文章让你搞懂如何通过Nginx来解决跨域问题
Nginx跨域实现 首先大家要搞清楚什么是跨域,为什么会有跨域情况的出现.哪些情况属于跨域? 跨域:由于浏览器的同源策略,即属于不同域的页面之间不能相互访问各自的页面内容 注:同源策略,单说来就是 ...
- 嵌入式ARM汇编详解
文章目录 零.预备知识 1.ARM与X86 2.ARM中指令的执行 3.ARM的九种寻址方式 立即数寻址 寄存器寻址 寄存器间接寻址 寄存器偏移寻址 寄存器基址变址寻址 批量寄存器寻址 相对寻址 堆栈 ...
- STM32—串口通讯详解
串口通讯目录 物理层 协议层 USART简介 开发板与上位机的连接 代码讲解: 一.初始化结构体 二.NVIC配置中断优先级 三.USART配置函数讲解 四.传输数据的函数: 1.发送一个字节 2.发 ...
- Spring Data JPA:解析SimpleJpaRepository
源码 SimpleJpaRepository的定义如下: /** * Default implementation of the {@link org.springframework.data.rep ...