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) {
<("发送消息异常");
}
}
@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("查询数据库");
UserById(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 = Class();
Object obj = ClassLoader(), Interfaces(), 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 {
cacheableboolean 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> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
public class UserService{ //买⽕车票
@Transactional
public void hello(){
System.out.println("开始hello");
try {
/
/通过代理对象去调⽤saveUser()⽅法
} catch(Exception e) {
<("发送消息异常");
}
}
@Transactional
public void saveUser(){
User user = new User();
user.setName("zhangsan");
System.out.println("将⽤户存⼊数据库");
}
}
完成!!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。