最近因一个微信项目的升级需要,又打开了以前用Jodd写的一个小项目。Jodd这个微框架编译后,项目也就6到7M,麻雀虽小,五脏俱全。然而就一个weixin-java-tool就追加5M左右了。。。
是一个微信带参数二维码的问题:临时二维码有过期时间,然而只能带一个Int参数(坑爹,你TM让我传个字符串会死啊),但可以无限制生成。永久二维码可以传字符串,然而只能生成10万张。鉴于业务发展需要,10万张压根不可取,不过期也不安全。换言之只能生成临时二维码,传一个参数了。
项目的特性属于附属项目,需调用另一个项目的数据。但是两个项目依赖性比较大,使用rpc又得在两个项目集成rpc,通过http调用又太慢且要考虑安全问题。再说,传一个参数在那边执行几个sql查询再通过网络返回结果,这种损耗让人受不了。我想:要是Jodd支持多数据源那该多好啊!反正两个项目不需要分得太细,还不到拆分的时候。
于是,把Jodd的源码弄下来了:https://github.com/oblac/jodd.git
在官网上浏览了一遍,发现了这个叫Proxetta的组件:
官网上(http://jodd.org/doc/proxetta/)有详细的介绍,这里就不啰嗦了,直接引用原图:

有三种委托调用方式:
Proxy在程序启动的时候会生成一个目标类的子类,会重写父类需要代理执行的方法。Wrapper则会生成另一个类,持有对一个或多个目标类实例的引用,委托调用目标方法。InvokeReplacer会硬生生把目标类的方法替换掉。
于是,我选择Proxy的方式实现多数据源的切换:
1. 从官网得知,Jodd的切面可以通过自定义注解实现,从程序的入口DefaultAppCore.java中可以找到@Transaction是如何实现的。
protected ProxyAspect createTxProxyAspects() { return new ProxyAspect( AnnotationTxAdvice.class, new MethodAnnotationPointcut(jtxAnnotations) { @Override public boolean apply(MethodInfo methodInfo) { return isPublic(methodInfo) && isTopLevelMethod(methodInfo) && super.apply(methodInfo); } }); }
重点是AnnotationTxAdvice.class:
import static jodd.jtx.proxy.AnnotationTxAdviceSupport.manager; //这个东西是静态的,SingletonScope,只管理一个数据源
public class AnnotationTxAdvice implements ProxyAdvice { public Object execute() throws Exception { Class type = targetClass(); String methodName = targetMethodName(); Class[] methodArgsTypes = createArgumentsClassArray(); String methodDescription = targetMethodDescription(); // read transaction mode from annotation JtxTransactionMode txMode = manager.getTxMode(type, methodName, methodArgsTypes, methodDescription); // request transaction JtxTransaction tx = null; try { String scope = manager.resolveScope(type, methodName); tx = manager.getJtxWorker().maybeRequestTransaction(txMode, scope); Object result = invoke(); manager.getJtxWorker().maybeCommitTransaction(tx); return result; } catch (Exception ex) { manager.getJtxWorker().markOrRollbackTransaction(tx, ex); throw ex; } } }
由此可得知在invoke()返回结果前,做了启动事务的操作,调用方法后,提交事务,发生异常则回滚。
到这一步一切都明了了,我需要替换AnnotationTxAdvice实现多数据源的切换。
2. 通过jndi的方式与mysql交互,在.props中定义多个数据源并在AppCore中获取,初始化多个数据源:
# 配置jndi数据源,第一个为默认数据源 hierarchical=test1,test2 test1.packageName=com.eyes.test1.entity test1.dataSourceProvider=JNDI test1.jndi=java:comp/env/TEST1 test1.tablePrefix=abc_ test1.tableUppercase=false test1.columnUppercase=false # 目录要与默认目录区分开,不能在子目录下 test2.packageName=com.eyes.test1.test2 test2.dataSourceProvider=JNDI test2.jndi=java:comp/env/test2 test2.tablePrefix=haha_ test2.tableUppercase=false test2.columnUppercase=false
3. 在重写的AppCore中一次性初始化多个AnnotationTxAdviceSupport放在一个Map中,并在PetiteContainer中注册为Bean。PetiteContainer的作用域也是SingletonScope,可以注册ThreadLocalScope的Bean,但这个map全局只有一个,无需考虑线程问题。
4. 自定义Annotation:
@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Source { String value(); }
@Source的作用在于在事务调用之前决定切换的数据源,value为数据源的名称。
5. 定义SourceAdvice,使用ThreadLocalScope的变量存放要使用的manager
public class SourceAdvice implements ProxyAdvice { @Override public Object execute() throws Exception { Class type = ProxyTarget.targetClass(); String methodName = ProxyTarget.targetMethodName(); Class[] methodArgsTypes = ProxyTarget.createArgumentsClassArray(); Method method = type.getMethod(methodName,methodArgsTypes); Source source = method.getAnnotation(Source.class); String name = source.value(); PetiteContainer pc = BeanUtil.declared.getProperty(ProxyTarget.target(), PetiteContainer.PETITE_CONTAINER_REF_NAME); HashMap map = pc.getBean(ANNOTATION_MANAGERS); AnnotationTxAdviceManager manager = (AnnotationTxAdviceManager) map.get(name); ThreadAdviceManagerHolder.set(manager); //在这里决定使用哪个数据源管理器 ThreadAdviceManagerHolder.setKey(name); Object result = ProxyTarget.invoke(); //@Transaction调用后。。。 ThreadAdviceManagerHolder.set(Hierarchical.DEFAULT); //恢复默认数据源 ThreadAdviceManagerHolder.setKey(Hierarchical.DEFAULT_KEY); return result; } }
6. 配置proxetta:
ProxyAspect sourceProxy = new ProxyAspect(SourceAdvice.class, new MethodAnnotationPointcut(Source.class) { @Override public boolean apply(MethodInfo mi) { return isPublic(mi) && isTopLevelMethod(mi) && matchClassName(mi, "*Service") && super.apply(mi); } });
proxetta = ProxyProxetta.withAspects(sourceProxy,txProxy); //使@Source,@Transaction生效 proxetta.setClassLoader(this.getClass().getClassLoader());
7. 调用
@Source("test2") @Transaction(propagation = PROPAGATION_SUPPORTS,readOnly = false) public Hi nihaoma(Hi e){ ... return e; }
总结:
在实现的过程中会遇到一些问题,然后把问题一一解决就OK了。
最好的学习方法就是看源码,参考源码,修改源码。本项目用的是Jodd v3.7.1,Jodd最新的版本是3.8.*,升级到java8了。
下班,回去吃麻麻寄来的粽子。