当前位置: 首页 > 科技观察

美团专访:为什么可以直接调用userMapper接口的方法?

时间:2023-03-22 14:49:02 科技观察

老规矩先上传案例代码,让大家更熟悉如何使用。看过Mybatis系列的朋友几乎都能记住这段代码。哈哈~是不是有点夸张了?不夸张,就是这行代码。publicclassMybatisApplication{publicstaticfinalStringURL="jdbc:mysql://localhost:3306/mblog";publicstaticfinalStringUSER="root";publicstaticfinalStringPASSWORD="123456";publicstaticvoidmain(String[]args){Stringresource="mybatis-config.xml";InputStreaminputStream=null;SqlSessionsqlSession=null;try{inputStream=Resources.getResourceAsStream(resource);SqlSessionFactorysqlSessionFactory=newSqlSessionFactoryBuilder().build(inputStream);sqlSession=sqlSessionFactory.openSession();//今天代码的主线UserMapperuserMapper=sqlMapper(UserMapper.get.class);System.out.println(userMapper.selectById(1));}catch(Exceptione){e.printStackTrace();}finally{try{inputStream.close();}catch(IOExceptione){e.printStackTrace();}sqlSession.close();}}看源码有什么用?通过对源码的学习,我们可以获得Mybatis的核心思想和框架设计,也可以获得设计模式的应用。在前两篇文章中,我们已经解析了Mybatis的配置文件来获取SqlSession。下面我们从SqlSession分析到userMapper:UserMapperuserMapper=sqlSession.getMapper(UserMapper.class);前面的文章已经知道,这里的sqlSession使用的是默认的实现类DefaultSqlSession。所以我们直接进入DefaultSqlSession的getMapper方法。//DefaultSqlSession中的privatefinalConfiguration配置;//type=UserMapper.class@OverridepublicTgetMapper(Classtype){returnconfiguration.getMapper(type,this);}这里有3个问题:问题1:getMapper返回的是什么对象?上面可以看到getMapper方法调用了Configuration中的getMapper方法。然后我们进入Configuration//protectedfinalMapperRegistrymapperRegistry=newMapperRegistry(this);////type=UserMapper.classpublicTgetMapper(Classtype,SqlSessionsqlSession){returnmapperRegistry.getMapper(type,sqlSession);}这里没什么都不做,继续调用MapperRegistry中的getMapper://MapperRegistry中的publicclassMapperRegistry{//主要存储配置信息privatefinalConfigurationconfig;//MapperProxyFactory映射privatefinalMap,MapperProxyFactory>knownMappers=newHashMap<>();//获取MapperProxy对象//type=UserMapper.class,session为当前sessionpublicTgetMapper(Classtype,SqlSessionsqlSession){//这里是get,然后是add或者putfinalMapperProxyFactorymapperProxyFactory=(MapperProxyFactory)knownMappers.get(type);if(mapperProxyFactory==null){thrownewBindingException("Type"+type+"MapperRegistry不知道。");}try{//创建实例returnmapperProxyFactory.newInstance(sqlSession);}catch(Exceptione){thrownewBindingException("Errorgettingmapperinstance.Cause:"+e,e);}}//解析配置文件时会调用该方法,//type=UserMapper.classpublicvoidaddMapper(Classtype){//判断type必须是一个接口,也就是说已经添加了Mapper接口if(type.isInterface()){//,那么抛出BindingExceptionif(hasMapper(type)){thrownewBindingException("Type"+type+"isalreadyknowntotheMapperRegistry.");}booleanloadCompleted=false;try{//添加knownMappersknownMappers.put(type,newMapperProxyFactory<>(type));//创建MapperAnnotationBuilder对象,解析Mapper注解配置MapperAnnotationBuilderparser=newMapperAnnotationBuilder(config,type);parser.parse();//标记加载完成loadCompleted=true;}finally{//如果加载未完成,从knownMappers中移除if(!loadCompleted){knownMappers.remove(type);}}}}}MapperProxyFactory对象保存的类mapper接口的对象是一个普通的类,没有任何逻辑。MapperProx中使用了两种设计模式yFactory类:单例模式methodCache(注册单例模式)。工厂模式getMapper()。继续看MapperProxyFactory的newInstance方法。publicclassMapperProxyFactory{privatefinalClassmapperInterface;privatefinalMapmethodCache=newConcurrentHashMap<>();publicMapperProxyFactory(ClassmapperInterface){this.mapperInterface=mapperInterface;}publicTnewInstance(SqlSessionsqlSession){/创建Mapper对象finalMapperProxymapperProxy=newMapperProxy<>(sqlSession,mapperInterface,methodCache);returnnewInstance(mapperProxy);}//最后创建一个带有JDK动态代理的对象并返回protectedTnewInstance(MapperProxymapperProxy){return(T)Proxy.newProxyInstance(mapperInterface.getClassLoader(),newClass[]{mapperInterface},mapperProxy);}}从代码可以看出,还是基于JDKProxy实现的,InvocationHandler参数是MapperProxy对象。//UserMapper的类加载器//接口是UserMapper//h是mapperProxy对象publicstaticObjectnewProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandlerh){}问题2:为什么他的方法可以调用?上面调用newInstance方法的时候会创建MapperProxy对象,作为newProxyInstance的第三个参数,所以MapperProxy类必须实现InvocationHandler。进入MapperProxy类中://果然实现了InvocationHandler接口publicclassMapperProxyimplementsInvocationHandler,Serializable{privatestaticfinallongserialVersionUID=-6424540398559729838L;privatefinalSqlSessionsqlSession;privatefinalClassmapperInterface;privatefinalMapmethodCache;publicMapperProxy(SqlSessionsqlSession,ClassmapperInterface,MapmethodCache){this.sqlSession=sqlSession;this.mapperInterface=mapperInterface;this.methodCache=methodCache;}//调用userMapper.selectById()本质上就是调用这个invoke方法@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{//如果是Object的方法toString(),hashCode()等方法if(Object.class.equals(method.getDeclaringClass())){returnmethod.invoke(this,args);}elseif(method.isDefault()){//JDK8后的接口默认实现方法returninvokeDefaultMethod(proxy,method,args);}}catch(Throwablet){throwExceptionUtil.unwrapThrowable(t);}//创建MapperMethod对象finalMapperMethodmapperMethod=cachedMapperMethod(method);//下篇文章会讲到returnmapperMethod.execute(sqlSession,args);}}也就是说getMapper方法返回一个JDK动态代理对象(type是$Proxy+number)这个代理对象会继承Proxy类实现了代理接口UserMpper,它持有一个MapperProxy类型的触发器管理类。当我们调用UserMpper的方法时,实际上调用的是MapperProxy的invoke方法。userMapper=$Proxy6@2355。为什么要在MapperRegistry中保存一个工厂类?原来他是用来创建和返回代理类的。下面是代理模式的一个非常经典的应用。MapperProxy是如何实现对接口的代理的?JDK动态代理我们知道JDK动态代理有3个核心作用:Proxied类(即实现类)接口实现了InvocationHanndler的触发器管理类,用于生成代理对象。代理类必须实现接口,因为方法必须通过接口获取,代理类也必须实现这个接口。但是Mybatis中并没有Mapper接口的实现类,怎么代理呢?它忽略实现类,直接代理Mapper接口。MyBatis动态代理:在Mybatis中,为什么JDK动态代理不需要实现一个类?我们这里的目的是根据一个可执行的方法直接在Mapper.xml中找到statementID,方便调用。最后返回的userMapper是MapperProxyFactory创建的代理对象,然后这个对象包含了MapperProxy对象。问题三:如何根据Mapper.java找到Mapper.xml?最后我们调用userMapper.selectUserById(),本质上就是调用了MapperProxy的invoke()方法。请看下图:如果根据(接口+方法名)找到StatementID,这个逻辑可以在InvocationHandler子类(MapperProxy类)中完成,不需要使用实现类。总结本文主要讲getMapper方法,本质上是获取一个JDK动态代理对象(类型为Proxy+number),这个代理类会继承MapperProxy类,实现代理接口UserMapper,Trigger内部持有一个MapperProxy类型管理类。这里我们得到了代理类,后面我们可以使用这个代理对象进行方法调用。问题涉及的设计模式:代理模式。工厂模式。单例模式。整个流程图:本文转载自微信公众号《Java后端技术全栈》,可通过以下二维码关注。转载本文请联系Java后端技术全栈公众号。

最新推荐
猜你喜欢