详解Spring中实现接口动态的解决方法
前言
本文主要给大家介绍的是关于spring实现接口动态的相关内容,分享出来供大家参考学习,下面话不多说,来一起看看详细的介绍吧。
关于这个问题是因为领导最近跟我提了一个需求,是有关于实现类mybatis的@select、@insert注解的功能。其是基于interface层面,不存在任何的接口实现类。因而在实现的过程中,首先要解决的是如何动态实现接口的实例化。其次是如何将使接口根据注解实现相应的功能。
声明
解决方案是基于mybatis源码,进行二次开发实现。
解决方法
我们先来看看mybatis是如何实现dao类的扫描的。
mapperscannerconfigurer.java
public void postprocessbeandefinitionregistry(beandefinitionregistry registry) throws beansexception { if (this.processpropertyplaceholders) { processpropertyplaceholders(); } classpathmapperscanner scanner = new classpathmapperscanner(registry); scanner.setaddtoconfig(this.addtoconfig); scanner.setannotationclass(this.annotationclass); scanner.setmarkerinterface(this.markerinterface); scanner.setsqlsessionfactory(this.sqlsessionfactory); scanner.setsqlsessiontemplate(this.sqlsessiontemplate); scanner.setsqlsessionfactorybeanname(this.sqlsessionfactorybeanname); scanner.setsqlsessiontemplatebeanname(this.sqlsessiontemplatebeanname); scanner.setresourceloader(this.applicationcontext); scanner.setbeannamegenerator(this.namegenerator); scanner.registerfilters(); scanner.scan(stringutils.tokenizetostringarray(this.basepackage, configurableapplicationcontext.config_location_delimiters)); }
classpathmapperscanner是mybatis继承classpathbeandefinitionscanner类而来的。这里对于classpathmapperscanner的配置参数来源于我们在使用mybatis时的配置而来,是不是还记得在使用mybatis的时候要配置basepackage的参数呢?
接着我们就顺着scanner.scan()
方法,进入查看一下里面的实现。
classpathbeandefinitionscanner.java
public int scan(string... basepackages) { int beancountatscanstart = this.registry.getbeandefinitioncount(); doscan(basepackages); // register annotation config processors, if necessary. if (this.includeannotationconfig) { annotationconfigutils.registerannotationconfigprocessors(this.registry); } return (this.registry.getbeandefinitioncount() - beancountatscanstart); }
这里关键的代码是doscan(basepackages);
,那么我们在进去看一下。可能你会看到的是spring源码的实现方法,但这里mybatis也实现了自己的一套,我们看一下mybatis的实现。
classpathmapperscanner.java
public set<beandefinitionholder> doscan(string... basepackages) { set<beandefinitionholder> beandefinitions = super.doscan(basepackages); if (beandefinitions.isempty()) { logger.warn("no mybatis mapper was found in '" + arrays.tostring(basepackages) + "' package. please check your configuration."); } else { for (beandefinitionholder holder : beandefinitions) { genericbeandefinition definition = (genericbeandefinition) holder.getbeandefinition(); if (logger.isdebugenabled()) { logger.debug("creating mapperfactorybean with name '" + holder.getbeanname() + "' and '" + definition.getbeanclassname() + "' mapperinterface"); } // the mapper interface is the original class of the bean // but, the actual class of the bean is mapperfactorybean definition.getpropertyvalues().add("mapperinterface", definition.getbeanclassname()); definition.setbeanclass(mapperfactorybean.class); definition.getpropertyvalues().add("addtoconfig", this.addtoconfig); boolean explicitfactoryused = false; if (stringutils.hastext(this.sqlsessionfactorybeanname)) { definition.getpropertyvalues().add("sqlsessionfactory", new runtimebeanreference(this.sqlsessionfactorybeanname)); explicitfactoryused = true; } else if (this.sqlsessionfactory != null) { definition.getpropertyvalues().add("sqlsessionfactory", this.sqlsessionfactory); explicitfactoryused = true; } if (stringutils.hastext(this.sqlsessiontemplatebeanname)) { if (explicitfactoryused) { logger.warn("cannot use both: sqlsessiontemplate and sqlsessionfactory together. sqlsessionfactory is ignored."); } definition.getpropertyvalues().add("sqlsessiontemplate", new runtimebeanreference(this.sqlsessiontemplatebeanname)); explicitfactoryused = true; } else if (this.sqlsessiontemplate != null) { if (explicitfactoryused) { logger.warn("cannot use both: sqlsessiontemplate and sqlsessionfactory together. sqlsessionfactory is ignored."); } definition.getpropertyvalues().add("sqlsessiontemplate", this.sqlsessiontemplate); explicitfactoryused = true; } if (!explicitfactoryused) { if (logger.isdebugenabled()) { logger.debug("enabling autowire by type for mapperfactorybean with name '" + holder.getbeanname() + "'."); } definition.setautowiremode(abstractbeandefinition.autowire_by_type); } } } return beandefinitions; }
definition.setbeanclass(mapperfactorybean.class);
这行代码是非常关键的一句,由于在spring中存在两种自动实例化的方式,一种是我们常用的本身的接口实例化类进行接口实例化,还有一种就是这里的自定义实例化。而这里的setbeanclass方法就是在beandefinitionholder中进行配置。在spring进行实例化的时候进行处理。
那么我们在看一下mapperfactorybean.class
mapperfactorybean.java
public class mapperfactorybean<t> extends sqlsessiondaosupport implements factorybean<t> { private class<t> mapperinterface; private boolean addtoconfig = true; /** * sets the mapper interface of the mybatis mapper * * @param mapperinterface class of the interface */ public void setmapperinterface(class<t> mapperinterface) { this.mapperinterface = mapperinterface; } /** * if addtoconfig is false the mapper will not be added to mybatis. this means * it must have been included in mybatis-config.xml. * <p> * if it is true, the mapper will be added to mybatis in the case it is not already * registered. * <p> * by default addtocofig is true. * * @param addtoconfig */ public void setaddtoconfig(boolean addtoconfig) { this.addtoconfig = addtoconfig; } /** * {@inheritdoc} */ @override protected void checkdaoconfig() { super.checkdaoconfig(); notnull(this.mapperinterface, "property 'mapperinterface' is required"); configuration configuration = getsqlsession().getconfiguration(); if (this.addtoconfig && !configuration.hasmapper(this.mapperinterface)) { try { configuration.addmapper(this.mapperinterface); } catch (throwable t) { logger.error("error while adding the mapper '" + this.mapperinterface + "' to configuration.", t); throw new illegalargumentexception(t); } finally { errorcontext.instance().reset(); } } } /** * {@inheritdoc} */ public t getobject() throws exception { return getsqlsession().getmapper(this.mapperinterface); } /** * {@inheritdoc} */ public class<t> getobjecttype() { return this.mapperinterface; } /** * {@inheritdoc} */ public boolean issingleton() { return true; }
在该类中其实现了factorybean接口,看过spring源码的人,我相信对其都有很深的印象,其在bean的实例化中起着很重要的作用。在该类中我们要关注的是getobject方法,我们之后将动态实例化的接口对象放到spring实例化列表中,这里就是入口,也是我们的起点。不过要特别说明的是mapperinterface的值是如何被赋值的,可能会有疑问,我们再来看看上面的classpathmapperscanner.java我们在配置mapperfactorybean.class的上面存在一行 definition.getpropertyvalues().add("mapperinterface", definition.getbeanclassname());
其在之后在spring的postprocessorregistrationdelegate类的populatebean方法中进行属性配置,会将其依靠反射的方式将其注入到mapperfactorybean.class中。
而且definition.getpropertyvalues().add
中添加的值是注入到mapperfactorybean对象中去的。这一点需要说明一下。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。