欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cglib实现动态代理-解决大对象值传递问题 cglib动态代理拦截器AOP 

程序员文章站 2024-02-14 11:55:58
...

 

整篇基于cglib的3.0 版本实现来介绍下Cglib的一种应用场景。Cglib的底层是调用ASM来实现动态链接的,所以性能方便会比Java原生的Proxy的性能提升很多。Spring框架的AOP也是基于Cglib来实现的,这里不继续扩展ASM框架实现。大家感兴趣的可以阅读cglib源码,并做进一步扩展。

一、场景:通过Cglib的方法拦截器解放以下场景。相同对象的值拷贝,如果是个很大的对象,很容易遗漏或者出错。

 

/**
     * 数据转换和处理
     */
    private ItemDO changeValue(ItemDO newDO, ItemDO oldDO) {
       //数据赋给新DO数据
       newDO.setCount(oldDO.getCount());
       newDO.setCapacity(oldDO.getCapacity());
       newDO.setUrl(oldDO.getUrl());
       newDO.setName(oldDO.getName());
       newDO.setTitle(oldDO.getTitle());
       newDO.setIntro(oldDO.getIntro());
       newDO.setTotalNum(oldDO.getTotalNum());
       …
 
       return newDO;
    }

 

 

以上场景在code的海洋中大量存在着。下面尝试一下用拦截器去把上面这种代码解放成一行,并且通用于其他类似场景。

二、实现思路:

1、获得一个DO对象的代理。我们叫这个是sdo.

2、给代理添加一个方法拦截器(针对该场景,拦截所有set方法)。

3、对DO进行一些字段的set方法调用,就如平常我们去通过一些各种手段拿到一个DO字段值一样,,,这时每次调用set方法的时候,我们都会在拦截器里面put下来这个被调用的方法签名(key)——方法返回值(key)的Map。

4、new 一个新的或者从DB里面其他里面拿到一个旧时代的对象。但因为是后出现的,所以我们叫他ndo。

5、通过反射去将sdo的新改变的值赋给ndo(通過sdo的interceptor)。

 

三、实例概览:这里涉及到的几个类简单说明一下。

我们的例子里面的Bean叫做SourceDO。ChangeValueProxyFactory 类里面的方法来获得一个代理类。ChangeValueInterceptorFactory 是一个拦截器工厂,用于获取对象拦截器(以后可以继续扩展改类,增加其他拦截器),ChangeValueSetInterceptor 是具体的set方法拦截器实现。InvokeProxxy 是用来实现上述第5步骤的。通过反射将sdo的值赋值给ndo。

 

四、具体实现代码

 

1>ChangeValueProxyFactory

public class ChangeValueProxyFactory<T> {
 
    public static Object getChangeValueInstance(Callback callBack, Class<?> proxyClass) {
       Enhancer enhancer = new Enhancer();
       enhancer.setSuperclass(proxyClass);
       enhancer.setCallback(callBack);
       return enhancer.create();
    }
}

 

2>ChangeValueInterceptorFactory

 

public class ChangeValueInterceptorFactory {
 
    public static ChangeValueSetInterceptor getInterceptor(Object obj) {
       if (!(obj instanceof Factory)) {
           return null;
       }
       Factory f = (Factory) obj;
       Callback[] callBacks = f.getCallbacks();
       for (Callback callBack : callBacks) {
           if (callBack instanceof ChangeValueSetInterceptor) {
              return (ChangeValueSetInterceptor) callBack;
           }
       }
       return null;
    }
}

3> ChangeValueSetInterceptor

public class ChangeValueSetInterceptor implements MethodInterceptor {
 
    private static final String SET = "set";
    private Map<String, Object[]> changedPropMap;
 
    public ChangeValueSetInterceptor() {
       changedPropMap = new HashMap<String, Object[]>();
    }
 
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
 
       String name = method.getName();
       Object pobj = proxy.invokeSuper(obj, args);
       if (name.startsWith(SET)) {
           changedPropMap.put(name, args);
       }
       return pobj;
    }
 
    public Map<String, Object[]> getChangedPropMap() {
       return Collections.unmodifiableMap(changedPropMap);
    }
}

 

4> InvokeProxxy

 

public class InvokeProxy {
 
    public static void invokeChangeValue(Object sobject, Object nobject, ChangeValueSetInterceptor interceptor) throws Exception {
       if (sobject == null && nobject == null && interceptor == null)
           return;
       Map<String, Object[]> map = interceptor.getChangedPropMap();
       if (map == null || map.size() == 0)
           return;
       Method[] methoerds = nobject.getClass().getMethods();
       for (Method m : methoerds) {
           String setMethodName = m.getName();
           Object[] paramsValues = map.get(setMethodName);
           if (paramsValues != null && paramsValues.length > 0) {
              try {
                  Method setMethod = nobject.getClass().getMethod(setMethodName, m.getParameterTypes());
                  if (setMethod != null) {
                     setMethod.invoke(nobject, paramsValues);
                  }
              } catch (InvocationTargetException e) {
                  throw new InvocationTargetException(e);
              } catch (IllegalArgumentException e) {
                  throw new IllegalArgumentException(e);
              } catch (SecurityException e) {
                  throw new SecurityException(e);
              } catch (IllegalAccessException e) {
                  throw new IllegalAccessException();
              } catch (NoSuchMethodException e) {
                  throw new NoSuchMethodException();
              }
           }
 
       }
    }
 
}

 

5> SourceDO

 

public class SourceDO implements Serializable {
 
    private int id;
    private String name;
    private String note;
    private Date date;
    private String address;
    private boolean happy;
private List<String> foods;
// some setters and getters
……
@Override
    public String toString() {
       // TODO Auto-generated method stub
       return "id:" + id + "|name:" + name + "|note:" + note + "|date:" + date + "|happy:"+happy+"|foods:"+foods.toString()+
"|address:" + address;
}
}

 

6> ChangeValueTest

 

public class ChangeValueTest {
 
    public static void main(String[] args) {
       SourceDO sdo = (SourceDO) ChangeValueProxyFactory.getChangeValueInstance(new ChangeValueSetInterceptor(), SourceDO.class);
 
       ChangeValueSetInterceptor interceptor = ChangeValueInterceptorFactory.getInterceptor(sdo);
       fillSourchDO(sdo);
       SourceDO ndo = new SourceDO();
       fillNewSourchDO(ndo);
       try {
           InvokeProxy.invokeChangeValue(sdo, ndo, interceptor);
       } catch (Exception e) {
           // log and do something
       }
       System.out.println("SourchDO info:" + ndo.toString());
    }
 
private static void fillSourchDO(SourceDO sdo) {
       sdo.setId(32);
       sdo.setName("山田くん");
       sdo.setDate(new Date());
       sdo.setHappy(true);
       List<String> foods=new ArrayList<String>();
       foods.add("茄子");
       foods.add("草莓");
       sdo.setFoods(foods);
    }
 
    private static void fillNewSourchDO(SourceDO ndo) {
       ndo.setName("山田さん");
       ndo.setAddress("不在这里");
       ndo.setId(99);
       ndo.setHappy(false);
       ndo.setNote("Hey!Say!JUMP");
    }}

 

 

预期:用sdo的字段值覆盖ndo的字段值,未被改变的字段保留原来ndo的字段值。

Run result:

SourchDO info:SourchDO info:id:32|name:山田くん|note:Hey!Say!JUMP|date:Fri Jul 27 10:51:24 CST 2012|happy:true|foods:[茄子,草莓]|address:不在这里

比如对于之前那么多行的代码。现在只需一行: 至此已经实现了,最初的场景目标。但是还有很多问题有待扩充和解决。比如对于一个DO,这里限制了一种默认的规约,getXxxx和setXxx的方式。如果不是这种规约书写的方法就不能被拦截,不过可以部分字段特殊处理一下。对于什么时候放intercepter和什么场景获取intercepter还需要更多的思考和扩展。

 

InvokeProxy.invokeChangeValue(oldDO, newDO, itemDO.getInterceptor());//拦截方式

// Copy法:newDO= changeValue(newDO,oldDO); changeValue方法就是开篇的那个private赋值方法。

这里还要注意,因为itemDO的intercepter的代理创建在外层的类里面所以就通过将intercepter Set的方式来存放处理。

感慨于,通过拦截的方式的另一个好处,就是减少出错率,比如我们不记得前面的Action到底改变了多少个字段,而且也不关心到itemDO(相当于sdo)到底改变了什么。减少了比如一些编辑业务上的代码丢失部分更新字段的Bug~也不用为copy字段而眼花缭乱,解放Coding的生产力。