java动态代理

  • 今天写了个大方法,功能强大,happy之余,share下,让大家都嗨嗨呸呸的。
public interface IUserService {
    void login(String username, String password);
}
public class UserServiceImpl implements IUserService {
    @Override
    public void login(String username, String password) {
        System.out.println("login username : " + username);
    }
}

然后写个主函数。

public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        userService.login("Donald Trump", "******");
    }

运行结果 : login username : Donald Trump
牛逼。上线,程序一直运行挺好,突然有一天老板无聊的很,就扒拉了下打印结果,咦?login username : Ivanka Trump。这是啥时候的事情呀?大姐居然在用我们的应用。不行,必须要知道大姐啥时候这么无聊。
加代理,打印大姐login的时间。

public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        IUserService userProxy= (IUserService) Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), 
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object result = method.invoke(userService, args);
                        if("login".equals(method.getName()) && "Ivanka Trump".equalsIgnoreCase((String)args[0])) {
                            System.out.println("Ivanka Trump login time : " + new Date());
                        }
                        return result;
                    }
        });
        userProxy.login("Donald Trump", "******");
        userProxy.login("Ivanka Trump", "******");
    }

运行结果: login username : Donald Trump
login username : Ivanka Trump
Ivanka Trump login time : Thu Jan 18 18:23:08 CST 2018
以上纯粹扯皮开玩笑。但是这是一个最简单的java动态代理。剥离除业务逻辑代码之外,添加横切关注点来处理一下公用的功能,比如日志管理,数据库事物管理等等,大家都可以用动态代理。


  • 下面介绍一个特别的需求,记录一个DTO(Data Transfer Object)都set了哪些属性。
public class UserDTO {

    private String name;

    private String email;

    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

}

对于这个DTO的对象,我们可以有选择的调用任意的set方法去设置user的相关属性。比如:

  UserDTO user = new UserDTO();
  user.setName("Trump");
  user.setAge(58);

现在我们要记录出name和age的属性设置了,而email没有设置。说明email没有设置和user.setEmail(null)我们也要区分出来。这时候我们就会想到动态代理。但是DTO不是基于接口实现的,所以jdk反射包中的Proxy就不能打出这个技能。下面cglib出场。我们需要先引用cglib的jar。

public static void main(String[] args) {
        UserDTO user = injectProxyHandler(UserDTO.class);
        user.setName("Trump");
        user.setAge(58);
        user.getEmail();
    }

    public static <T> T injectProxyHandler(Class<T> dtoClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(dtoClass);
        enhancer.setCallbacks(new Callback[] {NoOp.INSTANCE, new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                            throws Throwable {
                        Object result = proxy.invokeSuper(obj, args);
                        System.out.println("setting attribute : " + method.getName().replaceFirst("set", ""));
                        return result;
                    }
        }});
        enhancer.setCallbackFilter(
                new CallbackFilter() {
                    @Override
                    public int accept(Method method) {
                        // only deal with those method starting with "set"
                        return method.getName().startsWith("set") ? 1 : 0;
                    }
        });
        return (T)enhancer.create();
    }

运行结果:setting attribute : Name
setting attribute : Age
预期已经海海呸呸的达到了,但是上线一段时间后,发现java方法区(Non-Heap)内存越来越大,而且完全没有回收。直觉(MAT)告诉我class文件太多。一下就猜到可能是cglib的问题。因为cglib的原理就是生成target类的子类。如果每次调用方法都生成一个新的子类,然后classloader加载的类只会越来越多。那现在我们简单的测试下code。

public static void main(String[] args) {
        UserDTO user = injectProxyHandler(UserDTO.class);
        System.out.println(user.getClass().getName());
        UserDTO user1 = injectProxyHandler(UserDTO.class);
        System.out.println(user1.getClass().getName());
    }

运行结果:com.arcserve.spog.users.UserDTO$$EnhancerByCGLIB$$4aaabb0
com.arcserve.spog.users.UserDTO$$EnhancerByCGLIB$$d6b70eca

果然,每次调用都会生成一个新类。这里不介绍cglib的工作原理,仅仅列出修改后的code。

private static SetMethodFilter setMethodFilter = new SetMethodFilter();

    static class SetMethodFilter implements CallbackFilter{
        @Override
        public int accept(Method method) {
            // only deal with those method starting with "set"
            return method.getName().startsWith("set") ? 1 : 0;
        }
    }

    public static void main(String[] args) {
        UserDTO user = injectProxyHandler(UserDTO.class);
        System.out.println(user.getClass().getName());
        UserDTO user1 = injectProxyHandler(UserDTO.class);
        System.out.println(user1.getClass().getName());
    }

    public static <T> T injectProxyHandler(Class<T> dtoClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(dtoClass);
        enhancer.setCallbacks(new Callback[] {NoOp.INSTANCE, new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                            throws Throwable {
                        Object result = proxy.invokeSuper(obj, args);
                        System.out.println("setting attribute : " + method.getName().replaceFirst("set", ""));
                        return result;
                    }
        }});
        enhancer.setCallbackFilter(setMethodFilter);
        return (T)enhancer.create();
    }

运行结果:com.arcserve.spog.users.UserDTO$$EnhancerByCGLIB$$4aaabb0
com.arcserve.spog.users.UserDTO$$EnhancerByCGLIB$$4aaabb0

现在仗剑走天涯,无边旮旯堰。

  • 下面再说下spring框架里面的aop代理。
    spring在aop的jar中给我们提供了一个ProxyFactory的工厂类。我们可以通过它很方便的实现动态代理。
public static void main(String[] args) {
        IUserService userService = new UserServiceImpl();
        IUserService userProxy = injectProxyHandler(userService);
        userProxy.login("Donald Trump", "******");
        userProxy.login("Ivanka Trump", "******");
    }

    public static <T> T injectProxyHandler(T serviceImpl) {
        ProxyFactory proxyFactory = new ProxyFactory(serviceImpl);
        proxyFactory.addAdvice(
                new MethodInterceptor() {
                    @Override
                    public Object invoke(MethodInvocation invocation) throws Throwable {
                        Method method = invocation.getMethod();
                        Object[] args = invocation.getArguments();
                        Object result = method.invoke(serviceImpl, args);
                        if("login".equals(method.getName()) && "Ivanka Trump".equalsIgnoreCase((String)args[0])) {
                            System.out.println("Ivanka Trump login time : " + new Date());
                        }
                        return result;
                    }
        });
        return (T)proxyFactory.getProxy();
    }

运行结果:20:09:50.237 [main] DEBUG org.springframework.aop.framework.JdkDynamicAopProxy - Creating JDK dynamic proxy: target source is SingletonTargetSource for target object [com.arcserve.spog.users.impl.UserServiceImpl@2a098129]
login username : Donald Trump
login username : Ivanka Trump
Ivanka Trump login time : Thu Jan 18 20:09:50 CST 2018
从运行结果的debug log中JdkDynamicAopProxy,我们能大体猜到这是用jdk的反射包实现的。
下面我们来试试对DTO进行代理。

public static void main(String[] args) {
        UserDTO user = injectProxyHandler(new UserDTO());
        user.setName("Trump");
        user.setAge(58);
        user.getEmail();
    }

    public static <T> T injectProxyHandler(T serviceImpl) {
        ProxyFactory proxyFactory = new ProxyFactory(serviceImpl);
        proxyFactory.addAdvice(
                new MethodInterceptor() {
                    @Override
                    public Object invoke(MethodInvocation invocation) throws Throwable {
                        Method method = invocation.getMethod();
                        Object[] args = invocation.getArguments();
                        Object result = method.invoke(serviceImpl, args);
                        if(method.getName().startsWith("set") ) {
                            System.out.println("setting attribute : " + method.getName().replaceFirst("set", ""));
                        }
                        return result;
                    }
        });
        return (T)proxyFactory.getProxy();
    }

运行结果:20:14:13.833 [main] DEBUG org.springframework.aop.framework.CglibAopProxy - Method is declared on Advised interface: public abstract java.lang.Class org.springframework.aop.TargetClassAware.getTargetClass()
setting attribute : Name
setting attribute : Age
从运行结果的debug log中CglibAopProxy ,我们能大体猜到这是用cglib来实现的。

下面我们可以看看ProxyFactory的相关源码:

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: " +
                        "Either an interface or a target is required for proxy creation.");
            }
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }
            return new ObjenesisCglibAopProxy(config);
        }
        else {
            return new JdkDynamicAopProxy(config);
        }
    }

如果target 是接口,那就把球踢给JdkDynamicAopProxy,否则踢给ObjenesisCglibAopProxy。

好了,罗嗦这么半天,全数慰情廖胜无。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值