Loading

MyBatis动态代理执行原理

前言

        大家使用MyBatis都知道,不管是单独使用还是和Spring集成,我们都是使用接口定义的方式声明数据库的增删改查方法。那么我们只声明一个接口,MyBatis是如何帮我们来实现SQL呢,对吗,我们的sql是定义在/resources/mapper/mybatis下。每个单独的xml文件都有一个id和接口里的方法一一对应。这里的对应关系就是mybatis框架帮我们做的工作。

         这里的关键类分为两部分:SqlSessionFactory、SqlSession、MapperProxy。 

 

1、MapperProxy

       这个类乍一看有点陌生,那我们就先从调用入口出发来分析这个类。

 SqlSession sqlSession=sqlSessionFactory.openSession();
 StudentDao studentDao=sqlSession.getMapper(StudentDao.class);

 Student student=studentDao.getStudent(1);

 这里看到调用sqlSession.getMapper

public <T> T getMapper(Class<T> type) {
     return this.configuration.getMapper(type, this);
}

 SqlSession本身不做任何事情,直接把任务甩给Configuration。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
     return this.mapperRegistry.getMapper(type, sqlSession);
}

 Configuration又把任务甩给MapperRegistry。 从类名就能看出来它的作用是用来注册接口和生成代理类实例的工具类。 从getMapper里看到先获取MapperProxyFactory,如果未空则抛异常。然后继续调用mapperProxyFactory.newInstance()。

     这里我们看到mybatis中变量太多的话可以使用var1、var2。。。是个好办法哦。

public class MapperRegistry {
    private final Configuration config;
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

    public MapperRegistry(Configuration config) {
        this.config = config;
    }

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if(mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

    public <T> boolean hasMapper(Class<T> type) {
        return this.knownMappers.containsKey(type);
    }

    public <T> void addMapper(Class<T> type) {
        if(type.isInterface()) {
            if(this.hasMapper(type)) {
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if(!loadCompleted) {
                    this.knownMappers.remove(type);
                }

            }
        }

    }

    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(this.knownMappers.keySet());
    }

    public void addMappers(String packageName, Class<?> superType) {
        ResolverUtil resolverUtil = new ResolverUtil();
        resolverUtil.find(new IsA(superType), packageName);
        Set mapperSet = resolverUtil.getClasses();
        Iterator var5 = mapperSet.iterator();

        while(var5.hasNext()) {
            Class mapperClass = (Class)var5.next();
            this.addMapper(mapperClass);
        }

    }

    public void addMappers(String packageName) {
        this.addMappers(packageName, Object.class);
    }
}

  MapperProxyFactory是创建Mapper代理类的工厂类。 这个类里看到两个newInstance方法。第一个是直接创建一个代理类并返回。第二类是创建一个MapperProxy类然后调用第一个newInstance方法。这里绕了一大圈,终于看到MapperProxy的影子了。调用链实在太长,继续往下看。

public class MapperProxyFactory<T> {
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

    public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    public Class<T> getMapperInterface() {
        return this.mapperInterface;
    }

    public Map<Method, MapperMethod> getMethodCache() {
        return this.methodCache;
    }

    protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}

  MapperProxy这个类实现了jdk动态代理接口InvocationHandler。在invoke方法中实现代理方法调用的细节。 到这里说它是关键类还没看到有sql蛛丝马迹的地方。那只能继续往下看,这里看到在invoke方法里先获取MapperMethod类,然后调用mapperMethod.execute()。 

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if(Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if(this.isDefaultMethod(method)) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }

    private MapperMethod cachedMapperMethod(Method method) {
        MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
        if(mapperMethod == null) {
            mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
            this.methodCache.put(method, mapperMethod);
        }

        return mapperMethod;
    }

    @UsesJava7
    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args) throws Throwable {
        Constructor constructor = Lookup.class.getDeclaredConstructor(new Class[]{Class.class, Integer.TYPE});
        if(!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }

        Class declaringClass = method.getDeclaringClass();
        return ((Lookup)constructor.newInstance(new Object[]{declaringClass, Integer.valueOf(2)})).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

    private boolean isDefaultMethod(Method method) {
        return (method.getModifiers() & 1033) == 1 && method.getDeclaringClass().isInterface();
    }
}

  MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装增删改查操作,也就是我们在xml中配置的select、update、delete、insert节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数,返回类型。

   

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

    public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        switch(null.$SwitchMap$org$apache$ibatis$mapping$SqlCommandType[this.command.getType().ordinal()]) {
        case 1:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case 2:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case 3:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case 4:
            if(this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if(this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if(this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if(this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case 5:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if(result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method \'" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

    private Object rowCountResult(int rowCount) {
        Object result;
        if(this.method.returnsVoid()) {
            result = null;
        } else if(!Integer.class.equals(this.method.getReturnType()) && !Integer.TYPE.equals(this.method.getReturnType())) {
            if(!Long.class.equals(this.method.getReturnType()) && !Long.TYPE.equals(this.method.getReturnType())) {
                if(!Boolean.class.equals(this.method.getReturnType()) && !Boolean.TYPE.equals(this.method.getReturnType())) {
                    throw new BindingException("Mapper method \'" + this.command.getName() + "\' has an unsupported return type: " + this.method.getReturnType());
                }

                result = Boolean.valueOf(rowCount > 0);
            } else {
                result = Long.valueOf((long)rowCount);
            }
        } else {
            result = Integer.valueOf(rowCount);
        }

        return result;
    }

    private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
        MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(this.command.getName());
        if(Void.TYPE.equals(((ResultMap)ms.getResultMaps().get(0)).getType())) {
            throw new BindingException("method " + this.command.getName() + " needs either a @ResultMap annotation, a @ResultType annotation, or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
        } else {
            Object param = this.method.convertArgsToSqlCommandParam(args);
            if(this.method.hasRowBounds()) {
                RowBounds rowBounds = this.method.extractRowBounds(args);
                sqlSession.select(this.command.getName(), param, rowBounds, this.method.extractResultHandler(args));
            } else {
                sqlSession.select(this.command.getName(), param, this.method.extractResultHandler(args));
            }

        }
    }

    private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        List result;
        if(this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectList(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectList(this.command.getName(), param);
        }

        return !this.method.getReturnType().isAssignableFrom(result.getClass())?(this.method.getReturnType().isArray()?this.convertToArray(result):this.convertToDeclaredCollection(sqlSession.getConfiguration(), result)):result;
    }

    private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        Cursor result;
        if(this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectCursor(this.command.getName(), param, rowBounds);
        } else {
            result = sqlSession.selectCursor(this.command.getName(), param);
        }

        return result;
    }

    private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
        Object collection = config.getObjectFactory().create(this.method.getReturnType());
        MetaObject metaObject = config.newMetaObject(collection);
        metaObject.addAll(list);
        return collection;
    }

    private <E> Object convertToArray(List<E> list) {
        Class arrayComponentType = this.method.getReturnType().getComponentType();
        Object array = Array.newInstance(arrayComponentType, list.size());
        if(!arrayComponentType.isPrimitive()) {
            return list.toArray((Object[])((Object[])array));
        } else {
            for(int i = 0; i < list.size(); ++i) {
                Array.set(array, i, list.get(i));
            }

            return array;
        }
    }

    private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
        Object param = this.method.convertArgsToSqlCommandParam(args);
        Map result;
        if(this.method.hasRowBounds()) {
            RowBounds rowBounds = this.method.extractRowBounds(args);
            result = sqlSession.selectMap(this.command.getName(), param, this.method.getMapKey(), rowBounds);
        } else {
            result = sqlSession.selectMap(this.command.getName(), param, this.method.getMapKey());
        }

        return result;
    }

    public static class MethodSignature {
        private final boolean returnsMany;
        private final boolean returnsMap;
        private final boolean returnsVoid;
        private final boolean returnsCursor;
        private final Class<?> returnType;
        private final String mapKey;
        private final Integer resultHandlerIndex;
        private final Integer rowBoundsIndex;
        private final ParamNameResolver paramNameResolver;

        public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
            Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
            if(resolvedReturnType instanceof Class) {
                this.returnType = (Class)resolvedReturnType;
            } else if(resolvedReturnType instanceof ParameterizedType) {
                this.returnType = (Class)((ParameterizedType)resolvedReturnType).getRawType();
            } else {
                this.returnType = method.getReturnType();
            }

            this.returnsVoid = Void.TYPE.equals(this.returnType);
            this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
            this.returnsCursor = Cursor.class.equals(this.returnType);
            this.mapKey = this.getMapKey(method);
            this.returnsMap = this.mapKey != null;
            this.rowBoundsIndex = this.getUniqueParamIndex(method, RowBounds.class);
            this.resultHandlerIndex = this.getUniqueParamIndex(method, ResultHandler.class);
            this.paramNameResolver = new ParamNameResolver(configuration, method);
        }

        public Object convertArgsToSqlCommandParam(Object[] args) {
            return this.paramNameResolver.getNamedParams(args);
        }

        public boolean hasRowBounds() {
            return this.rowBoundsIndex != null;
        }

        public RowBounds extractRowBounds(Object[] args) {
            return this.hasRowBounds()?(RowBounds)args[this.rowBoundsIndex.intValue()]:null;
        }

        public boolean hasResultHandler() {
            return this.resultHandlerIndex != null;
        }

        public ResultHandler extractResultHandler(Object[] args) {
            return this.hasResultHandler()?(ResultHandler)args[this.resultHandlerIndex.intValue()]:null;
        }

        public String getMapKey() {
            return this.mapKey;
        }

        public Class<?> getReturnType() {
            return this.returnType;
        }

        public boolean returnsMany() {
            return this.returnsMany;
        }

        public boolean returnsMap() {
            return this.returnsMap;
        }

        public boolean returnsVoid() {
            return this.returnsVoid;
        }

        public boolean returnsCursor() {
            return this.returnsCursor;
        }

        private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
            Integer index = null;
            Class[] argTypes = method.getParameterTypes();

            for(int i = 0; i < argTypes.length; ++i) {
                if(paramType.isAssignableFrom(argTypes[i])) {
                    if(index != null) {
                        throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
                    }

                    index = Integer.valueOf(i);
                }
            }

            return index;
        }

        private String getMapKey(Method method) {
            String mapKey = null;
            if(Map.class.isAssignableFrom(method.getReturnType())) {
                MapKey mapKeyAnnotation = (MapKey)method.getAnnotation(MapKey.class);
                if(mapKeyAnnotation != null) {
                    mapKey = mapKeyAnnotation.value();
                }
            }

            return mapKey;
        }
    }

    public static class SqlCommand {
        private final String name;
        private final SqlCommandType type;

        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
            String statementName = mapperInterface.getName() + "." + method.getName();
            MappedStatement ms = null;
            if(configuration.hasStatement(statementName)) {
                ms = configuration.getMappedStatement(statementName);
            } else if(!mapperInterface.equals(method.getDeclaringClass())) {
                String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
                if(configuration.hasStatement(parentStatementName)) {
                    ms = configuration.getMappedStatement(parentStatementName);
                }
            }

            if(ms == null) {
                if(method.getAnnotation(Flush.class) == null) {
                    throw new BindingException("Invalid bound statement (not found): " + statementName);
                }

                this.name = null;
                this.type = SqlCommandType.FLUSH;
            } else {
                this.name = ms.getId();
                this.type = ms.getSqlCommandType();
                if(this.type == SqlCommandType.UNKNOWN) {
                    throw new BindingException("Unknown execution method for: " + this.name);
                }
            }

        }

        public String getName() {
            return this.name;
        }

        public SqlCommandType getType() {
            return this.type;
        }
    }

    public static class ParamMap<V> extends HashMap<String, V> {
        private static final long serialVersionUID = -2212268410512043556L;

        public ParamMap() {
        }

        public V get(Object key) {
            if(!super.containsKey(key)) {
                throw new BindingException("Parameter \'" + key + "\' not found. Available parameters are " + this.keySet());
            } else {
                return super.get(key);
            }
        }
    }
}

  OK。整个动态代理一次完整的调用就结束了,可以看到最后的核心还是回到sqlsession。

 

2、SqlSessionFactory、SqlSession

    从上面这个一大圈调用流程来看sqlsession才是关键吗,它才是封装sql操作的核心人物,那就来看看他是怎么生成的,我们调用的实现类又是那个呢?

sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);

 通常我们是这样先来获取sqlsessionfactory,它是怎么来的? 

SqlSessionFacotryBuild()

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder e = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(e.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                reader.close();
            } catch (IOException var13) {
                ;
            }

        }

        return var5;
    }

public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

 这里看到先去解析xml配置文件,然后保存到Configuration对象里。 最后创建一个DefaultSqlSessionFactory。  有了SqlSessionFactory再来看看SqlSession。

 

SqlSession sqlSession=sqlSessionFactory.openSession();

DefaultSqlSessionFactory

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
            Environment e = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(e);
            tx = transactionFactory.newTransaction(e.getDataSource(), level, autoCommit);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

    private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
        DefaultSqlSession var8;
        try {
            boolean e;
            try {
                e = connection.getAutoCommit();
            } catch (SQLException var13) {
                e = true;
            }

            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            Transaction tx = transactionFactory.newTransaction(connection);
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, e);
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var14, var14);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

  这里看到两个方法最后都返回了DefaultSqlSession类,也就是说最后你的工作都由DefaultSqlSession来完成。 

 

总结

        看了这两个关键类的分析,其实我们还遗漏两个比较低调的类Configuration、SimpleExecutor(实现了接口Executor)。通常我们说的SqlSession来完成我们的工作最后都要落实在执行器上,执行器是对Statement的封装。SqlSession也就是个调度的角色吧。Configuration类帮我们解析xml文件并保存其中的配置信息,在框架流转的过程中使用。

 

posted @ 2018-12-23 17:18  歪头儿在北京  阅读(3601)  评论(1编辑  收藏  举报