在spring 中使用 @Transactional 、 @Cacheable 或 自定义 AOP 注解时,会发现个问题:在对象内部的方法中调用该对象的其他使用AOP注解的方法,被调用方法的AOP注解失效。
事物失效
public class UserService{
@Transactional
public void hello(){
System.out.println("开始hello方法");
try {
//在同一个类中的方法,再调用AOP注解(@Transactional注解也是AOP注解)的方法,会使AOP注解失效
//此时如果saveUser()存数据库动作失败抛出异常,“存入数据库“动作不会回滚,数据仍旧存入数据库
saveUser();
} catch (Exception e) {
logger.error("发送消息异常");
}
}
@Transactional
public void saveUser(){
User user = new User();
user.setName("zhangsan");
System.out.println("将用户存入数据库");
}
}
2、缓存失效,或者自定义注解失效
//使用缓存,查询时先查询缓存,缓存中查询不到时,调用数据库。
@Cacheable(value = "User")
public User getUser(String id){
System.out.println("查询数据库");
return UserDao.getUserById(id);
}
//在同一个类中的方法,调用@Cacheable注解的方法,会使AOP注解失效
public User getUser(String id){
//此时注解失效,getUser方法不会去缓存中查询数据,会直接查询数据库。
return getUser(id);
}
原因:
java动态代理和 cglib 代理来创建AOP代理,没有接口的类使用cglib 代理。
Spring AOP的java动态代理原理:
public interface PersonService {
void hello();
}
public class PersonServiceImpl implements PersonService {
@Override
public void hello() {
System.out.println("你好我好大家好");
}
}
//代理类实现InvovationHandler接口,来帮助被代理类去实现方法
public class HelloService implements InvocationHandler {
private PersonService target;
/**
* 获取被代理对象
*/
public Object getInstance(PersonService target) {
this.target = target;
Class clazz = target.getClass();
Object obj = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
return obj;
}
/**
* 调用被代理对象的底层方法
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是来打招呼的");
method.invoke(target, args);
System.out.println("我已经打完招呼了");
return null;
}
public static void main(String[] args) {
//获取被代理对象
PersonService personProxy = (PersonService) new HelloService().getInstance(new PersonServiceImpl());
//调用被代理对象的方法
personProxy.hello();
}
}
在嘴上面的例子中,调用UserService中的hello()方法时,Spring的动态代理帮我们动态生成了一个代理的对象,暂且叫他$UserService。所以调用hello()方法实际上是代理对象$UserService调用的。但是在hello()方法内调用同一个类的另外一个注解方法saveUser()时,实际上是通过this.saveUser()执行的, this 指的是UserService 对象,并不是$UserService代理对象调用的,没有走代理。所以注解失效。
Spring解决方案
通过AopContext.currentProxy获取当前代理对象,通过代理对象调用方法。最好的方法是避免在方法内部调用。
修改XML 新增如下语句;先开启cglib代理,开启 exposeProxy = true,暴露代理对象
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
public class UserService{
@Transactional
public void hello(){
System.out.println("开始hello");
try {
//通过代理对象去调用saveUser()方法
(UserService)AopContext.currentProxy().saveUser();
} catch (Exception e) {
logger.warn("发送消息异常");
}
}
@Transactional
public void saveUser(){
User user = new User();
user.setName("zhangsan");
System.out.println("将用户存入数据库");
}
}
SpringBoot解决方案
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
}
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
if (SpringUtil.applicationContext == null) {
SpringUtil.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通过class获取Bean.
public static T getBean(Class clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static T getBean(String name,Class clazz){
return getApplicationContext().getBean(name, clazz);
}
public class UserService{ //买火车票
@Transactional
public void hello(){
System.out.println("开始hello");
try {
//通过代理对象去调用saveUser()方法
SpringUtil.getBean(this.getClass()).saveUser()
} catch(Exception e) {
logger.error("发送消息异常");
}
}
@Transactional
public void saveUser(){
User user = new User();
user.setName("zhangsan");
System.out.println("将用户存入数据库");
}
}
完成!!