使用:AopContext.currentProxy() 这个解决Spring注解失效
原因简单来说: 同一个类内这样调用的话,只有第一次调用了动态代理生成的ProxyClass,之后一直用的是不带任何切面信息的
知道了原因,处理方法也特别简单只需要显示利用Spring暴露的AopContext即可
解决:
((YourClass) AopContext.currentProxy()).noTransactionTask(keyword);
例子:
public void noTransactionTask(String keyword){ // 注意这里 调用了代理类的方法
((YourClass) AopContext.currentProxy()).transactionTask(keyword);
}
@Transactional
void transactionTask(String keyword) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) { //logger
//error tracking
}
System.out.println(keyword);
}
spring boot注解记得要加上
@EnableAspectJAutoProxy(exposeProxy = true)来暴露AOP的Proxy对象才行,否则会报异常
Cannot find current proxy: Set 'exposeProxy' property on Advised to 'true' to make it available.
注意:exposeProxy = true 是用来暴露AOP的Proxy对象的
产生代理对象就必须要产生一个切面,如果没有,创建代理对象的时候我们会发现对象中的exposeProxy属性还是false,不管你是否设置了exposeProxy = true。
ProxyCreatorSupport的createAopProxy方法:
像@Async这个只是简单的ioc,而没有aop ,创建代理对象的时候我们会发现对象中的exposeProxy属性总是false
而@Transactional 会将类定义成是一个切面。所以我们可以用 AopContext.currentProxy() 来解决同一个类下,调用方法事务的问题
例子:
@Async 想要用 AopContext.currentProxy() 最简单的方法就是在被调用方法上添加一个事务的注解 @Transactional :
@Transactional
@Async
void transactionTask(String keyword) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) { //logger
//error tracking
}
System.out.println(keyword);
}
其实以上通过 AopContext.currentProxy() 这个方法去解决Spring注解失效的问题不是好的方案
更为简单的解决方案:
1.将被调用方法放到另一个类中
2.@Autowired注解让spring自动注入ApplicationContext,通过applicationContext.getBean(YourClass.class)获取类,然后调用你的方法
/**
* 注解让spring自动注入ApplicationContext
*/
@Autowired
private ApplicationContext applicationContext;
3.web项目可以通过WebApplicationContext 获取Spring容器上下文,通过wac.getBean(YourClass.class)获取类,然后调用你的方法
WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
或者 (通过request或session加载spring容器)
WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
或者
ServletContext application = ServletActionContext.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(application);
4.写一个工具类获取Spring 的bean,然后通过 SpringContextUtil去获取类,然后调用你的方法
SpringContextUtil.getBean(YourClass.class)
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* Created by qinjp on 2018/3/28.
*/
@Component
public class SpringContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Autowired
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtil.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz){
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> requiredType) {
return applicationContext.getBean(name, requiredType);
}
}
5.通过ApplicationListener获取spring 的上下文容器 ,获取类,然后调用你的方法
@Component // 注入spring容器
public class SpringManager implements ApplicationListener<ContextRefreshedEvent> {
private static ApplicationContext applicationContext = null;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if(applicationContext == null){
applicationContext = event.getApplicationContext();
}
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
public static <T> T getBean(Class<T> clazz){
return applicationContext.getBean(clazz);
}
}
6.spring boot 通过入口main函数获取spring 的上下文容器 ,获取类,然后调用你的方法
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(Application.class, args);
SpringContextUtil.setApplicationContext(context);
}
import org.springframework.context.ApplicationContext;
public class SpringContextUtil {
private static ApplicationContext applicationContext;
public static ApplicationContext getApplicationContext(){
return applicationContext;
}
public static void setApplicationContext(ApplicationContext applicationContext){
SpringContextUtil.applicationContext = applicationContext;
}
public static <T> T getBean(String name){
return (T)applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz){
return applicationContext.getBean(clazz);
}
}