- 今天写了个大方法,功能强大,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。
好了,罗嗦这么半天,全数慰情廖胜无。