spring aop的@target与@within的真正区别到底是什么? 您所在的位置:网站首页 in与within的区别 spring aop的@target与@within的真正区别到底是什么?

spring aop的@target与@within的真正区别到底是什么?

2024-05-31 09:41| 来源: 网络整理| 查看: 265

文档里面是这么说的:

@target: Limits matching to join points (the execution of methods when using Spring AOP) where the class of the executing object has an annotation of the given type. @within: Limits matching to join points within types that have the given annotation (the execution of methods declared in types with the given annotation when using Spring AOP).

看不出来啥区别,文档后面还举了个例子:

@target(org.springframework.transaction.annotation.Transactional):Any join point (method execution only in Spring AOP) where the target object has a @Transactional annotation @within(org.springframework.transaction.annotation.Transactional):Any join point (method execution only in Spring AOP) where the declared type of the target object has an @Transactional annotation

貌似一个是说目标object的类上有注解,一个是说声明方法的object的类上有注解,还是云山雾绕,不知道他要表达啥意思。

我们来结合一个具体的业务场景来说一下这个问题,比如,现在我们要用aop拦截系统中所有的@Controller,然后记录用户都访问了系统中的哪些接口,首先我们用@target来搞一下:

//这是aop的切面,用@target拦截所有的@RestController @Aspect public class DemoAdvice { @Pointcut("@target(org.springframework.web.bind.annotation.RestController)") public void pointCut(){ } @Before("pointCut()") public void before(){ log.info("---------before-------"); } } //加一个配置类,把切面加入到容器 @Configuration public class DemoConfig { @Bean public DemoAdvice demoAdvice(){ return new DemoAdvice(); } //同时加入了一个业务的Service @Bean public Service1 service(){ return new Service1(); } } //service里面啥也没有 public class Service1 { } //搞个controller测试一下 @RestController public class DemoController { @Autowired private Service1 service1; @GetMapping("/hello") public String hello(){ System.out.println("service1.getClass():"+service1.getClass().getName()); return "hello"; } }

很不幸,系统竟然没法启动,日志中有很多类似这样的东西:

2020-06-17 20:31:43.218 INFO 1052 — [ main] o.s.aop.framework.CglibAopProxy : Unable to proxy interface-implementing method [public final void org.springframework.web.filter.OncePerRequestFilter.doFilter(javax.servlet.ServletRequest,javax.servlet.ServletResponse,javax.servlet.FilterChain) throws javax.servlet.ServletException,java.io.IOException] because it is marked as final: Consider using interface-based JDK proxies instead! 2020-06-17 20:31:43.218 INFO 1052 — [ main] o.s.aop.framework.CglibAopProxy : Unable to proxy interface-implementing method [public final void org.springframework.web.filter.GenericFilterBean.init(javax.servlet.FilterConfig) throws javax.servlet.ServletException] because it is marked as final: Consider using interface-based JDK proxies instead!

貌似是说没法给OncePerRequestFilter生成动态代理类,因为方法是final的,很奇怪啊,我们没有要求给它生成代理啊!如果把我们的切面换成@within试试,就正常了!

我们用一个相对简单的例子重现下这个问题,因为SpringMVC中自动配置了太多的东西,我们还是用相对简单的AnnotationConfigApplicationContext来测试下。

首先我们先自定义一个注解:

@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyAnno { }

定义切面拦截自定义的注解:

@Aspect public class Advice2 { //@Pointcut("@within(com.github.xjs.attarget.demo2.MyAnno)") @Pointcut("@target(com.github.xjs.attarget.demo2.MyAnno)") public void pointCut(){ } @Before("pointCut()") public void before(){ log.info("---------before-------"); } }

Service2上添加自定义的注解:

@MyAnno public class Service2 { public void hello() { System.out.println("s2 hello"); } }

Service3就是三个普通的类:

public class Service3 { public void hello() { System.out.println("s3 hello"); } }

用一个配置类把他们都加载起来:

@EnableAspectJAutoProxy @Configuration public class Config2 { @Bean public Advice2 advice2(){ return new Advice2(); } @Bean public Service2 service2(){ return new Service2(); } @Bean public Service3 service3(){ return new Service3(); } }

写一个测试类:

public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config2.class); Service2 s2 = ctx.getBean(Service2.class); s2.hello(); System.out.println(s2.getClass().getName()); Service3 s3 = ctx.getBean(Service3.class); s3.hello(); System.out.println(s3.getClass().getName()); }

输出结果如下:

---------before------- s2 hello com.github.xjs.attarget.demo2.Service2$$EnhancerBySpringCGLIB$$5503fe34 s3 hello com.github.xjs.attarget.demo2.Service3$$EnhancerBySpringCGLIB$$689f86d7

以上代码可以看出来,s2和s3都是用cglib生成了动态代理类,s2我们能理解,因为s2上有要拦截的注解,s3上没有要拦截的注解为啥还要生成动态代理类呢?

我们换成@within再试一下,输出结果:

---------before------- s2 hello com.github.xjs.attarget.demo2.Service2$$EnhancerBySpringCGLIB$$9fae5acb s3 hello com.github.xjs.attarget.demo2.Service3

这回看上去就比较正常了。

stackoverflow上有老外提到了这个问题:https://stackoverflow.com/questions/52992365/spring-creates-proxy-for-wrong-classes-when-using-aop-class-level-annotation/53452483

When using spring AOP with class level annotations, spring context.getBean seems to always create and return a proxy or interceptor for every class, wether they have the annotation or not. This behavior is only for class level annotation. For method level annotations, or execution pointcuts, if there is no need for interception, getBean returns a POJO.

对于这个问题,答案可以参考下aspectj的文档: https://www.eclipse.org/aspectj/doc/released/adk15notebook/annotations-pointcuts-and-advice.html#runtime-type-matching-and-context-exposure

The this(), target(), and args() pointcut designators allow matching based on the runtime type of an object, as opposed to the statically declared type. In AspectJ 5, these designators are supplemented with three new designators : @this() (read, “this annotation”), @target(), and @args(). The forms of @this() and @target() that take a single annotation name are analogous to their counterparts that take a single type name. They match at join points where the object bound to this (or target, respectively) has an annotation of the specified type. @within(Foo) Matches any join point where the executing code is defined within a type which has an annotation of type Foo.

结合之前的文档,我们可以得出结论,@target是匹配的对象的运行时类型,而@within是匹配的定义正在执行的代码(方法)的类,一个方法定义在哪个类中这个是明确可以知道的,但是对象的运行时类型只有在运行时才能知道,所以@target才给所有的bean都生成了代理类,如果无法生成代理类就会出问题了,比如:

//明确指定用cglib @EnableAspectJAutoProxy(proxyTargetClass = true) @Configuration public class Config2 { @Bean public Advice2 advice2(){ return new Advice2(); } //显然,String是不可以有子类的 @Bean public String hello(){ return "hello"; } }

上面的配置中,我们明确设置了用cglib代理,再配置上hello那个bean以后,显然还是会报错的,因为String是不可以有子类的。

我们再来看下,运行时执行的方法的类与定义方法的类不是同一个类的情况,比如在继承的场景下。

首先定义两个注解:

@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AnnoFather { } @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface AnnoSon { }

然后定义两个类,上面分别加上这两个注解:

//父类上添加@AnnoFather @AnnoFather public class Father { public void fun1(){ System.out.println("father fun1"); } public void fun2(){ System.out.println("father fun2"); } } //子类上添加@AnnoSon,并且子类重写了父类的fun1()方法 @AnnoSon public class Son extends Father { @Override public void fun1(){ System.out.println("son fun1"); } }

添加切面:

@Before("@within(com.github.xjs.attarget.demo3.AnnoFather)") public void before1(){ System.out.println("---------@within @AnnoFather-------"); } @Before("@target(com.github.xjs.attarget.demo3.AnnoFather))") public void before2() { System.out.println("---------@target @AnnoFather-------"); } @Before("@within(com.github.xjs.attarget.demo3.AnnoSon)") public void before3(){ System.out.println("---------@within @AnnoSon-------"); } @Before("@target(com.github.xjs.attarget.demo3.AnnoSon)") public void before4(){ System.out.println("---------@target @AnnoSon-------"); }

测试一下:

public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config3.class); Father father = ctx.getBean("father", Father.class); father.fun1(); /** * ---------@within @AnnoFather------- * ---------@target @AnnoFather------- * father fun1 * */ father.fun2(); /** * ---------@within @AnnoFather------- * ---------@target @AnnoFather------- * father fun2 * */ Son son = (Son)ctx.getBean("son", Son.class); son.fun1(); /** * ---------@within @AnnoSon------- * ---------@target @AnnoSon------- * son fun1 * */ son.fun2(); /** * ---------@within @AnnoFather------- * ---------@target @AnnoSon------- * father fun2 * fun2()的运行时对象是son,但是fun2()却是在father中定义的。 * */ }

此外,@target也可以用execution(* (@cn.javass…Secure ).(…))来代替,@annotation也可以用execution(@java.lang.Deprecated * *(…))来代替,如下:

@Component @Aspect public class AspectA { @Around("execution(* (@MyAnnotation *).*(..)) || execution(@MyAnnotation * *(..))") public Object process(ProceedingJoinPoint joinPoint) throws Throwable { MyAnnotation myAnnotation = null; for (Annotation annotation : ((MethodSignature)joinPoint.getSignature()).getMethod().getDeclaredAnnotations()) { if (annotation instanceof MyAnnotation) { myAnnotation = (MyAnnotation) annotation; break; } } if (myAnnotation == null) { myAnnotation = joinPoint.getTarget().getClass().getAnnotationsByType(MyAnnotation.class)[0]; } System.out.println("AspectA: myAnnotation target:" + joinPoint.getTarget().getClass().getSimpleName()); System.out.println(" condition:" + myAnnotation.condition()); System.out.println(" key:" + myAnnotation.key()); System.out.println(" value:" + myAnnotation.value()); return joinPoint.proceed(); } } 结论

1.@target匹配的是执行方法的对象的class上是否有注解,@within匹配的是定义方法的对象的class上是否有注解,由于运行时多态的特性,执行方法的对象和定义方法的对象可能不是同一个对象。

2.定义方法的类是可以提前知道的,这个是静态的,但是执行方法的类是无法提前知道的,是运行时动态的,因此,在@target的场景下,spring aop给所有的bean都生成了代理类,如果无法生成代理就会报错。比如:cglib遇到final的时候。

3.@target一定要慎用,可以用execution(* (@cn.javass…Secure ).(…))来代替。

扫码查看全文: 在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有