目录
插件原理
/**
* 插件原理
* 在四大对象创建的时候
* 1、每个创建出来的对象不是直接返回的,而是
* interceptorChain.pluginAll(parameterHandler);包装后,再返回的。
* 2、获取到所有的Interceptor(拦截器)(插件需要实现的接口);
* 调用interceptor.plugin(target);返回target包装后的对象
* 3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
* 我们的插件可以为四大对象创建出代理对象;
* 代理对象就可以拦截到四大对象的每一个执行;
*
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
*/
项目DEMO
Mybatis全局配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--plugins:注册插件 -->
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 将我们写好的sql映射文件(EmployeeMapper.xml)一定要注册到全局配置文件(mybatis-config.xml)中 -->
<mappers>
<mapper resource="EmployeeMapper.xml" />
</mappers>
</configuration>
Mybatis映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
<select id="getEmpById" resultType="com.atguigu.mybatis.bean.Employee">
select id,last_name lastName,email,gender from tbl_employee where id = #{id}
</select>
</mapper>
实体类
package com.atguigu.mybatis.bean;
public class Employee {
private Integer id;
private String lastName;
private String email;
private String gender;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Employee [id=" + id + ", lastName=" + lastName + ", email="
+ email + ", gender=" + gender + "]";
}
}
Mapper接口
package com.atguigu.mybatis.dao;
import com.atguigu.mybatis.bean.Employee;
public interface EmployeeMapper {
public Employee getEmpById(Integer id);
}
编写一个Mybatis插件
插件编写步骤
1、编写Interceptor的实现类
2、使用@Intercepts注解完成插件签名
3、将写好的插件注册到全局配置文件中
新建自定插件类
package com.atguigu.mybatis.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
/**
* @Intercepts 插件签名
* 告诉Mybatis用当前插件来拦截那个对象的哪个方法
*/
@Intercepts({@Signature(type= StatementHandler.class,method="parameterize",args= Statement.class)})
public class MyFirstPlugin implements Interceptor {
/**
* 拦截目标方法的执行
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//执行目标方法 直接被放行
System.out.println("MyFirstPlugin......intercept:"+invocation.getMethod());
Object proceed = invocation.proceed();
return proceed;
}
/**
* 包装目标对象,也就是为目标对象创建一个代理对象
* @param o
* @return
*/
@Override
public Object plugin(Object o) {
//借助plugin的wrap方法来使用当前Interceptor包装目标对象
System.out.println("MyFirstPlugin......plugin : Mybatis将要包装的对象" + o);
Object wrap = Plugin.wrap(o, this);
//返回创建的动态代理
return wrap;
}
/**
* 将插件注册时的properties的属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件的配置信息:"+properties);
}
}
测试类
package com.atguigu.mybatis.test;
import java.io.IOException;
import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import com.atguigu.mybatis.bean.Employee;
import com.atguigu.mybatis.dao.EmployeeMapper;
import org.junit.Test;
public class MyBatisTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
return new SqlSessionFactoryBuilder().build(inputStream);
}
@Test
public void test01() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee employee = mapper.getEmpById(1);
System.out.println(mapper);
System.out.println(employee);
} finally {
openSession.close();
}
}
}
测试结果
源码分析
在我们编写的插件测试类中进行Debug
变量signatureMap中存放的我们定义的插件签名和对应的方法,也就是使用当前插件拦截哪个类,哪个方法。变量type:目标对象的类型,当前是CacheExecutor对象。getAllInterfaces()判断type对象是不是在插件签名中。当前插件签名只拦截StatementHandler对象,所以当前不会反悔目标对象,直接返回目标对象。
同样继续往下执行到DefaultParameterHandler对象,不在插件签名内,返回目标对象,不返回代理对象。
同样继续往下执行到DefaultResultHandler对象,不在插件签名中,不返回代理对象,返回目标对象。
执行到StatementHandler对象,确定是插件签名,返回目标对象的代理对象。最后再newStatementHandler(),创建StatementHandler对象时,实际返回的是RoutingStatementHandler对象。
返回了目前的代理对象,然后执行目标方法:handler.parameterize(stmt);进入该方法,进入Plugins类中的invoke方法,最后就会进入MyFirstPlugin类的intercept方法。只有调用这个invocation.proceed()方法,才能对四大对象真正调用的方法进行拦截。所以通过这个我们可以在目标方法执行前后将参数修改一下。这样就可以介入mybatis的内部流程。
多个插件运行流程
再定义一个MySecondPlugin插件,同样拦截的是StatementHandler的parameterize方法。
新建MySecondPlugin插件
package com.atguigu.mybatis.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Statement;
import java.util.Properties;
@Intercepts({@Signature(type= StatementHandler.class,method="parameterize",args= Statement.class)})
public class MySecondPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MySecondPlugin.......intercept:");
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
System.out.println("MySecondPlugin......plugin : Mybatis将要包装的对象" + o);
Object wrap = Plugin.wrap(o, this);
return wrap;
}
@Override
public void setProperties(Properties properties) {
}
}
注册MySecondPlugin插件
<!--plugins:注册插件 -->
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin">
</plugin>
</plugins>
测试结果
可以看出:创建动态代理的时候,按照注册顺序创建层层代理对象。执行目标方法时,按照逆序方法执行。创建动态代理的时候,先创建StatementHandler的代理对象MyFirstPlugin,然后MySecondPlugin对象又对其进行包装代理。
开发插件
场景:动态改变SQL的运行参数 。查询1号员工,实际从数据库中查询的是3号员工。
修改MyFirstPlugin的intercept()方法:
@Override
public Object intercept(Invocation invocation) throws Throwable {
//执行目标方法 直接被放行
System.out.println("MyFirstPlugin......intercept:"+invocation.getMethod());
System.out.println("当前拦截的对象:"+invocation.getArgs());
//拿到StatementHandler->ParameterHandler===>parameterObjet
//拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(invocation.getTarget());
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数是"+value);
metaObject.setValue("parameterHandler.parameterObject",3);
Object proceed = invocation.proceed();
return proceed;
}
测试结果:sql语句用的参数是1 ,最后查询到的实际是3。