Jodd 切面编程支持多数据源

最近因一个微信项目的升级需要,又打开了以前用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的组件:

180326_DTIs_3492483.png

官网上(http://jodd.org/doc/proxetta/)有详细的介绍,这里就不啰嗦了,直接引用原图:

180704_2F2Y_3492483.png180732_M87P_3492483.png

180830_lxD6_3492483.png

 

有三种委托调用方式:

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了。

下班,回去吃麻麻寄来的粽子。

 

 

 

转载于:https://my.oschina.net/eyesos/blog/910680

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值