java拦截被注解的参数_Spring 下基于自定义注解拦截方法调用

其实很多年前就做过如此的实验,一翻开自己的日志有关于 aspectj site:unmi.cc, 可以找到  2008 年写的日志。真是流光容易把人抛,红了樱桃,绿了巴蕉。只是那时候 Spring 刚步入 2.0, 才翻开强大 AOP 的篇章,还记得彼时只要是直接使用 AspectJ 就要写  *.aj 文件。而如今 Spring 都到 5.0 了,也就是一年前才重拾起 Spring, 这期间 AspectJ 早就可以不用 *.aj 文件,只需普通 Java 文件,加上 @Aspect 和 @Pointcut 之类的注解就行。

本文内容与几年前写过的日志大体相差不大,再缀上一篇纯粹是个人笔记。这里不以 Spring 5.0 为例,仍然是最新的 4.3.11.RELEASE, 并且直接用 Spring, 而非选择  Spring Boot, 因为用了 Spring Boot 常常搞不清楚哪些是自动配置了的。原生的 Spring 可以使自己掌握一个 Spring AOP 的基本要素。

需求:@LogStartTime 注解的方法,在每次进入该方法时把当前时间写入 ThreadLocal 中去,被 @LogStartTime 注解的方法中随时可以获得进入方法的时间

接下来怎么能离开最具说明问题的代码呢?我们会通过 Main 方法和测试用例来验证。

spring-annotation-aop.png?resize=500%2C365&ssl=1

这是项目所有的文件,下面逐一列出内容(为不值钱的篇幅考虑还是会对内容有所裁剪,比如省略 import 部分)

pom.xml, 用到以下依赖,用简洁的格式表示

org.springframework:spring-context:4.3.11.RELEASE:runtime

org.aspectj:aspectjweaver:1.8.9:runtime

javax.inject:javax.inject:1:runtime

junit:junit:4.12:test

org.springframework:spring-test:4.3.11.RELEASE:runtime

1

2

3

4

5

org.springframework:spring-context:4.3.11.RELEASE:runtime

org.aspectj:aspectjweaver:1.8.9:runtime

javax.inject:javax.inject:1:runtime

junit:junit:4.12:test

org.springframework:spring-test:4.3.11.RELEASE:runtime

@LogStartTime, 有该注解的方法将被拦截

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface LogStartTime {

}

1

2

3

4

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceLogStartTime{

}

MethodStartAspect, 定义切面和 Advice

@Named //它也必须是一个 Spring bean,它要能被 Spring 扫描到

@Aspect //说明这是一个切面定义类

public class MethodStartAspect {

private static ThreadLocal startTime = new ThreadLocal<>();

@Pointcut("@annotation(cc.unmi.aspects.LogStartTime)") //切面表达式, 从织入效率上还应该加上像 execution 限制

private void logStartTime() { //切面签名,凡是被 LogStartTime 注解的方法都是 logStartTime() 切面

}

@Before("logStartTime()") //进入切面 logStartTime() 之前把当前系统时间存入 startTime, 以备后用

public void setStartTimeInThreadLocal() {

startTime.set(System.currentTimeMillis());

System.out.println("saved method start time in threadLocal");

}

public static Long getStartTime() {

return startTime.get();

}

public static void clearStartTime() {

startTime.set(null);

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

@Named//它也必须是一个 Spring bean,它要能被 Spring 扫描到

@Aspect//说明这是一个切面定义类

publicclassMethodStartAspect{

privatestaticThreadLocalstartTime=newThreadLocal<>();

@Pointcut("@annotation(cc.unmi.aspects.LogStartTime)")//切面表达式, 从织入效率上还应该加上像 execution 限制

privatevoidlogStartTime(){//切面签名,凡是被 LogStartTime 注解的方法都是 logStartTime() 切面

}

@Before("logStartTime()")//进入切面 logStartTime() 之前把当前系统时间存入 startTime, 以备后用

publicvoidsetStartTimeInThreadLocal(){

startTime.set(System.currentTimeMillis());

System.out.println("saved method start time in threadLocal");

}

publicstaticLonggetStartTime(){

returnstartTime.get();

}

publicstaticvoidclearStartTime(){

startTime.set(null);

}

}

这个类里就是用的 AspectJ 的语法来定义切面和 Advice 的,也可以定义普通方法。它同样是一个必须由 Spring 来管理的 Bean, 所以有 @Name 注解。代码中有详细的注释。

Spring AOP 只支持基于方法的拦截,可以拦截到 public, protected 和 package-visible 方法,从设计交互上应该只拦截 public 方法。其他诸如属性,构造函数等的拦截就得用真正的 AspectJ 了.

切面的定义还可以内联,如上面的 @Before("logStartTime()") 可以一句话写成 @Before("annotation(cc.unmi.aspects.LogStartTime)").

AppConfig, Spring  的 Java  Config 文件

@Configuration

@EnableAspectJAutoProxy //在原生 Spring 中这个是必须的,在 Spring Boot 中默认是被启用了的

@ComponentScan(basePackages = "cc.unmi") //注意这个要兼顾到 @AspectJ 注释的类

public class AppConfig {

}

1

2

3

4

5

6

@Configuration

@EnableAspectJAutoProxy//在原生 Spring 中这个是必须的,在 Spring Boot 中默认是被启用了的

@ComponentScan(basePackages="cc.unmi")//注意这个要兼顾到 @AspectJ 注释的类

publicclassAppConfig{

}

这个配置要能扫描到切面定义(@Aspect) 的类,如上面的 MethodStartAspect

UserService, 有我们要拦截的方法

@Named

public class UserService {

@LogStartTime //因为有了这个注解,在方法中便随时能拿到进入该方法的时间,在 ThreadLocal 中

public String fetchUserById(int userId) {

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("start time: " + MethodStartAspect.getStartTime() + ", execution time: " +

(System.currentTimeMillis() - MethodStartAspect.getStartTime()));

return "nameOf" + userId;

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Named

publicclassUserService{

@LogStartTime//因为有了这个注解,在方法中便随时能拿到进入该方法的时间,在 ThreadLocal 中

publicStringfetchUserById(intuserId){

try{

Thread.sleep(2000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

System.out.println("start time: "+MethodStartAspect.getStartTime()+", execution time: "+

(System.currentTimeMillis()-MethodStartAspect.getStartTime()));

return"nameOf"+userId;

}

}

HelloAop, 应用类

public class HelloAop {

public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

UserService userService = context.getBean(UserService.class);

System.out.println(userService.fetchUserById(234));

}

}

1

2

3

4

5

6

7

8

9

publicclassHelloAop{

publicstaticvoidmain(String[]args){

ApplicationContextcontext=newAnnotationConfigApplicationContext(AppConfig.class);

UserServiceuserService=context.getBean(UserService.class);

System.out.println(userService.fetchUserById(234));

}

}

执行后输出如下:

saved method start time in threadLocal

start time: 1506746277397, execution time: 2033

nameOf234

HelloAopTest, 测试类中看效果

@ContextConfiguration(classes = AppConfig.class)

@RunWith(SpringJUnit4ClassRunner.class)

public class HelloAopTest {

@Inject

private UserService userService;

@Before

public void setup() {

MethodStartAspect.clearStartTime();

}

@Test

public void testSettingMethodStartTimeInThreadLocal() {

userService.fetchUserById(9999);

assertThat(MethodStartAspect.getStartTime(), notNullValue());

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

@ContextConfiguration(classes=AppConfig.class)

@RunWith(SpringJUnit4ClassRunner.class)

publicclassHelloAopTest{

@Inject

privateUserServiceuserService;

@Before

publicvoidsetup(){

MethodStartAspect.clearStartTime();

}

@Test

publicvoidtestSettingMethodStartTimeInThreadLocal(){

userService.fetchUserById(9999);

assertThat(MethodStartAspect.getStartTime(),notNullValue());

}

}

执行后绿了spring-annotation-aop-1.png?resize=730%2C95&ssl=1

今天测试中碰到过在多模块的 Maven 项目中,把切面定义类 MethodStartAspect 放到另一个模块中方法拦截便失效了,不过刚刚的实验却是正常的,不知为何。

再进一步,在注解 @LogStartTime 中,我们可以定义属性,然后在切面中根据注解属性的不同作出不同的行为响应,如清楚日志的 MDC ContextMap 等。

如何获得注解参数

如果我们给注解 @LogStartTime 加个参数,那么它的声明就是

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface LogStartTime {

String value() default "";

}

1

2

3

4

5

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceLogStartTime{

Stringvalue()default"";

}

这时候要在切面中获得该注解的  value 参数的写法如下

@Named

@Aspect

public class MethodStartAspect {

@Pointcut("execution(* cc.unmi..*(..)) && @annotation(logStartTime)")

private void logStartTimePointcut(LogStartTime logStartTime) {

}

@Before("logStartTimePointcut(logStartTime)")

public void setStartTimeInThreadLocal(LogStartTime logStartTime) {

System.out.println(logStartTime.value());

}

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

@Named

@Aspect

publicclassMethodStartAspect{

@Pointcut("execution(* cc.unmi..*(..)) && @annotation(logStartTime)")

privatevoidlogStartTimePointcut(LogStartTimelogStartTime){

}

@Before("logStartTimePointcut(logStartTime)")

publicvoidsetStartTimeInThreadLocal(LogStartTimelogStartTime){

System.out.println(logStartTime.value());

}

}

Spring 中运用 AOP 时有三种写法风格:

AspectJ language code style:  就是我原来写过的 *.aj 文件的语法。Java5 之前的无奈之选

AspectJ annotation style: Java 代码却不失 *.aj 的特性,我的钟爱。而且它是 Spring AOP 和 AspectJ 同时能理解的

Spring XML style: 这都什么年代了,缺点多多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值