以下内容的公开课视频已经录制, 需要的同学可留言
MyBatis 这个框架具有强大的灵活性, MyBatis 对持久层的操作就是借助于四大组件 (Executor,StatementHandler,ParameterHandler,ResultSetHandler), 在四大组件处提供了简单易用的插件扩展机制.
MyBatis 支持用插件对四大核心组件进行拦截, 对 MyBatis 来说插件就是拦截器, 用来增强核心组件的功能, 增强功能本质上是借助于底层的动态代理实现的, 换句话说, MyBatis 中的四大对象都是代理对象.
四大核心组件简介
MyBatis 四大核心组件:
ParameterHandler: 处理 SQL 的参数的类
ResultSetHandler: 处理 SQL 的返回结果集的类
StatementHandler: 处理数据库 SQL 语句的类
Executor: 用于执行增删改查操作的执行器类
MyBatis 插件原理
MyBatis 的插件借助于责任链的模式进行对拦截的处理
使用动态代理对目标对象进行包装, 达到拦截的目的
拦截
插件具体是如何拦截并附加额外的功能的呢? 以 ParameterHandler 来说
- public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain){
- ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
- parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
- return parameterHandler;
- }
- public Object pluginAll(Object target) {
- for (Interceptor interceptor : interceptors) {
- target = interceptor.plugin(target);
- }
- return target;
- }
interceptorChain 保存了所有的拦截器 (interceptors), 是 MyBatis 初始化的时候创建的.
调用拦截器链中的拦截器依次的对目标进行拦截或增强.
interceptor.plugin(target) 中的 target 就可以理解为 MyBatis 中的四大组件, 返回的 target 是被重重代理后的对象.
插件接口
MyBatis 插件接口 - Interceptor
intercept() 方法, 插件的核心方法
plugin() 方法, 生成 target 的代理对象
setProperties() 方法, 传递插件所需参数
插件实例
插件开发需要以下步骤
自定义插件需要实现上述接口
增加 @Intercepts 注解 (声明是哪个核心组件的插件, 以及对哪些方法进行扩展)
在 xml 文件中配置插件
**
* 插件签名, 告诉 MyBatis 插件用来拦截那个对象的哪个方法
- **/
- @Intercepts(
- {
- @Signature(
- type = StatementHandler.class,
- method = "parameterize",
- args = Statement.class
- )
- }
- )
- public class MyIntercepts implements Interceptor {
- /**
- * 拦截目标对象的目标方法
- *
- * @param invocation
- * @return
- * @throws Throwable
- */
- @Override
- public Object intercept(Invocation invocation) throws Throwable {
- System.out.println("进入自定义的拦截器, 拦截目标对象" + invocation + invocation.getMethod() + invocation.getTarget());
- return invocation.proceed();
- }
- /**
- * 包装目标对象 为目标对象创建代理对象
- *
- * @Param target 为要拦截的对象
- * @Return 代理对象
- */
- @Override
- public Object plugin(Object target) {
- System.out.println("自定义 plugin 方法, 将要包装的目标对象" + target.toString() + target.getClass());
- return Plugin.wrap(target, this);
- }
- /**
- * 获取配置文件的属性
- *
- * @param properties
- */
- @Override
- public void setProperties(Properties properties) {
- System.out.println("自定义插件的初始化参数" + properties);
- }
- }
在 mybatis-config.xml 中配置插件
- <!-- 自定义插件 -->
- <plugins>
- <plugin interceptor="com.boxuegu.javaee.mybatissourcelearn.MyIntercepts">
- <property name="test" value="testvalue"/>
- </plugin>
- </plugins>
调用查询方法, 查询方法会返回 ResultSet
- public class Test {
- public static void main(String[] args) {
- //1. 加载配置文件
- String resource = "mybatis-config.xml";
- InputStream inputStream = null;
- try {
- inputStream = Resources.getResourceAsStream(resource);
- } catch (IOException e) {
- e.printStackTrace();
- }
- //2. 获取 sqlSessionFactory
- SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
- //3. 获取 sqlSession
- SqlSession sqlSession = sqlSessionFactory.openSession();
- try {
- // 通过 xml 文件直接执行 sql 语句
- //Employee employee = sqlSession.selectOne("com.boxuegu.javaee.mybatissourcelearn.dao.EmployeeMapper.getEmployeeById", 1);
- //alt+shift+L introduce local variables;
- Thread thread = new Thread(()-> System.out.println("test"));
- //4. 获取 mapper 接口实现
- EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
- System.out.println("mapper::::" + mapper.getClass());
- //5. 执行 sql 语句
- Employee employee = mapper.getEmployeeById(1);
- System.out.println(employee);
- } finally {
- sqlSession.close();
- }
- }
- }
输出结果
自定义插件的初始化参数 {test=testvalue}
自定义 plugin 方法, 将要包装的目标对象 org.apache.ibatis.executor.CachingExecutor@46f5f779class org.apache.ibatis.executor.CachingExecutor
mapper::::class com.sun.proxy.$Proxy3
自定义 plugin 方法, 将要包装的目标对象 org.apache.ibatis.scripting.defaults.DefaultParameterHandler@1bc6a36eclass org.apache.ibatis.scripting.defaults.DefaultParameterHandler
自定义 plugin 方法, 将要包装的目标对象 org.apache.ibatis.executor.resultset.DefaultResultSetHandler@387c703bclass org.apache.ibatis.executor.resultset.DefaultResultSetHandler
自定义 plugin 方法, 将要包装的目标对象 org.apache.ibatis.executor.statement.RoutingStatementHandler@c39f790class org.apache.ibatis.executor.statement.RoutingStatementHandler
Wed Jun 19 18:14:24 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
进入自定义的拦截器, 拦截目标对象 org.apache.ibatis.plugin.Invocation@50f8360dpublic abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLExceptionorg.apache.ibatis.executor.statement.RoutingStatementHandler@c39f790
Employee{id=1, lastName='zhangsan', email='zhangsan@itcast.cn', gender='1'}
多插件开发
创建代理对象时, 按照插件配置的顺序进行包装
执行目标方法后, 是按照代理的逆向进行执行
小结
遵循插件尽量不使用的原则, 因为会修改底层设计
插件是生成的层层代理对象的责任链模式, 使用反射机制实现
插件的编写要考虑全面, 特别是多个插件层层代理的时候
来源: http://www.jianshu.com/p/32c3c2a80939