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

项目里用到@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

@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中使用@within与@target内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Springboot项目删除项目同步target文件问题解决方案

    1.问题来源 今天在做一个springboot的HelloWorld的web的项目,在我删除掉首页index.html后,重新编译,通过浏览器访问还是能访问到index.html页面. 此项目是在idea下完成的. 2.问题分析 问题在于每次我们每次编译这个项目时,新增的文件会被编译进入target文件,而删除后的文件不会被编译进入target文件. 3.解决问题 解决的方法有两种 第一种:直接删除target文件,重新编译就会重新生成一个已经删除index.htmld的target文件 第二种

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

    项目里用到@within时,出现了一些问题,使用@target就可以解决,但又会出现一些新的问题,因此本文探讨了在spring中,使用@within和@target的一些区别. 背景 项目里有一个动态切换数据源的功能,我们是用切面来实现的,是基于注解来实现的,但是父类的方法是可以切换数据源的,如果有一个类直接继承这个类,调用这个子类时,这个子类是不能够切换数据源的,除非这个子类重写父类的方法. 模拟项目例子 注解定义: @Target({ElementType.METHOD, ElementTy

  • 详解spring中aop不生效的几种解决办法

    先看下这个问题的背景:假设有一个spring应用,开发人员希望自定义一个注解@Log,可以加到指定的方法上,实现自动记录日志(入参.出参.响应耗时这些) package com.cnblogs.yjmyzz.springbootdemo.aspect; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy

  • 一篇文章从无到有详解Spring中的AOP

    前言 AOP (Aspect Orient Programming),直译过来就是 面向切面编程.AOP 是一种编程思想,是面向对象编程(OOP)的一种补充.面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面. 从<Spring实战(第4版)>图书中扒了一张图: 从该图可以很形象地看出,所谓切面,相当于应用对象间的横切点,我们可以将其单独抽象为单独的模块. <?xml version="1.0" encoding="UTF-8&qu

  • 详解Spring中BeanUtils工具类的使用

    目录 简介 Spring的BeanUtils方法 Spring的BeanUtils与Apache的BeanUtils区别 实例 简介 说明 本文介绍Spring的BeanUtils工具类的用法. 我们经常需要将不同的两个对象实例进行属性复制,比如将DO对象进行属性复制到DTO,这种转换最原始的方式就是手动编写大量的 get/set代码,很繁琐.为了解决这一痛点,就诞生了一些方便的类库,常用的有 Apache的 BeanUtils,Spring的 BeanUtils, Dozer,Orika等拷贝

  • 详解Spring中的FactoryBean

    spring  FactoryBean 是创建 复杂的bean,一般的bean 直接用xml配置即可,如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean 例子如下: 1:创建一个Car类(是为了简便)一般不能直接给出Car类,如果是这样直接注入就可以或者Car对象了,这里只是为了简便. package com.myapp.core.factorybean; public class Car { private Strin

  • 详解Spring中接口的bean是如何注入的

    Question: 这个问题困扰了我好久,一直疑问这个接口的bean是怎么注入进去的?因为只看到使用@Service注入了实现类serviceImpl,使用时怎么能获取的接口,而且还能调用到实现类的方法,难道这个接口是在什么时候自动注入了进去,且和实现类关联上了? 接口 public interface TestService { public String test(); } 实现类impl @Service public class TestServiceImpl implements Te

  • 详解Spring 中 Bean 的生命周期

    前言 这其实是一道面试题,是我在面试百度的时候被问到的,当时没有答出来(因为自己真的很菜),后来在网上寻找答案,看到也是一头雾水,直到看到了<Spring in action>这本书,书上有对Bean声明周期的大致解释,但是没有代码分析,所以就自己上网寻找资料,一定要把这个Bean生命周期弄明白! ​ 网上大部分都是验证的Bean 在面试问的生命周期,其实查阅JDK还有一个完整的Bean生命周期,这同时也验证了书是具有片面性的,最fresh 的资料还是查阅原始JDK!!! 一.Bean 的完整

  • 详解Spring中的Transactional属性

    一.Transactional 声明式事务管理建立在AOP之上的.其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务. 简而言之,@Transactional注解在代码执行出错的时候能够进行事务的回滚. 二.使用说明 在启动类上添加@EnableTransactionManagement注解. 用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义. 在项目中

  • 详解Spring中Bean的作用域与生命周期

    目录 一.Bean的作用域 二.Bean的生命周期 使用代码演示Bean的生命周期 一.Bean的作用域 通过Spring容器创建一个Bean的实例时,不仅可以完成Bean的实例化,还可以使用Bean的scope属性为bean设置作用域. 语法格式:<bean id="别名" scope="作用域" class="对应实现类"> 作用域的种类:(sing) singleton和prototype区别:(该两种比较常用) ① singl

  • 详解Spring中的Environment外部化配置管理

    目录 profiles ProfileService 声明一个配置类 定义测试方法 profiles总结 Properties environment的应用 指定profile属性 @Value注解的使用 SpringEnvironment原理设计 Environment的中文意思是环境,它表示整个spring应用运行时的环境信息,它包含两个关键因素 profiles properties profiles profiles这个概念相信大家都已经理解了,最常见的就是不同环境下,决定当前sprin

随机推荐