任何Bean通过实现ProxyableBeanAccessor接口即可获得动态灵活的获取代理对象或原生对象的能力
如果一个BEAN类上加了@Transactional,则默认的该类及其子类的公开方法均会开启事务,但有时某些业务场景下某些公开的方法可能并不需要事务,那这种情况该如何做呢?
常规的做法:
针对不同的场景及事务传播特性,定义不同的公开方法【哪怕是同一种业务】,并在方法上添加@Transactional且指明不同的传播特性,示例代码如下:
@Service
@Transactional
public class DemoSerivce {
//SUPPORTED 若无事务传播则默认不会有事务,若有事务传播则会开启事务
@Transactional(propagation = Propagation.SUPPORTED)
public int getValue(){
return 0;
}
//默认开启事务(由类上的@Transactional决定的)
public int getValueWithTx(){
return 0;
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int getValueWithoutTx(){
return 0;
}
}
上述这样的弊端就是:若是同一个逻辑但如果要精细控制事务,则需要定义3个方法来支持,getValue、getValueWithTx、getValueWithoutTx 3个方法,分别对应3种事务场景,这种代码就显得冗余过多,那有没有简单一点的方案呢?其实是有的。
声明式事务的本质是通过AOP切面,在代理执行原始方法【即:被标注了@Transactional的公开方法】前开启DB事务,在执行后提交DB事务(若抛错则执行回滚),如果要想事务不生效,则让AOP失效即可,即:调原生的service Bean公开方法而不是代理类的公开方法,那如何获得原生的BEAN类呢,答案是:在原生BEAN方法内部通过this获取即可,如果没理解,下面写一个手写代理示例,大家就明白了:
public class DemoService{
public int getValue(){
return 666;
}
public DemoService getReal(){
//这里的this指向的就是当前的自己,并非代理对象
return this;
}
}
public class DemoServiceProxy{
private DemoService target;
public DemoServiceProxy(DemoService target){
this.target=target;
}
public int getValue(){
//增强:开启事务
return target.getValue()+222;
//增强:提交事务
}
public DemoService getReal(){
//这里就会间接的把原生的对象传递返回
return target.getReal();
}
}
public static void main(String[] args) {
DemoServiceProxy proxy=new DemoServiceProxy(new DemoService());
System.out.println("proxy class:" +proxy.getClass().getName());
System.out.println("real class:" +proxy.getReal().getClass().getName());
System.out.println("proxy getValue result:" + proxy.getValue() );
System.out.println("real getValue result:" + proxy.getReal().getValue() );
}
最终的输出结果为:
proxy class:...DemoServiceProxy
real class:...DemoService →原始的对象
proxy getValue result:888
real getValue result:666
通过DEMO证实了通过避开代理的方案是正确的,而且非常简单,那么有了这个基础,再应用到实际的代码中则很简单,想控制有事务则取代理对象,想控制不要事务则取原生对象即可,就是这么简单。
下面贴出核心也是全部的ProxyableBeanAccessor代码:(注意必需扩展自RawTargetAccess,否则即使返回this也会被强制返回代理)
/**
* @author zuowenjun
* @date 2022/12/5 22:03
* @description 可代理BEAN访问者接口(支持获取代理的真实对象、获取代理对象)
*/
public interface ProxyableBeanAccessor<T extends ProxyableBeanAccessor> extends RawTargetAccess {
String CONTEXT_KEY_REAL_GET = "proxyable_bean_accessor_real_get";
/**
* 获取代理的真实对象(即:未被代理的对象)
* 注:若调用未被代理的bean的公开方法,则均不会再走AOP切面
*
* @return 未被代理的对象Bean
*/
@SuppressWarnings("unchecked")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T getReal() {
return (T) this;
}
/**
* 获取当前类的代理对象(即:已被代理的对象)
* 注:若调用已被代理的对象Bean的公开方法,则相关AOP切面均可正常拦截与执行
*
* @return 已被代理的对象Bean
*/
@SuppressWarnings("unchecked")
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T getProxy() {
return (T) SpringUtils.getBean(this.getClass());
}
/**
* 将当前BEAN转换为代理对象或真实对象
*
* @param realGet 是否转换获取真实对象
* @return 未被代理的对象Bean OR 已被代理的对象Bean
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
default T selfAs(Supplier<Boolean> realGet) {
Boolean needGetReal = false;
if (realGet == null) {
if (ContextUtils.get() != null) {
needGetReal = (Boolean) ContextUtils.get().getGlobalVariableMap().getOrDefault(CONTEXT_KEY_REAL_GET, false);
}
} else {
needGetReal = realGet.get();
}
return Boolean.TRUE.equals(needGetReal) ? getReal() : getProxy();
}
}
其中,SpringUtils是一个获取BEAN的工具类,代码如下:
public SpringUtils implements ApplicationContextAware{
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
context=applicationContext;
}
public static <T> getBean(Class<T> clazz){
return context.getBean(clazz);
}
}
ContextUtils只是一个内部定义了一个ThreadLocal的静态map字段,用于存放线程上下文要传递的对象。
使用方法:只需将原来Service的子类或其它可能被切面代理的类 加上实现自ProxyableBeanAccessor即可,然后在这个类里面或外部调用均可通过getReal获得原生对象、getProxy获得代理对象、selfAs动态根据条件来判断是否需要代理或原生对象,使用示例如下:
//serive BEAN定义
@Service
@Transactional
public class DemoService implements ProxyableBeanAccessor<DemoService> {
... ...
public Demo selectByMergerParam(Demo demo){
return getMapper().selectByMergerParam(demo);
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public Demo selectByMergerParam2(Demo demo){
//通过getProxy获取当前类的代理BEAN,以便可以执行事务切面
return getProxy().doSelectByMergerParam(demo);
}
public Demo doSelectByMergerParam(Demo demo){
return getMapper().selectByMergerParam(demo);
}
}
//具体使用:
@Autowired
private DemoService demoService;
Demo query = new Demo ();
query.setWaybillNumber("123455667");
//示例一:获取原生对象查询,由于没有代理,则无事务
demoService.getReal().selectByMergerParam(query);
//示例二:根据LAMBDA表达式的布尔值来决定是否获取原生或代理(这里演示:如果是TIDB,则获取原生对象,即:不开事务)
demoService.selfAs(()-> TidbDataSourceSwitcher.isUsingTidbDataSource()).selectByMergerParam(query);
//示例三:根据线程上下文设置的布尔值来决定是否获取原生或代理(这里演示:如果是TIDB,则获取原生对象,即:不开事务),这种方式主要是为了简化大批量的动态逻辑判断的场景,
// 一次设置同线程的所有ProxyableBeanAccessor的子类的selfAs(null)均可自动判断
ContextUtils.get().addGlobalVariable(ProxyableBeanAccessor.CONTEXT_KEY_REAL_GET,TidbDataSourceSwitcher.isUsingTidbDataSource());
demoService.selfAs(null).selectByMergerParam(query);
//示例四:获取代理对象,一般用于BEAN内部方法之间调用,外部调用其实本身就是代理无意义
demoService.getProxy().selectByMergerParam(query);
通过上述示例代码可以看到,借助于ProxyableBeanAccessor接口默认实现的getReal、getProxy、selfAs方法,可以很灵活的实现按需获取代理或非代理对象。
任何Bean通过实现ProxyableBeanAccessor接口即可获得动态灵活的获取代理对象或原生对象的能力的更多相关文章
- Envelope几何对象 Curve对象几何对象 Multipatch几何对象 Geometry集合接口 IGeometryCollection接口
Envelope是所有几何对象的外接矩形,用于表示几何对象的最小边框,所有的几何对象都有一个Envelope对象,IEnvelope是Envelope对象的主要接口,通过它可以获取几何对象的XMax, ...
- java Web项目Service层通用接口和entityVo对象与entity对象转化问题的解决方案
Service层的接口中有一些比较常用方法,一次又一次的在新的Service层中被书写,所以懒惰的程序员又烦了,他们决定写个通用接口来解决这个问题. 有些项目中,实体类即承担接收表单数据的任务,又承担 ...
- 使用Spring容器动态注册和获取Bean
有时候需要在运行时动态注册Bean到Spring容器,并根据名称获取注册的Bean.比如我们自己的SAAS架构的系统需要调用ThingsBoard API和Thingsboard交互,就可以通过Thi ...
- Activity之间传递数据或数据包Bundle,传递对象,对象序列化,对象实现Parcelable接口
package com.gaojinhua.android.activitymsg; import android.content.Intent; import android.os.Bundle; ...
- 微信公众号平台接口开发:基础支持,获取access_token
新建Asp.net MVC 4.0项目 WeChatSubscript是项目UI层 WeChatTools是封装操作访问公众号接口的一些方法类库 获取AccssToken 我们要的得到AccessTo ...
- 微信公众号平台接口开发:基础支持,获取微信服务器IP地址
官方说明 目前看不出来这个接口有哪些具体运用,但是既然有这个接口,那我们就试试能不能用 访问接口 修改WeCharBase.cs,新增以下2个方法 public static string Serve ...
- cglib动态代理是通过继承父类的方式进行代理的 不是通过接口方式进行动态代理的 因此可以对普通的类进行代理
cglib动态代理是通过继承父类的方式进行代理的 不是通过接口方式进行动态代理的
- Servlet接口的实现类,路径配置映射,ServletConfig对象,ServletContext对象及web工程中文件的读取
一,Servlet接口实现类:sun公司为Servlet接口定义了两个默认的实现类,分别为:GenericServlet和HttpServlet. HttpServlet:指能够处理HTTP请求的se ...
- 无状态会话bean(3)---远程业务接口(没有排版)
迄今为止,我们仅仅讨论了使用一个本地业务接口的会话bean.在这样的情况下.本地意味着仅仅能由执行在同一个应用程序server实例的JavaEE组件声明会话bean的依赖性.比如.远程client不可 ...
- spring4.1.8扩展实战之五:改变bean的定义(BeanFactoryPostProcessor接口)
本章我们继续实战spring的扩展能力,通过自定义BeanFactoryPostProcessor接口的实现类,来对bean实例做一些控制: 原文地址:https://blog.csdn.net/bo ...
随机推荐
- 【SpringBoot】 启动后会调用执行的方法的 (五种方式)
在 SpringBoot 工程 启动后, 会调用执行方法的五种方式: 亲自测试, 按照执行顺序如下: 第一种: @Component public class SpringContext1 { @Po ...
- ngix反向代理服务器
Nginx ("engine x") 是一个高性能的HTTP 和反向代理 服务器,在大负载的情况下表现十分优秀. 1.正向代理 正向代理也是大家最常接触的到的代理模式.正向代理最大 ...
- 换架 3D 飞机,继续飞呀飞
相信大多数图扑 HT 用户都曾见过这个飞机的 Demo,在图扑发展的这十年,这个 Demo 是许多学习 HT 用户一定会参考的经典 Demo 之一. 这个 Demo 用简洁的代码生动地展示了 OBJ ...
- Serverless Devs 重大更新,基于 Serverless 架构的 CI/CD 框架:Serverless-cd
近日,Serverless 开发者平台 Serverless Devs 重磅发布基于 Serverless 架构的轻量级 CI/CD 框架--Serverless-cd.Serverless-cd 是 ...
- secure boot (一)fit image
前言 secure boot 和FIT Image是前段时间接触到的,其实早就该总结下了,奈何懒癌犯了,拖了好久才写出来. 之前也有人问我,工作后最大的感受是什么?我的回答是:"快速学习&q ...
- Mycat 实现分库分表及读写分离
本文为博主原创,未经允许不得转载: Mycat 官网: http://mycat.org.cn/ MyCat 权威指南 文档:http://www.mycat.org.cn/document/myca ...
- zipkin 与 sleuth 实现链路追踪
本文为博主原创,转载请注明出处 1.Zipkin 与 Sleuth 简介 zipkin 的官网地址: https://zipkin.io/ Zipkin 和 Sleuth 都是由 Twitter 开源 ...
- Redis 哨兵模式高可用
本文为博主原创,未经允许不得转载: 目录: 1. 哨兵 Sentinel 介绍 2. 哨兵架构特点及工作原理 3. redis哨兵架构搭建步骤 4. 哨兵数据丢失 5. spring boot 整合 ...
- ONVIF网络摄像头(IPC)客户端开发—RTSP RTCP RTP加载AAC音频流
前言: RTSP,RTCP,RTP一般是一起使用,在FFmpeg和live555这些库中,它们为了更好的适用性,所以实现起来非常复杂,直接查看FFmpeg和Live555源代码来熟悉这些协议非常吃力, ...
- [转帖]SQL Server中查询CPU占用高的SQL语句
本文导读:触发器造成死锁.作业多且频繁.中间表的大量使用.游标的大量使用.索引的设计不合理.事务操作频繁.SQL语句设计不合理,都会造成查询效率低下.影响服务器性能的发挥.我们可以使用sql serv ...