拦截器顾名思义为拦截某个功能的一个武器,在众多框架中均有“拦截器”。这个Plugin有什么用呢?或者说拦截器有什么用呢?可以想想拦截器是怎么实现的。Plugin用到了Java中很重要的一个特性——动态代理。所以这个Plugin可以理解为,在调用一个方法时,我“拦截”其方法做一些我想让它做的事(包括方法的前与后)。在Mybatis中可以拦截以下方法:
这正是mybatis中大名鼎鼎的四大对象;
1 //ParameterHandler 处理sql的参数对象
2 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
3 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
4 //包装参数插件
5 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
6 return parameterHandler;
7 }
8
9 //ResultSetHandler 处理sql的返回结果集
10 public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
11 ResultHandler resultHandler, BoundSql boundSql) {
12 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
13 //包装返回结果插件
14 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
15 return resultSetHandler;
16 }
17
18 //StatementHandler 数据库的处理对象
19 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
20 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
21 //包装数据库执行sql插件
22 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
23 return statementHandler;
24 }
25
26 public Executor newExecutor(Transaction transaction) {
27 //创建Mybatis的执行器:Executor
28 return newExecutor(transaction, defaultExecutorType);
29 }
30
31 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
32 executorType = executorType == null ? defaultExecutorType : executorType;
33 executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
34 Executor executor;
35 //mybatis支持的三种执行器:batch、reuse、simple,其中默认支持的是simple
36 if (ExecutorType.BATCH == executorType) {
37 executor = new BatchExecutor(this, transaction);
38 } else if (ExecutorType.REUSE == executorType) {
39 executor = new ReuseExecutor(this, transaction);
40 } else {
//默认为SimpleExecutor
41 executor = new SimpleExecutor(this, transaction);
42 }
//如果开启二级缓存 则对executor进行缓存包装
43 if (cacheEnabled) {
44 executor = new CachingExecutor(executor);
45 }
46 //包装执行器插件
47 executor = (Executor) interceptorChain.pluginAll(executor);
48 return executor;
49 }
我们可以看到这四大对象 在创建的过程中 都调用了 (Executor) interceptorChain.pluginAll(Object target) 代码,该代码是怎么样的呢 正是我们所说的拦截器链,将四大对象传入到拦截器链进行处理 然后返回包装后的 四大对象 如果我们在拦截器链中进行拦截处理 则实现了拦截技术;
下面我们看连接器链中的内容:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
//在pluginAll方法中 遍历拦截器集 将传入的target 也就是四大对象进行传入 在interceptor中的Plugin方法中处理
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);
}
}
下面我们看一下interceptor对象的源代码:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
从源代码看出 interceptor是一个接口 接口中 有三个方法 分别是 intercept plugin 和 setProperties;下面分别介绍着几个方法;
我们自己写插件或者拦截四大对象后 进行相应功能的添加就在要实现该接口,然后实现接口的三个方法;
举例实现该接口:
注意: 记住必须使用 注解的方式实现声明拦截器拦截哪个类对象 原因在后面源码中进行分析
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }) })
public class ExamplePlugin implements Interceptor {
/* 此方法用于实现拦截逻辑
* @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
*/
@Override
public Object intercept(Invocation invocation) throws Throwable
{
我们看点在plugin方法中 通过Plugin.wrap创建了代理对象 我们来看源代码:看到了Plugin 类实现了 InvocationHandler 是不是感觉很熟悉 这就是我们上篇文章中讲过的 动态代理中的 invocationHandel类
前面说了为什么自己实现拦截器类时 为什么必须使用注解的方式 因为在获得数字签名Map的方法中 存在使用反射获得注解信息的方法
//获得Interceptor注解,@Signature中的type(要拦截的类),method(拦截类的方法)和args(拦截器用于这些类中)
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
所以如果没有添加注解方式 则会抛出 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName())
然后我们看一下wrap方法 该方法就是通过数据签名 然后看数字签名中的是否包含要拦截对象和方法 如果包含 则创建代理对象 返回代理对象
public class Plugin implements InvocationHandler {
private Object target; //目标对象
private Interceptor interceptor;//拦截器对象
private Map<Class<?>, Set<Method>> signatureMap;//目标对象方法签名
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = 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) {
//生成代理对象,Plugin对象为该代理对象的InvocationHandler
return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));
}
return target;
}
@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)) {
//调用代理类所属拦截器的intercept方法
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) {
//获得Interceptor注解,@Signature中的type(要拦截的类),method(拦截类的方法)和args(拦截器用于这些类中)
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());
}
//获得注解type,method 和args生成一个signature数组
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
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<Class<?>>();
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()]);
}
}
到这里 代理对象以及中间对象invocationHandler对象已经创建完成 现在我们看一下 被代理对象 被代理对象就是 statementHandler 该接口的子类 这类的是
SimpleStatementHandler
由此可以看到该类实现了 statementHandler接口的方法。
通过看这里的源码也可以看出 其实mysql的底层其实也是使用了 底层的jdbc来实现的!
public abstract class BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
//获取BoundSql
public BoundSql getBoundSql() {
return boundSql;
}
//获取ParameterHandler
public ParameterHandler getParameterHandler() {
return parameterHandler;
}
//准备语句
public Statement prepare(Connection connection) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//实例化Statement
statement = instantiateStatement(connection);
//设置超时
setStatementTimeout(statement);
//设置读取条数
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
//如何实例化Statement,交给子类去做
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
//设置超时,实际就是调用Statement.setQueryTimeout
protected void setStatementTimeout(Statement stmt) throws SQLException {
Integer timeout = mappedStatement.getTimeout();
Integer defaultTimeout = configuration.getDefaultStatementTimeout();
if (timeout != null) {
stmt.setQueryTimeout(timeout);
} else if (defaultTimeout != null) {
stmt.setQueryTimeout(defaultTimeout);
}
}
//设置读取条数,其实就是调用Statement.setFetchSize
protected void setFetchSize(Statement stmt) throws SQLException {
Integer fetchSize = mappedStatement.getFetchSize();
if (fetchSize != null) {
stmt.setFetchSize(fetchSize);
}
}
//关闭Statement
protected void closeStatement(Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
//ignore
}
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
}
public class SimpleStatementHandler extends BaseStatementHandler {
public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
@Override
public void batch(Statement statement) throws SQLException {
String sql = boundSql.getSql();
statement.addBatch(sql);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
}
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
if (mappedStatement.getResultSetType() != null) {
return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.createStatement();
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
// N/A
}
}