欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

详解Spring中实现接口动态的解决方法

程序员文章站 2023-12-19 09:55:58
前言 本文主要给大家介绍的是关于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对象中去的。这一点需要说明一下。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。

上一篇:

下一篇: