spring中的多线程aop方法拦截

spring中的多线程aop方法拦截

日常开发中,常用spring的aop机制来拦截方法,记点日志、执行结果、方法执行时间啥的,很是方便,比如下面这样:(以spring-boot项目为例)

一、先定义一个Aspect

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

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.*;

import org.springframework.stereotype.Component;

 

@Aspect

@Component("logAspect")

public class LogAspect {

 

 

    @Pointcut("execution(* com.cnblogs.yjmyzz..service..*(..))")

    private void logPointCut() {

    }

 

    @Around("logPointCut()")

    public Object doAround(ProceedingJoinPoint pjp) {

        Object result = null;

        StringBuilder sb = new StringBuilder();

        long start = 0;

        try {

            //记录线程id、方法签名

            sb.append("thread:" + Thread.currentThread().getId() + ", method:" + pjp.getSignature() + ",");

            //记录参数

            if (pjp.getArgs() != null) {

                sb.append("args:");

                for (int i = 0; i < pjp.getArgs().length; i++) {

                    sb.append("[" + i + "]" + pjp.getArgs()[i] + ",");

                }

            }

            start = System.currentTimeMillis();

            result = pjp.proceed();

            //记录返回结果

            sb.append("result:" + result);

        catch (Throwable e) {

            sb.append(",error:" + e.getMessage());

            throw e;

        finally {

            long elapsedTime = System.currentTimeMillis() - start;

            //记录执行时间

            sb.append(",elapsedTime:" + elapsedTime + "ms");

            System.out.println(sb.toString());

            return result;

        }

    }

 

}

  

二、定义一个service

1

2

3

4

5

6

7

8

9

10

import org.springframework.stereotype.Service;

 

@Service("sampleService")

public class SampleService {

 

    public String hello(String name) {

        return "你好," + name;

    }

 

}

  

三、跑一把

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

@SpringBootApplication

@EnableAspectJAutoProxy

@ComponentScan(basePackages = {"com.cnblogs.yjmyzz"})

public class AopThreadApplication {

 

    public static void main(String[] args) throws InterruptedException {

        ApplicationContext context = SpringApplication.run(AopThreadApplication.class, args);

        SampleService sampleService = context.getBean(SampleService.class);

 

        System.out.println("main thread:" + Thread.currentThread().getId());

 

        System.out.println(sampleService.hello("菩提树下的杨过"));

        System.out.println();

 

    }

}

输出:

1

2

3

main thread:1

thread:1, method:String com.cnblogs.yjmyzz.aop.thread.service.SampleService.hello(String),args:[0]菩提树下的杨过,result:你好,菩提树下的杨过,elapsedTime:6ms

你好,菩提树下的杨过

第2行即aop拦截后输出的内容。但有些时候,我们会使用多线程来调用服务,这时候aop还能不能拦到呢?

 

四、多线程

4.1 场景1:Runnable中传入了Spring上下文

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public class RunnableA implements Runnable {

 

    private ApplicationContext context;

 

    public RunnableA(ApplicationContext context) {

        this.context = context;

    }

 

    @Override

    public void run() {

        SampleService sampleService = context.getBean(SampleService.class);

        System.out.println("thread:" + Thread.currentThread().getId() + "," + sampleService.hello("菩提树下的杨过-2"));

    }

}

把刚才的main方法,改成用线程池调用(即:多线程)

1

2

3

4

5

6

7

8

9

public static void main(String[] args) throws InterruptedException {

    ApplicationContext context = SpringApplication.run(AopThreadApplication.class, args);

 

    System.out.println("main thread:" + Thread.currentThread().getId());

    System.out.println();

 

    ExecutorService executorService = Executors.newSingleThreadExecutor();

    executorService.submit(new RunnableA(context));

}

输出如下:

1

2

3

main thread:1

thread:23, method:String com.cnblogs.yjmyzz.aop.thread.service.SampleService.hello(String),args:[0]菩提树下的杨过-2,result:你好,菩提树下的杨过-2,elapsedTime:4ms

thread:23,你好,菩提树下的杨过-2

很明显,仍然正常拦截到了,而且从线程id上看,确实是一个新线程。

 

4.2 场景2:Runnable中没传入Spring上下文

1

2

3

4

5

6

7

8

9

10

11

public class RunnableB implements Runnable {

 

    public RunnableB() {

    }

 

    @Override

    public void run() {

        SampleService sampleService = new SampleService();

        System.out.println("thread:" + Thread.currentThread().getId() + "," + sampleService.hello("菩提树下的杨过-2"));

    }

}

与RunnableA的区别在于,完全与spring上下文没有任何关系,服务实例是手动new出来的。

修改main方法:

1

2

3

4

5

6

7

8

9

public static void main(String[] args) throws InterruptedException {

    ApplicationContext context = SpringApplication.run(AopThreadApplication.class, args);

 

    System.out.println("main thread:" + Thread.currentThread().getId());

    System.out.println();

 

    ExecutorService executorService = Executors.newSingleThreadExecutor();

    executorService.submit(new RunnableB());

}

输出:

1

2

main thread:1

thread:22,你好,菩提树下的杨过-2

全都是手动new出来的对象,与spring没半毛钱关系,aop不起作用也符合预期。这种情况下该怎么破?

 

轮到CGLib出场了,其实spring的aop机制,跟它就有密切关系,大致原理:CGLib会从被代理的类,派生出一个子类,然后在子类中覆写所有非final的public方法,从而达到"方法增强"的效果。为此,我们需要写一个代理类:

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

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

import net.sf.cglib.proxy.MethodInterceptor;

import net.sf.cglib.proxy.MethodProxy;

import org.apache.commons.lang3.ArrayUtils;

 

import java.lang.reflect.Method;

 

public class AopProxy implements MethodInterceptor {

 

    private final static int MAX_LEVEL = 3;

    private final static String DOT = ".";

 

    public static String getMethodName(Method method) {

        if (method == null) {

            return null;

        }

        String[] arr = method.toString().split(" ");

        String methodName = arr[2].split("\\(")[0] + "()";

        String[] arr2 = methodName.split("\\.");

        if (arr2.length > MAX_LEVEL) {

            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < arr2.length; i++) {

                if (i <= MAX_LEVEL) {

                    sb.append(arr2[i].substring(01) + DOT);

                else {

                    sb.append(arr2[i] + DOT);

                }

            }

            String temp = sb.toString();

            if (temp.endsWith(DOT)) {

                temp = temp.substring(0, temp.length() - 1);

            }

            return temp;

        }

        return methodName;

    }

 

    @Override

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        StringBuilder sb = new StringBuilder();

        Object result = null;

        long start = System.currentTimeMillis();

        boolean hasError = false;

        try {

            sb.append("thread[" + Thread.currentThread().getId() + "] " + getMethodName(method) + " =>args:");

            if (ArrayUtils.isNotEmpty(objects)) {

                for (int i = 0; i < objects.length; i++) {

                    sb.append("[" + i + "]" + objects[i].toString() + ",");

                }

            else {

                sb.append("null,");

            }

            result = methodProxy.invokeSuper(o, objects);

            sb.append(" result:" + result);

        catch (Exception e) {

            sb.append(", error:" + e.getMessage());

            hasError = true;

        finally {

            long execTime = System.currentTimeMillis() - start;

            sb.append(", execTime:" + execTime + " ms");

        }

        System.out.println(sb.toString());

        return result;

    }

}

关键点都在intercept方法里,被代理的类有方法调用时,在intercept中处理拦截逻辑,为了方便使用这个代理类,再写一个小工具:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

import net.sf.cglib.proxy.Enhancer;

 

public class ProxyUtils {

 

    /**

     * 创建代理对象实例

     *

     * @param type

     * @param <T>

     * @return

     */

    public static <T> T createProxyObject(Class<T> type) {

        AopProxy factory = new AopProxy();

        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(type);

        enhancer.setCallback(factory);

        //注意:被代理的类,必须有默认无参的空构造函数

        T instance = (T) enhancer.create();

        return instance;

    }

}

有了它就好办了:

1

2

3

4

5

6

7

8

9

10

11

12

public class RunnableB implements Runnable {

 

    public RunnableB() {

    }

 

    @Override

    public void run() {

        //注:这里改成用CGLib来创建目标的代理类实例

        SampleService sampleService = ProxyUtils.createProxyObject(SampleService.class);

        System.out.println("thread:" + Thread.currentThread().getId() + "," + sampleService.hello("菩提树下的杨过-2"));

    }

}

手动new的地方,改成用ProxyUtils生成代理类实例,还是跑刚才的main方法:

1

2

3

main thread:1

thread[24] c.c.y.a.thread.service.SampleService.hello() =>args:[0]菩提树下的杨过-2, result:你好,菩提树下的杨过-2, execTime:9 ms

thread:24,你好,菩提树下的杨过-2

第2行的输出,便是AopProxy类拦截的输出,成功拦截,皆大欢喜! 

 

注意事项:

1. 被代理的类,不能是内部类(即嵌套在类中的类),更不能是final类

2. 要拦截的方法,不能是private方法或final方法

转载于:https://my.oschina.net/demons99/blog/2236858

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值