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

一、先定义一个Aspect

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

import org.springframework.stereotype.Service;

@Service("sampleService")
public class SampleService { public String hello(String name) {
return "你好," + name;
} }

  

三、跑一把

@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(); }
}

输出:

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上下文

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方法,改成用线程池调用(即:多线程)

    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));
}

输出如下:

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上下文

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方法:

    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());
}

输出:

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

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

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

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(0, 1) + 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中处理拦截逻辑,为了方便使用这个代理类,再写一个小工具:

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;
}
}

有了它就好办了:

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方法:

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://github.com/yjmyzz/aop-multi-thread-demo

spring中的多线程aop方法拦截的更多相关文章

  1. Spring中IOC和AOP的详细解释(转)

    原文链接:Spring中IOC和AOP的详细解释 我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂 ...

  2. JavaEE开发之Spring中的多线程编程以及任务定时器详解

    上篇博客我们详细的聊了Spring中的事件的发送和监听,也就是常说的广播或者通知一类的东西,详情请移步于<JavaEE开发之Spring中的事件发送与监听以及使用@Profile进行环境切换&g ...

  3. 详细分析 Java 中实现多线程的方法有几种?(从本质上出发)

    详细分析 Java 中实现多线程的方法有几种?(从本质上出发) 正确的说法(从本质上出发) 实现多线程的官方正确方法: 2 种. Oracle 官网的文档说明 方法小结 方法一: 实现 Runnabl ...

  4. Spring中IOC和AOP的详细解释

    我们是在使用Spring框架的过程中,其实就是为了使用IOC,依赖注入,和AOP,面向切面编程,这两个是Spring的灵魂. 主要用到的设计模式有工厂模式和代理模式. IOC就是典型的工厂模式,通过s ...

  5. spring中IOC和AOP原理

    IoC(Inversion of Control): (1)IoC(Inversion of Control)是指容器控制程序对象之间的关系,而不是传统实现中,由程序代码直接操控.控制权由应用代码中转 ...

  6. 【转】Spring中事务与aop的先后顺序问题

    [原文链接] http://my.oschina.net/HuifengWang/blog/304188 [正文] Spring中的事务是通过aop来实现的,当我们自己写aop拦截的时候,会遇到跟sp ...

  7. Spring中RestTemplate的使用方法

    一.REST 在互联网中,我们会通过请求url来对网络上的资源做增删改查等动作,这里的请求包含两部分:动词,主要包括增.删.改.查:名词,就是网络中的各种资源.传统的非REST风格的请求方式是把动词和 ...

  8. 面试官:spring中定义bean的方法有哪些?我一口气说出了12种,把面试官整懵了。

    前言 在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜.我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工 ...

  9. 【Spring Framework】12种spring中定义bean的方法

    前言 在庞大的java体系中,spring有着举足轻重的地位,它给每位开发者带来了极大的便利和惊喜.我们都知道spring是创建和管理bean的工厂,它提供了多种定义bean的方式,能够满足我们日常工 ...

随机推荐

  1. Python_logging模块

    日志:方便用户了解系统.软件或应用的运行情况,及时发现问题并快速定位.解决问题. 一个日志信息对应的是一个事件的发生,而一个事件需要包括的几个内容: 事件发生时间 事件发生位置 事件发生严重程度(日志 ...

  2. 删除了原有的offset之后再次启动会报错park Streaming from Kafka has error numRecords must not ...

          笔者使用Spark streaming读取Kakfa中的数据,做进一步处理,用到了KafkaUtil的createDirectStream()方法:该方法不会自动保存topic parti ...

  3. scrapy 基础使用以及错误方案

    原先用的是selenium(后面有时间再写),这是第一次使用scrapy这个爬虫框架,所以记录一下这个心路历程,制作简单的爬虫其实不难,你需要的一般数据都可以爬取到. 下面是我的目录,除了main.p ...

  4. BZOJ2480 Spoj3105 Mod 数论 扩展BSGS

    原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ2480.html 题目传送门 - BZOJ2480 题意 已知数 $a,p,b$ ,求满足 $a^x≡b ...

  5. python josn包

    Python josn包中的编码与解码方法 对于Python数据类型进行编码解码 json.dumps  对python的数据类型进行json格式编码 :(将dict转为json格式) eg: imp ...

  6. js将时间戳转为时间格式

    时间戳转时间格式 //分钟 let timeM= parseInt(msg/1000/60%60); if(timeM<10){ timeM="0"+timeM; } //秒 ...

  7. python解释器遇到if __name__=="__main__"会如何做?

    python解释器遇到if __name__=="__main__"会如何做 # Threading example import time, thread def myfunct ...

  8. 反向传播算法(前向传播、反向传播、链式求导、引入delta)

    参考链接: 一文搞懂反向传播算法

  9. README 语法记录

    转自:nblogs.com/liugang-vip/p/6337580.html 正文: 1.标题的几种写法: 第一种:     前面带#号,后面带文字,分别表示h1-h6,上图可以看出,只到h6,而 ...

  10. Codeforces gym 101291 M (最长交替子序列)【DP】

    <题目链接> 题目大意:给你一段序列,要求你求出该序列的最长交替子序列,所谓最长交替子序列就是,这段序列的相邻三项必须是先递增再递减或者先递减再递增这样交替下去. 解题分析: 这与一道dp ...