MyBatis插件机制分析

2020-01-28  本文已影响0人  山东大葱哥

以下内容的公开课视频已经录制,需要的同学可留言

MyBatis这个框架具有强大的灵活性,MyBatis对持久层的操作就是借助于四大组件(ExecutorStatementHandlerParameterHandlerResultSetHandler),在四大组件处提供了简单易用的插件扩展机制。

MyBatis支持用插件对四大核心组件进行拦截,对MyBatis来说插件就是拦截器,用来增强核心组件的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。

四大核心组件简介

MyBatis四大核心组件:

-ParameterHandler:处理SQL的参数的类
-ResultSetHandler:处理SQL的返回结果集的类
-StatementHandler:处理数据库SQL语句的类
-Executor:用于执行增删改查操作的执行器类

MyBatis插件原理

1.MyBatis的插件借助于责任链的模式进行对拦截的处理
2.使用动态代理对目标对象进行包装,达到拦截的目的

拦截

插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler来说

publicParameterHandlernewParameterHandler(MappedStatementmappedStatement,Objectobject,BoundSqlsql,InterceptorChaininterceptorChain){
ParameterHandlerparameterHandler=mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);
returnparameterHandler;
}
publicObjectpluginAll(Objecttarget){
for(Interceptorinterceptor:interceptors){
target=interceptor.plugin(target);
}
returntarget;
}

interceptorChain保存了所有的拦截器(interceptors),是MyBatis初始化的时候创建的。

调用拦截器链中的拦截器依次的对目标进行拦截或增强。

interceptor.plugin(target)中的target就可以理解为MyBatis中的四大组件,返回的target是被重重代理后的对象。

插件接口

MyBatis插件接口-Interceptor

1.intercept()方法,插件的核心方法
2.plugin()方法,生成target的代理对象
3.setProperties()方法,传递插件所需参数

插件实例

插件开发需要以下步骤
1.自定义插件需要实现上述接口
2.增加@Intercepts注解(声明是哪个核心组件的插件,以及对哪些方法进行扩展)
3.在xml文件中配置插件

**
*插件签名,告诉MyBatis插件用来拦截那个对象的哪个方法
**/
@Intercepts(
{
@Signature(
type=StatementHandler.class,
method="parameterize",
args=Statement.class
)
}
)
publicclassMyInterceptsimplementsInterceptor{
/**
*拦截目标对象的目标方法
*
*@paraminvocation
*@return
*@throwsThrowable
*/
@Override
publicObjectintercept(Invocationinvocation)throwsThrowable{
System.out.println("进入自定义的拦截器,拦截目标对象"+invocation+invocation.getMethod()+invocation.getTarget());
returninvocation.proceed();
}

/**
*包装目标对象为目标对象创建代理对象
*
*@Paramtarget为要拦截的对象
*@Return代理对象
*/
@Override
publicObjectplugin(Objecttarget){
System.out.println("自定义plugin方法,将要包装的目标对象"+target.toString()+target.getClass());
returnPlugin.wrap(target,this);
}

/**
*获取配置文件的属性
*
*@paramproperties
*/
@Override
publicvoidsetProperties(Propertiesproperties){
System.out.println("自定义插件的初始化参数"+properties);
}
}

在mybatis-config.xml中配置插件

    <!--自定义插件-->
<plugins>
<plugininterceptor="com.boxuegu.javaee.mybatissourcelearn.MyIntercepts">
<propertyname="test"value="testvalue"/>
</plugin>
</plugins>

调用查询方法,查询方法会返回ResultSet

publicclassTest{
publicstaticvoidmain(String[]args){
//1.加载配置文件
Stringresource="mybatis-config.xml";
InputStreaminputStream=null;
try{
inputStream=Resources.getResourceAsStream(resource);
}catch(IOExceptione){
e.printStackTrace();
}

//2.获取sqlSessionFactory
SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);

//3.获取sqlSession
SqlSessionsqlSession=sqlSessionFactory.openSession();
try{
//通过xml文件直接执行sql语句
//Employeeemployee=sqlSession.selectOne("com.boxuegu.javaee.mybatissourcelearn.dao.EmployeeMapper.getEmployeeById",1);
//alt+shift+Lintroducelocalvariables;
Threadthread=newThread(()->System.out.println("test"));
//4.获取mapper接口实现
EmployeeMappermapper=sqlSession.getMapper(EmployeeMapper.class);
System.out.println("mapper::::"+mapper.getClass());

//5.执行sql语句
Employeeemployee=mapper.getEmployeeById(1);
System.out.println(employee);
}finally{
sqlSession.close();
}
}
}

输出结果

自定义插件的初始化参数{test=testvalue}
自定义plugin方法,将要包装的目标对象org.apache.ibatis.executor.CachingExecutor@46f5f779classorg.apache.ibatis.executor.CachingExecutor
mapper::::classcom.sun.proxy.$Proxy3
自定义plugin方法,将要包装的目标对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@1bc6a36eclassorg.apache.ibatis.scripting.defaults.DefaultParameterHandler
自定义plugin方法,将要包装的目标对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@387c703bclassorg.apache.ibatis.executor.resultset.DefaultResultSetHandler
自定义plugin方法,将要包装的目标对象org.apache.ibatis.executor.statement.RoutingStatementHandler@c39f790classorg.apache.ibatis.executor.statement.RoutingStatementHandler
WedJun1918:14:24CST2019WARN:EstablishingSSLconnectionwithoutserver'sidentityverificationisnotrecommended.AccordingtoMySQL5.5.45+,5.6.26+and5.7.6+requirementsSSLconnectionmustbeestablishedbydefaultifexplicitoptionisn'tset.ForcompliancewithexistingapplicationsnotusingSSLtheverifyServerCertificatepropertyissetto'false'.YouneedeithertoexplicitlydisableSSLbysettinguseSSL=false,orsetuseSSL=trueandprovidetruststoreforservercertificateverification.
进入自定义的拦截器,拦截目标对象org.apache.ibatis.plugin.Invocation@50f8360dpublicabstractvoidorg.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement)throwsjava.sql.SQLExceptionorg.apache.ibatis.executor.statement.RoutingStatementHandler@c39f790
Employee{id=1,lastName='zhangsan',email='zhangsan@itcast.cn',gender='1'}

多插件开发

1.创建代理对象时,按照插件配置的顺序进行包装
2.执行目标方法后,是按照代理的逆向进行执行

小结

1.遵循插件尽量不使用的原则,因为会修改底层设计

2.插件是生成的层层代理对象的责任链模式,使用反射机制实现

3.插件的编写要考虑全面,特别是多个插件层层代理的时候

上一篇下一篇

猜你喜欢

热点阅读