詳解Spring中使用@within與@target的區(qū)別
項(xiàng)目里用到@within時(shí),出現(xiàn)了一些問(wèn)題,使用@target就可以解決,但又會(huì)出現(xiàn)一些新的問(wèn)題,因此本文探討了在spring中,使用@within和@target的一些區(qū)別。
背景
項(xiàng)目里有一個(gè)動(dòng)態(tài)切換數(shù)據(jù)源的功能,我們是用切面來(lái)實(shí)現(xiàn)的,是基于注解來(lái)實(shí)現(xiàn)的,但是父類(lèi)的方法是可以切換數(shù)據(jù)源的,如果有一個(gè)類(lèi)直接繼承這個(gè)類(lèi),調(diào)用這個(gè)子類(lèi)時(shí),這個(gè)子類(lèi)是不能夠切換數(shù)據(jù)源的,除非這個(gè)子類(lèi)重寫(xiě)父類(lèi)的方法。
模擬項(xiàng)目例子
注解定義: @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()); } } 父類(lèi)Bean: @MyAnnotation("father") public class Father { public void hello() { System.out.println("father.hello()"); } public void hello2() { System.out.println("father.hello2()"); } } 子類(lèi)Bean: @MyAnnotation("son") public class Son extends Father { @Override public void hello() { System.out.println("son.hello()"); } } 配置類(lèi): @Configuration @EnableAspectJAutoProxy(exposeProxy = true) public class Config { @Bean public Father father() { return new Father(); } @Bean public Son son() { return new Son(); } } 測(cè)試類(lèi): 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(); } }
我們定義了一個(gè)@Before
通知,方法參數(shù)有point, myAnnotation
,方法里輸出了myAnnotation.value
的值
下面是輸出結(jié)果:
before, myAnnotation.value : father
father.hello()
before, myAnnotation.value : father
father.hello2()
before, myAnnotation.value : son
son.hello()
before, myAnnotation.value : father
father.hello2()
從上面的輸出結(jié)果看出:Son
類(lèi)重寫(xiě)了hello
方法,myAnnotation.value
的輸出的值是son
,hello2
方法沒(méi)有重寫(xiě),myAnnotation.value
的輸出的值是father
根據(jù)需求,我們肯定希望調(diào)用Son
類(lèi)的所有方法時(shí),都希望myAnnotation.value
的輸出的值是son
,因此就需要重寫(xiě)父類(lèi)的所有public
方法
那有沒(méi)有辦法不重寫(xiě)這些方法也能達(dá)到相同的效果呢,答案是可以的。
看看使用@within
和@target
的區(qū)別
我們分別在父類(lèi)和子類(lèi)上加上注解和去掉注解,一起來(lái)看看對(duì)應(yīng)的結(jié)果
@within
父類(lèi)無(wú)注解,子類(lèi)有注解:
father.hello() father.hello2() before, myAnnotation.value : son son.hello() father.hello2()
父類(lèi)有注解,子類(lèi)無(wú)注解:
before, myAnnotation.value : father father.hello() before, myAnnotation.value : father father.hello2() before, myAnnotation.value : father son.hello() before, myAnnotation.value : father father.hello2()
父類(lèi)有注解,子類(lèi)有注解(其實(shí)就是上面那個(gè)例子的結(jié)果):
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()); } }
我們?cè)僖黄饋?lái)看看測(cè)試結(jié)果:
父類(lèi)無(wú)注解,子類(lèi)有注解:
father.hello() father.hello2() before, myAnnotation.value : son son.hello() before, myAnnotation.value : son father.hello2()
父類(lèi)有注解,子類(lèi)無(wú)注解:
before, myAnnotation.value : father father.hello() before, myAnnotation.value : father father.hello2() son.hello() father.hello2()
父類(lèi)有注解,子類(lèi)有注解
before, myAnnotation.value : father father.hello() before, myAnnotation.value : father father.hello2() before, myAnnotation.value : son son.hello() before, myAnnotation.value : son father.hello2()
我們從上面總結(jié)出一套規(guī)律:
@within
:@Before
通知方法的myAnnotation
參數(shù)指的是調(diào)用方法所在的類(lèi)上面的注解,就是這個(gè)方法是在哪個(gè)類(lèi)上定義的
@target
:@Before
通知方法的myAnnotation
參數(shù)指的是調(diào)用方法運(yùn)行時(shí)所屬于的類(lèi)上面的注解
我們最后總結(jié)一下,如果父類(lèi)和子類(lèi)上都標(biāo)有注解,@within
和@target
的所得到實(shí)際注解的區(qū)別
@within |
@target | |
---|---|---|
父類(lèi)方法 | 父類(lèi)注解 | 父類(lèi)注解 |
子類(lèi)不重寫(xiě)方法 | 父類(lèi)注解 | 子類(lèi)注解 |
子類(lèi)重寫(xiě)方法 | 子類(lèi)注解 | 子類(lèi)注解 |
@target 看起來(lái)跟合理一點(diǎn)
從上面的分析可以看出,其實(shí)用@target更符合我們想要的結(jié)果,在某個(gè)類(lèi)上面加一個(gè)注解,攔截的時(shí)候就會(huì)獲取這個(gè)類(lèi)上面的注解,跟父類(lèi)完全沒(méi)有關(guān)系了
但這個(gè)時(shí)候會(huì)遇到一個(gè)問(wèn)題,就是不相關(guān)的類(lèi)都會(huì)生從代理類(lèi),
例子如下:
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自己什么都沒(méi)做,但卻被代理了
我們?cè)侔袬target換成@within:
class cn.eagleli.spring.aop.demo.NormalBean
可以看出使用@within時(shí),不相關(guān)的類(lèi)沒(méi)有被代理
我們一起來(lái)看看為什么
在AbstractAutoProxyCreator類(lèi)中的wrapIfNecessary方法打斷點(diǎn),看看什么情況:
@within
@target
我們從上面的圖片就可以理解為什么@target會(huì)生成代理類(lèi)
我們?cè)偕钊肟匆幌拢?br /> @within會(huì)走到如下:
public class ExactAnnotationTypePattern extends AnnotationTypePattern { @Override public FuzzyBoolean matches(AnnotatedElement annotated, ResolvedType[] parameterAnnotations) { // ...... } }
我沒(méi)深入研究,大致意思就是只要這個(gè)類(lèi)或者這個(gè)類(lèi)的祖先們帶有這個(gè)注解,即匹配成功
@target會(huì)走到如下:
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)); // 這里會(huì)返回true } } }
我沒(méi)深入研究,大致意思是匹配的話就返回YES,否則就返回MAYBE,匹配邏輯是和@within一樣的
因此所有不相關(guān)的類(lèi)都會(huì)是一個(gè)MAYBE的結(jié)果,這個(gè)結(jié)果會(huì)讓不相關(guān)的類(lèi)最后生成代理類(lèi)
通知方法中注解參數(shù)的值為什么是不一樣的
經(jīng)過(guò)調(diào)試,最終是在這里獲取的:
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()); // 這里設(shè)置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()); } }
很簡(jiǎn)單,從JoinPoint中得到target,然后從這個(gè)類(lèi)上得到對(duì)應(yīng)的注解即可
此時(shí),父類(lèi)和子類(lèi)都加有注解,一起來(lái)看看輸出結(jié)果:
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
能力有限,只能先探討這么多了,不懂的或者有其他見(jiàn)解的,歡迎一起討論呀~
到此這篇關(guān)于Spring中使用@within與@target的一些區(qū)別的文章就介紹到這了,更多相關(guān)Spring中使用@within與@target內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis-Plus條件構(gòu)造器Wrapper應(yīng)用實(shí)例
QueryWrapper是用于查詢(xún)的Wrapper條件構(gòu)造器,可以通過(guò)它來(lái)構(gòu)建SELECT語(yǔ)句中的WHERE條件,這篇文章主要介紹了MyBatis-Plus數(shù)據(jù)表操作條件構(gòu)造器Wrapper,需要的朋友可以參考下2023-09-09Spring Boot2配置服務(wù)器訪問(wèn)日志過(guò)程解析
這篇文章主要介紹了Spring Boot2配置服務(wù)器訪問(wèn)日志過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Spring中的FactoryBean實(shí)現(xiàn)原理詳解
這篇文章主要介紹了Spring中的FactoryBean實(shí)現(xiàn)原理詳解,spring中有兩種類(lèi)型的Bean,一種是普通的JavaBean,另一種就是工廠Bean(FactoryBean),這兩種Bean都受Spring的IoC容器管理,但它們之間卻有一些區(qū)別,需要的朋友可以參考下2024-02-02Java面向?qū)ο蠡A(chǔ),類(lèi),變量,方法
這篇文章主要介紹了Java面向?qū)ο蠡A(chǔ),類(lèi),變量,方法,需要的朋友可以參考下2020-10-10SpringBoot+actuator和admin-UI實(shí)現(xiàn)監(jiān)控中心方式
這篇文章主要介紹了SpringBoot+actuator和admin-UI實(shí)現(xiàn)監(jiān)控中心方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05Java中id,pid格式數(shù)據(jù)轉(zhuǎn)樹(shù)和森林結(jié)構(gòu)工具類(lèi)實(shí)現(xiàn)
本文主要介紹了Java中id,pid格式數(shù)據(jù)轉(zhuǎn)樹(shù)和森林結(jié)構(gòu)工具類(lèi)實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05詳解Java中finally和return的執(zhí)行順序
try-catch-finally是一種針對(duì)程序運(yùn)行時(shí)出錯(cuò)的響應(yīng)手段,對(duì)于一些可以預(yù)料到的出錯(cuò)類(lèi)型,在發(fā)生時(shí)對(duì)其進(jìn)行報(bào)告和補(bǔ)救,這篇文章主要介紹了Java中finally和return的執(zhí)行順序,需要的朋友可以參考下2024-01-01