Mybatis Mapper
动态代理的功能:通过拦截器方法回调,对目标target方法进行增强。
言外之意就是为了增强目标target方法。上面这句话没错,但也不要认为它就是真理,殊不知,动态代理还有投鞭断流的霸权,连目标target都不要的科幻模式。

1. 自定义JDK动态代理之投鞭断流实现自动映射器Mapper

首先定义一个pojo。

  1. public class User {
  2. private Integer id;
  3. private String name;
  4. private int age;
  5. public User(Integer id, String name, int age) {
  6. this.id = id;
  7. this.name = name;
  8. this.age = age;
  9. }
  10. // getter setter
  11. }

再定义一个接口UserMapper.java。

  1. public interface UserMapper {
  2. public User getUserById(Integer id);
  3. }

接下来看看如何使用动态代理之投鞭断流,实现实例化接口并调用接口方法返回数据的。
自定义一个InvocationHandler。

  1. import java.lang.reflect.InvocationHandler;
  2. import java.lang.reflect.Method;
  3. import java.lang.reflect.Proxy;
  4. public class MapperProxy implements InvocationHandler {
  5. @SuppressWarnings("unchecked")
  6. public <T> T newInstance(Class<T> clz) {
  7. return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
  8. }
  9. @Override
  10. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  11. if (Object.class.equals(method.getDeclaringClass())) {
  12. try {
  13. // 诸如hashCode()、toString()、equals()等方法,将target指向当前对象this
  14. return method.invoke(this, args);
  15. } catch (Throwable t) {
  16. }
  17. }
  18. // 投鞭断流
  19. return new User((Integer) args[0], "zhangsan", 18);
  20. }
  21. }

上面代码中的target,在执行Object.java内的方法时,target被指向了this,target已经变成了傀儡、象征、占位符。在投鞭断流式的拦截时,已经没有了target。
写一个测试代码:

  1. public static void main(String[] args) {
  2. MapperProxy proxy = new MapperProxy();
  3. UserMapper mapper = proxy.newInstance(UserMapper.class);
  4. User user = mapper.getUserById(1001);
  5. System.out.println("ID:" + user.getId());
  6. System.out.println("Name:" + user.getName());
  7. System.out.println("Age:" + user.getAge());
  8. System.out.println(mapper.toString());
  9. }

output:

  1. ID:1001
  2. Name:zhangsan
  3. Age:18
  4. x.y.MapperProxy@6bc7c054

这便是Mybatis自动映射器Mapper的底层实现原理。

2. Mybatis自动映射器Mapper的源码分析

首先编写一个测试类:

  1. public static void main(String[] args) {
  2. SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
  3. try {
  4. StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
  5. List<Student> students = studentMapper.findAllStudents();
  6. for (Student student : students) {
  7. System.out.println(student);
  8. }
  9. } finally {
  10. sqlSession.close();
  11. }
  12. }

Mapper长这个样子:

  1. public interface StudentMapper {
  2. List<Student> findAllStudents();
  3. Student findStudentById(Integer id);
  4. void insertStudent(Student student);
  5. }

org.apache.ibatis.binding.MapperProxy.java部分源码。

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {
  2. private static final long serialVersionUID = -6424540398559729838L;
  3. private final SqlSession sqlSession;
  4. private final Class<T> mapperInterface;
  5. private final Map<Method, MapperMethod> methodCache;
  6. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  7. this.sqlSession = sqlSession;
  8. this.mapperInterface = mapperInterface;
  9. this.methodCache = methodCache;
  10. }
  11. @Override
  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  13. if (Object.class.equals(method.getDeclaringClass())) {
  14. try {
  15. return method.invoke(this, args);
  16. } catch (Throwable t) {
  17. throw ExceptionUtil.unwrapThrowable(t);
  18. }
  19. }
  20. // 投鞭断流
  21. final MapperMethod mapperMethod = cachedMapperMethod(method);
  22. return mapperMethod.execute(sqlSession, args);
  23. }
  24. // ...

org.apache.ibatis.binding.MapperProxyFactory.java部分源码。

  1. public class MapperProxyFactory<T> {
  2. private final Class<T> mapperInterface;
  3. @SuppressWarnings("unchecked")
  4. protected T newInstance(MapperProxy<T> mapperProxy) {
  5. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  6. }
  7. }

这便是Mybatis使用动态代理之投鞭断流

3. 接口Mapper内的方法能重载(overLoad)吗?(重要)

类似下面:

  1. public User getUserById(Integer id);
  2. public User getUserById(Integer id, String name);

答案:不能。
原因:在投鞭断流时,Mybatis使用package+Mapper+method全限名作为key,去xml内寻找唯一sql来执行的。类似:key=x.y.UserMapper.getUserById,那么,重载方法时将导致矛盾。对于Mapper接口,Mybatis禁止方法重载(overLoad)。