Spring之AOP注解失效原因和解决方法

问题:

在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();
	}
}
        通过上面的描述,我们可以看出当方法被代理时,其实是动态生成了一个代理对象,代理对象去执行 invoke方法,在调用被代理对象的方法来完成业务。当在被代理对象的方法中调用被代理对象的方法时。其实是没有用代理调用,是通过被代理对象本身调用的。

       在嘴上面的例子中,调用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;
}


  • 通过实现ApplicationContext获取代理对象。新建获取代理对象的工具类SpringUtil
@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("将用户存入数据库");
    }
}

完成!!


你可能感兴趣的:(SpringBoot)