Interceptor :
Intercepts:
Signature:
InterceptorChain:
Invocation:
Plugin:
PluginException:
动态代理
一定要动态代理相关的知识搞清楚.动态代理搞清楚了,看mybatis的plugin源码,就会如鱼得水.
什么是插件?
其实插件更准确的叫法应该叫扩展点吧.哪些类和哪些方法可以被扩展,都是事先定义好的.用户如何需要扩展,那么就按照规范,实现扩展的方法.
像下面的这个简单的demo,我们支持Map的get是根据一个key,get相关的value,如果你要写一个变态的方法,不管你get什么都返回"Always",那么你只要实现一个intercept,我们看到Invocation这个类,这个类是源码里面被拦截的方法的调用参数相关信息,像返回"Always"这个拦截就没有使用invocation,正常的拦截方法是,我们在invocation.proceed前搞点事情,或者要统计耗时,在proceed前后一起搞点事情.
我们先看看最简单的用法.
我们要拦截的对象是Map的get方法,参数随意.当我们写单元测试get的时候,不管get什么都会返回Always.
因为我们的动态代理已经重写Map的get方法.
@Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
public static class AlwaysMapPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
//只是这个类没有使用invocation,
return "Always";
}
}
@Test
void mapPluginShouldInterceptGet() {
Map map = new HashMap();
map = (Map) new AlwaysMapPlugin().plugin(map);
assertEquals("Always", map.get("Anything"));
}
那么我们再来看看pagehelp是怎么使用的?
@Intercepts(
{
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
}
)
public class PageInterceptor implements Interceptor {
}
拦截了哪些方法是不是一目了然.
然后我们再一步一步的往下跟踪吧.
这里面会拼接limit参数.
ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql, CacheKey cacheKey) throws SQLException {
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(***) throws SQLException {
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}
y);
//调用方言获取分页 sql.这里已经有limit了.
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
}
//执行分页查询.参数拼装好了,交给executor去执行了.
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}
MySqlDialect.getPageSql.
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (page.getStartRow() == 0) {
sqlBuilder.append("\n LIMIT ? ");
} else {
sqlBuilder.append("\n LIMIT ?, ? ");
}
return sqlBuilder.toString();
}
如果我们要自己写个分库分表的组件,是不是也很容易实现了呢?
相关类分析
Interceptor
Object intercept(Invocation invocation) throws Throwable;
//target:需要拦截的方法,给target生成动态代理
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin
其实我们不需要看这个类,只看第一个demo,就应该知道Plugin肯定是实现了动态代理无非就是用的是jdk的还是cglib.
/**
* 这个类是继承了InvocationHandler的
*
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
//目标类
private final Object target;
//拦截器逻辑
private final Interceptor interceptor;
//方法签名
private final Map<Class<?>, Set<Method>> signatureMap;
//私有的构造器,不暴露出去.只有wrap才有权限调用.
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
//生成代理对象
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//生成代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
//返回原对象,不生成代理
return target;
}
//在 invoke() 方法中,Plugin 会检查当前要执行的方法是否在 signatureMap 集合中,
//如果在其中的话,表示当前待执行的方法是我们要拦截的目标方法之一,也就会调用 intercept() 方法执行代理逻辑;
//如果未在其中的话,则表示当前方法不应被代理,直接执行当前的方法即可。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//
if (methods != null && methods.contains(method)) {
//最终会执行用户传入的拦截方法
return interceptor.intercept(new Invocation(target, method, args));
}
//直接调用.
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
InterceptorChain
看名字就知道用了责任链这个设计模式.实现就很简单了,一个list拦截器集合,然后遍历这个集合,一个一个的给这个target生成动态代理.
然后看到获取拦截器的时候,返回的是一个不可变list,代码就更安全了,不会被外部随意的添加危险代码.
public class InterceptorChain {
//拦截器集合
private final List<Interceptor> interceptors = new ArrayList<>();
//核心:
public Object pluginAll(Object target) {
//
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//添加拦截器
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
//获取拦截器.
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
mybatis里plugin再哪里装配的?
我们看一下这个调用方法,有四个地方,刚好是mybatis的四大对象.
//下面几个方法是四大对象的创建过程:我们可以发现,这几个类对象的创建过程都调用了 InteceptorChain 的 pluginAll() 方法。
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
//生成对象
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//加入插件
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
plugin包里就这几个类,但是实现了及其灵活的扩展性,这个包虽然类很少,但是类的设计和类里面的代码都及其的优雅,各位读者们,赶快去阅读下大牛的代码吧.