任何Bean通过实现ProxyableBeanAccessor接口即可获得动态灵活的获取代理对 ...

打印 上一主题 下一主题

主题 674|帖子 674|积分 2022

如果一个BEAN类上加了@Transactional,则默认的该类及其子类的公开方法均会开启事务,但有时某些业务场景下某些公开的方法可能并不需要事务,那这种情况该如何做呢?
常规的做法:
针对不同的场景及事务传播特性,定义不同的公开方法【哪怕是同一种业务】,并在方法上添加@Transactional且指明不同的传播特性,示例代码如下:
  1. @Service
  2. @Transactional
  3. public class DemoSerivce {
  4.    //SUPPORTED 若无事务传播则默认不会有事务,若有事务传播则会开启事务
  5.    @Transactional(propagation = Propagation.SUPPORTED)
  6.    public int getValue(){
  7.       return 0;
  8.    }
  9.    //默认开启事务(由类上的@Transactional决定的)
  10.    public int getValueWithTx(){
  11.       return 0;
  12.    }
  13.     @Transactional(propagation = Propagation.NOT_SUPPORTED)
  14.    public int getValueWithoutTx(){
  15.       return 0;
  16.    }
  17. }
复制代码
上述这样的弊端就是:若是同一个逻辑但如果要精细控制事务,则需要定义3个方法来支持,getValue、getValueWithTx、getValueWithoutTx 3个方法,分别对应3种事务场景,这种代码就显得冗余过多,那有没有简单一点的方案呢?其实是有的。
声明式事务的本质是通过AOP切面,在代理执行原始方法【即:被标注了@Transactional的公开方法】前开启DB事务,在执行后提交DB事务(若抛错则执行回滚),如果要想事务不生效,则让AOP失效即可,即:调原生的service Bean公开方法而不是代理类的公开方法,那如何获得原生的BEAN类呢,答案是:在原生BEAN方法内部通过this获取即可,如果没理解,下面写一个手写代理示例,大家就明白了:
  1. public class DemoService{
  2.     public int getValue(){
  3.         return 666;
  4.     }
  5.     public DemoService getReal(){
  6.         //这里的this指向的就是当前的自己,并非代理对象
  7.         return this;
  8.     }
  9. }
  10. public class DemoServiceProxy{
  11.     private DemoService target;
  12.     public DemoServiceProxy(DemoService target){
  13.         this.target=target;
  14.     }
  15.     public int getValue(){
  16.         //增强:开启事务
  17.         return  target.getValue()+222;
  18.         //增强:提交事务
  19.     }
  20.     public DemoService getReal(){
  21.         //这里就会间接的把原生的对象传递返回
  22.         return target.getReal();
  23.     }
  24. }
  25.     public static void main(String[] args) {
  26.         DemoServiceProxy proxy=new DemoServiceProxy(new DemoService());
  27.         System.out.println("proxy class:" +proxy.getClass().getName());
  28.         System.out.println("real class:" +proxy.getReal().getClass().getName());
  29.         System.out.println("proxy getValue result:" + proxy.getValue() );
  30.         System.out.println("real getValue result:" + proxy.getReal().getValue() );
  31.     }
复制代码
最终的输出结果为:
proxy class:...DemoServiceProxy
real class:...DemoService  →原始的对象
proxy getValue result:888
real getValue result:666
通过DEMO证实了通过避开代理的方案是正确的,而且非常简单,那么有了这个基础,再应用到实际的代码中则很简单,想控制有事务则取代理对象,想控制不要事务则取原生对象即可,就是这么简单。

下面贴出核心也是全部的ProxyableBeanAccessor代码:(注意必需扩展自RawTargetAccess,否则即使返回this也会被强制返回代理)
  1. /**
  2. * @author zuowenjun
  3. * @date 2022/12/5 22:03
  4. * @description 可代理BEAN访问者接口(支持获取代理的真实对象、获取代理对象)
  5. */
  6. public interface ProxyableBeanAccessor<T extends ProxyableBeanAccessor> extends RawTargetAccess {
  7.     String CONTEXT_KEY_REAL_GET = "proxyable_bean_accessor_real_get";
  8.     /**
  9.      * 获取代理的真实对象(即:未被代理的对象)
  10.      * 注:若调用未被代理的bean的公开方法,则均不会再走AOP切面
  11.      *
  12.      * @return 未被代理的对象Bean
  13.      */
  14.     @SuppressWarnings("unchecked")
  15.     @Transactional(propagation = Propagation.NOT_SUPPORTED)
  16.     default T getReal() {
  17.         return (T) this;
  18.     }
  19.     /**
  20.      * 获取当前类的代理对象(即:已被代理的对象)
  21.      * 注:若调用已被代理的对象Bean的公开方法,则相关AOP切面均可正常拦截与执行
  22.      *
  23.      * @return 已被代理的对象Bean
  24.      */
  25.     @SuppressWarnings("unchecked")
  26.     @Transactional(propagation = Propagation.NOT_SUPPORTED)
  27.     default T getProxy() {
  28.         return (T) SpringUtils.getBean(this.getClass());
  29.     }
  30.     /**
  31.      * 将当前BEAN转换为代理对象或真实对象
  32.      *
  33.      * @param realGet 是否转换获取真实对象
  34.      * @return 未被代理的对象Bean OR 已被代理的对象Bean
  35.      */
  36.     @Transactional(propagation = Propagation.NOT_SUPPORTED)
  37.     default T selfAs(Supplier<Boolean> realGet) {
  38.         Boolean needGetReal = false;
  39.         if (realGet == null) {
  40.             if (ContextUtils.get() != null) {
  41.                 needGetReal = (Boolean) ContextUtils.get().getGlobalVariableMap().getOrDefault(CONTEXT_KEY_REAL_GET, false);
  42.             }
  43.         } else {
  44.             needGetReal = realGet.get();
  45.         }
  46.         return Boolean.TRUE.equals(needGetReal) ? getReal() : getProxy();
  47.     }
  48. }
复制代码
其中,SpringUtils是一个获取BEAN的工具类,代码如下:
  1. public SpringUtils implements ApplicationContextAware{
  2. private static ApplicationContext context;
  3.     @Override
  4.     public void setApplicationContext(ApplicationContext applicationContext)
  5.             throws BeansException {
  6.         context=applicationContext;
  7.     }
  8.   public static <T> getBean(Class<T> clazz){
  9.      return context.getBean(clazz);
  10.    }
  11. }
复制代码
ContextUtils只是一个内部定义了一个ThreadLocal的静态map字段,用于存放线程上下文要传递的对象。
使用方法:只需将原来Service的子类或其它可能被切面代理的类 加上实现自ProxyableBeanAccessor即可,然后在这个类里面或外部调用均可通过getReal获得原生对象、getProxy获得代理对象、selfAs动态根据条件来判断是否需要代理或原生对象,使用示例如下:
  1. //serive BEAN定义
  2. @Service
  3. @Transactional
  4. public class DemoService implements ProxyableBeanAccessor<DemoService> {
  5.    ... ...
  6.     public Demo selectByMergerParam(Demo demo){
  7.        return getMapper().selectByMergerParam(demo);
  8.     }
  9.     @Transactional(propagation = Propagation.NOT_SUPPORTED)
  10.     public Demo selectByMergerParam2(Demo demo){
  11.         //通过getProxy获取当前类的代理BEAN,以便可以执行事务切面
  12.         return getProxy().doSelectByMergerParam(demo);
  13.     }
  14.     public Demo doSelectByMergerParam(Demo demo){
  15.         return getMapper().selectByMergerParam(demo);
  16.     }
  17. }
  18. //具体使用:
  19.     @Autowired
  20.     private DemoService demoService;
  21.             Demo query = new Demo ();
  22.             query.setWaybillNumber("123455667");
  23.             //示例一:获取原生对象查询,由于没有代理,则无事务
  24.             demoService.getReal().selectByMergerParam(query);
  25.             //示例二:根据LAMBDA表达式的布尔值来决定是否获取原生或代理(这里演示:如果是TIDB,则获取原生对象,即:不开事务)
  26.             demoService.selfAs(()-> TidbDataSourceSwitcher.isUsingTidbDataSource()).selectByMergerParam(query);
  27.             //示例三:根据线程上下文设置的布尔值来决定是否获取原生或代理(这里演示:如果是TIDB,则获取原生对象,即:不开事务),这种方式主要是为了简化大批量的动态逻辑判断的场景,
  28.             // 一次设置同线程的所有ProxyableBeanAccessor的子类的selfAs(null)均可自动判断
  29.             ContextUtils.get().addGlobalVariable(ProxyableBeanAccessor.CONTEXT_KEY_REAL_GET,TidbDataSourceSwitcher.isUsingTidbDataSource());
  30.             demoService.selfAs(null).selectByMergerParam(query);
  31.             //示例四:获取代理对象,一般用于BEAN内部方法之间调用,外部调用其实本身就是代理无意义
  32.             demoService.getProxy().selectByMergerParam(query);
复制代码
通过上述示例代码可以看到,借助于ProxyableBeanAccessor接口默认实现的getReal、getProxy、selfAs方法,可以很灵活的实现按需获取代理或非代理对象。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

用户云卷云舒

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表