开年后上班这几天空闲时间较多,由于公司的技术栈在数据持久层这部分选择的是JOOQ,应该有不少人是第一次听说这玩意儿,一开始我也不知道这个,是来到公司后慢慢接触的,比起mybatis,他确实更简洁,没有繁琐的xml配置,但是我还是用不习惯,特别是涉及到多表操作的时候也要写一大堆,这时候就想着还不如直接些sql来得直接。于是又回头去温习了下mybatis。
好了废话不多说,先回顾一个问题,是关于动态代理的,这个知识点想必大家都很熟悉了,其作用也就无需多说 ,jdk自身支持了动态代理,但是有个前提是只能基于接口来实现,不知道大家有没有想过这个问题,先来个demo热热身。先定义一个接口,然后提供实现类
public interface UserService {
public void select();
public void update();
public String addr();
}
public class UserServiceImpl implements UserService {
@Override
public void select() {
System.out.println("开始查询");
}
@Override
public void update() {
System.out.println("开始更新");
}
@Override
public String addr() {
return "chengdu";
}
}
然后我们知道还需要一个接口InvocationHandler,无论是匿名对象还是工厂之类的实现,总之需要实现这个接口来完成对代理对象生成逻辑
public class JdkProxy {
/**
* 实际操作执行者,被代理的对象
*/
private final Object target;
public JdkProxy(Object target) {
this.target = target;
}
// 为目标生成代理对象并执行增强方法
public Object JdkProxy(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> {
before();
Object result = method.invoke(target,args);
after();
// 注意这个返回值
return result;
});
}
/**
* 调用 invoke()方法之前执行
*/
private void before() {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
/**
* 调用 invoke()方法之后执行
*/
private void after() {
System.out.println(LocalDate.now());
}
}
这里很简单,就是一个工厂形式的类,持有一个具体的被代理接口的实例,前后加上自己增强的逻辑,原方法则是通过Method.invoke()执行(反射相关的知识),关于这个invoke方法其实又可以说好大一堆了,jdk7新增了方法句柄MethodHadler
//
UserService service = new UserServiceImpl();
//增强
UserService proxy1 = (UserService) new JdkProxy(service).JdkProxy();
System.out.println(proxy1.addr());
这样就完成了一个简单又普通的代理增强过程,重点来了,请注意上面哪个invoke方法的返回值,正常是原样返回invoke的结果,然后这个例子会正常输出'chengdu',但是如果我更改了这个invoke的返回值
before();
Object result = method.invoke(target,args);
after();
// 注意这个返回值
return "dazhou";
我讲起改为了'dazhou',那么上面的例子就会输‘dazhou’而不是impl里面的'chengdu'了,看起来像是‘chengdu’被‘达州’覆盖了,那么说明动态代理之后的返回值是可以自己控制的,那么如果这样的话,可不可以不要接口实现类了呢。源码之下无秘密,首先将生成的代理类的源码打开,在上面的mian方法加上一行System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");,然后运行main方法就可以在目录下生成该代理对象的源码了,首先看继承关系
public final class $Proxy0 extends Proxy implements UserService
看到这个就应该知道为什么jdk动态代理只能给予接口实现了吧,因为java只能单继承,而生成的代理类会自动继承一个Proxy,同时还要保持原来的继承和实现关系不变,那就只能用接口了,然后我们找到自己增强的那个方法
public final String addr() throws {
try {
return (String)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
一目了然,我们生成的代理对象执行方法的具体链路就是:创建代理对象 -> 执行代理对象的addr() ->然后调用InvocationHandler的invoke ->反射执行原来的addr(),当然自己可以随意在前后进行自定义增强 ->最后返回的执行结果其实跟原方法的返回结果没有必然关系,只是一般情况都会原样返回而已,可以随时覆盖的,那么再思考一个问题,既然原有结果不一定非得存在,那是不是意味着,具体的实现方法或者是实现类也没必要一定存在了,试验一下,看是否可行
// 如果没有实现类,只有接口 ,返回的仅仅是invoke的结果
UserService proxy2 = (UserService)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserService.class}, (proxy, method, args1) -> {
System.out.println("没有实现类");
return "dazhou";
});
System.out.println(proxy2.addr());
我这里直接通过匿名内部类实现了InvocationHandler,这里面也没有包含任何目标对象的引用,执行main方法之后还是会正常打印'dazhou',说明借口没有实现类也能完成动态代理,因为在执行增强方法之前已经自动生成了一个代理类实现了这个接口了。ok到了这里再来回想一下mybatis,我们平时用的时候是不是只写了接口,没错,mybatis就是这么玩的,也是使用了jdk动态代理这个特性,因为sql这些已经写在xml里面了,解析出来就能直接执行得到结果,通过代理对象直接返回就行了,再有个实现类无非是多此一举,来看一下
OaOverTimeDao mapper = sqlSession.getMapper(xxx.class);
最后会追中到这里来
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
结识一些,这里的mapperInterface就是我们传进来的那个xxx.class,所以这里就会生成xxx.class的代理对象,再来看看这个MapperProxy,他就实现了InvocationHandler,所以我们得到mapper之后执行的方法逻辑都在这里面,可以看到这里面就没有任何引用对象,跟我上面的例子一样,返回的就是自己定义的增强方法(其实这里不该叫增强方法,而是具体的实现),免去了实现类。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
你学废了吗。