当前位置: 代码网 > it编程>编程语言>Java > 一文详解SpringBoot如何创建自定义的自动配置

一文详解SpringBoot如何创建自定义的自动配置

2025年07月22日 Java 我要评论
在实际开发中,仅靠springboot的自动配置是远远不够的,比如要访问多个数据源,自动配置就完全无能为力了。自动配置的本质本质就是在容器中预配置要整合的框架所需的基础bean。以mybatis为例,

在实际开发中,仅靠springboot的自动配置是远远不够的,比如要访问多个数据源,自动配置就完全无能为力了。

自动配置的本质

本质就是在容器中预配置要整合的框架所需的基础bean。

以mybatis为例,spring整合mybatis无非就是完成以下事情:

  • 配置sqlsessionfactory bean,当然,该bean需要注入一个datasource
  • 配置sqlsessiontemplate bean,将上面的sqlsessionfactory 注入该bean
  • 注册mapper组件的自动扫描,相当于添加<mybatis:scan.../>元素

自动配置非常简单,无非就是有框架提供一个@configuration修饰的配置类(相当于传统的xml配置文件),在该配置类中用@bean预先配置默认的sqlsessionfactory、sqlsessiontemplate,并注册mapper组件的自动扫描即可。

比如mybatisautoconfiguration源代码:

@configuration // 被修饰的类变成配置类
// 当sqlsessionfactory、sqlsessionfactorybean类存在时,才会生效。
// 条件注解之一
@conditionalonclass({sqlsessionfactory.class, sqlsessionfactorybean.class}) 
// 当datasource bean存在时,才会生效
// 条件注解之一
@conditionalonsinglecandidate(datasource.class)
// 启用mybatis的属性处理类
// 启动属性处理类
@enableconfigurationproperties({mybatisproperties.class})
//指定该配置类在datasourceautoconfiguration和mybatislanguagedriverautoconfiguration之后加载
@autoconfigureafter({datasourceautoconfiguration.class, mybatislanguagedriverautoconfiguration.class})
// 实现 initializingbean 接口,该接口中的 afterpropertiesset 方法会在该bean初始化完成后被自动调用
public class mybatisautoconfiguration implements initializingbean {
    private static final logger logger = loggerfactory.getlogger(org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.class);
    // mybatis的配置属性
    private final mybatisproperties properties;
    // mybatis的拦截器、类型处理器、语言驱动等
    private final interceptor[] interceptors;
    private final typehandler[] typehandlers;
    private final languagedriver[] languagedrivers;
    private final resourceloader resourceloader;
    private final databaseidprovider databaseidprovider;
    private final list<configurationcustomizer> configurationcustomizers;
    private final list<sqlsessionfactorybeancustomizer> sqlsessionfactorybeancustomizers;

    public mybatisautoconfiguration(mybatisproperties properties, objectprovider<interceptor[]> interceptorsprovider, objectprovider<typehandler[]> typehandlersprovider, objectprovider<languagedriver[]> languagedriversprovider, resourceloader resourceloader, objectprovider<databaseidprovider> databaseidprovider, objectprovider<list<configurationcustomizer>> configurationcustomizersprovider, objectprovider<list<sqlsessionfactorybeancustomizer>> sqlsessionfactorybeancustomizers) {
        this.properties = properties;
        this.interceptors = (interceptor[])interceptorsprovider.getifavailable();
        this.typehandlers = (typehandler[])typehandlersprovider.getifavailable();
        this.languagedrivers = (languagedriver[])languagedriversprovider.getifavailable();
        this.resourceloader = resourceloader;
        this.databaseidprovider = (databaseidprovider)databaseidprovider.getifavailable();
        this.configurationcustomizers = (list)configurationcustomizersprovider.getifavailable();
        this.sqlsessionfactorybeancustomizers = (list)sqlsessionfactorybeancustomizers.getifavailable();
    }

    // 在bean初始化完成后调用该方法
    public void afterpropertiesset() {
        this.checkconfigfileexists();
    }

    // 检查mybatis配置文件是否存在
    private void checkconfigfileexists() {
        if (this.properties.ischeckconfiglocation() && stringutils.hastext(this.properties.getconfiglocation())) {
            // 获取配置文件的资源
            resource resource = this.resourceloader.getresource(this.properties.getconfiglocation());
            // 如果resource.exists()方法返回false,则抛出异常
            assert.state(resource.exists(), "cannot find config location: " + resource + " (please add config file or check your mybatis configuration)");
        }

    }

    // 创建sqlsessionfactory bean
    @bean
    // 当没有sqlsessionfactory bean时才会创建
    @conditionalonmissingbean
    public sqlsessionfactory sqlsessionfactory(datasource datasource) throws exception {
        // 创建sqlsessionfactorybean实例
        sqlsessionfactorybean factory = new sqlsessionfactorybean();
        // 注入数据源
        factory.setdatasource(datasource);
        factory.setvfs(springbootvfs.class);
        // 如果配置文件路径不为空,则设置配置文件位置
        if (stringutils.hastext(this.properties.getconfiglocation())) {
            factory.setconfiglocation(this.resourceloader.getresource(this.properties.getconfiglocation()));
        }

        this.applyconfiguration(factory);
        // 如果配置属性不为空,则设置配置属性
        if (this.properties.getconfigurationproperties() != null) {
            factory.setconfigurationproperties(this.properties.getconfigurationproperties());
        }
        // 应用所有的拦截器
        if (!objectutils.isempty(this.interceptors)) {
            factory.setplugins(this.interceptors);
        }
        // 应用所有databaseidprovider
        if (this.databaseidprovider != null) {
            factory.setdatabaseidprovider(this.databaseidprovider);
        }
        // 根据包名应用typealiases
        if (stringutils.haslength(this.properties.gettypealiasespackage())) {
            factory.settypealiasespackage(this.properties.gettypealiasespackage());
        }
        // 根据父类型应用typealiases
        if (this.properties.gettypealiasessupertype() != null) {
            factory.settypealiasessupertype(this.properties.gettypealiasessupertype());
        }
        // 根据包名应用typehandler
        if (stringutils.haslength(this.properties.gettypehandlerspackage())) {
            factory.settypehandlerspackage(this.properties.gettypehandlerspackage());
        }
        // 应用所有typehandler
        if (!objectutils.isempty(this.typehandlers)) {
            factory.settypehandlers(this.typehandlers);
        }
        // 设置mapper的加载位置
        if (!objectutils.isempty(this.properties.resolvemapperlocations())) {
            factory.setmapperlocations(this.properties.resolvemapperlocations());
        }

        set<string> factorypropertynames = (set) stream.of((new beanwrapperimpl(sqlsessionfactorybean.class)).getpropertydescriptors()).map(featuredescriptor::getname).collect(collectors.toset());
        class<? extends languagedriver> defaultlanguagedriver = this.properties.getdefaultscriptinglanguagedriver();
        if (factorypropertynames.contains("scriptinglanguagedrivers") && !objectutils.isempty(this.languagedrivers)) {
            factory.setscriptinglanguagedrivers(this.languagedrivers);
            if (defaultlanguagedriver == null && this.languagedrivers.length == 1) {
                defaultlanguagedriver = this.languagedrivers[0].getclass();
            }
        }

        if (factorypropertynames.contains("defaultscriptinglanguagedriver")) {
            factory.setdefaultscriptinglanguagedriver(defaultlanguagedriver);
        }

        this.applysqlsessionfactorybeancustomizers(factory);
        // 返回sqlsessionfactory对象
        return factory.getobject();
    }

    private void applyconfiguration(sqlsessionfactorybean factory) {
        org.apache.ibatis.session.configuration configuration = this.properties.getconfiguration();
        if (configuration == null && !stringutils.hastext(this.properties.getconfiglocation())) {
            configuration = new org.apache.ibatis.session.configuration();
        }

        if (configuration != null && !collectionutils.isempty(this.configurationcustomizers)) {
            iterator var3 = this.configurationcustomizers.iterator();

            while(var3.hasnext()) {
                configurationcustomizer customizer = (configurationcustomizer)var3.next();
                customizer.customize(configuration);
            }
        }

        factory.setconfiguration(configuration);
    }

    private void applysqlsessionfactorybeancustomizers(sqlsessionfactorybean factory) {
        if (!collectionutils.isempty(this.sqlsessionfactorybeancustomizers)) {
            iterator var2 = this.sqlsessionfactorybeancustomizers.iterator();

            while(var2.hasnext()) {
                sqlsessionfactorybeancustomizer customizer = (sqlsessionfactorybeancustomizer)var2.next();
                customizer.customize(factory);
            }
        }

    }

    // 创建sqlsessiontemplate bean
    @bean
    // 当没有sqlsessiontemplate bean时才会创建
    @conditionalonmissingbean
    public sqlsessiontemplate sqlsessiontemplate(sqlsessionfactory sqlsessionfactory) {
        executortype executortype = this.properties.getexecutortype();
        // 如果executortype不为null,则创建sqlsessiontemplate时使用该executortype
        return executortype != null ? new sqlsessiontemplate(sqlsessionfactory, executortype) : new sqlsessiontemplate(sqlsessionfactory);
    }

    @configuration
    // 导入mapperscannerregistrarnotfoundconfiguration注册类
    @import({org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.autoconfiguredmapperscannerregistrar.class})
    // 当mapperfactorybean和mapperscannerconfigurer都不存在时,才会生效
    @conditionalonmissingbean({mapperfactorybean.class, mapperscannerconfigurer.class})
    // 实现 initializingbean 接口,该接口中的 afterpropertiesset 方法会在该bean初始化完成后被自动调用
    public static class mapperscannerregistrarnotfoundconfiguration implements initializingbean {
        public mapperscannerregistrarnotfoundconfiguration() {
        }

        // 重写afterpropertiesset方法
        public void afterpropertiesset() {
            org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.logger.debug("not found configuration for registering mapper bean using @mapperscan, mapperfactorybean and mapperscannerconfigurer.");
        }
    }

    // 注册mapper扫描器的自动配置类
    // 实现 beanfactoryaware接口可访问spring容器、
    // 实现importbeandefinitionregistrar 接口可配置额外的bean
    public static class autoconfiguredmapperscannerregistrar implements beanfactoryaware, environmentaware, importbeandefinitionregistrar {
        // beanfactory对象,用于保存spring容器
        private beanfactory beanfactory;
        private environment environment;

        public autoconfiguredmapperscannerregistrar() {
        }

        public void registerbeandefinitions(annotationmetadata importingclassmetadata, beandefinitionregistry registry) {
            if (!autoconfigurationpackages.has(this.beanfactory)) {
                org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.logger.debug("could not determine auto-configuration package, automatic mapper scanning disabled.");
            } else {
                org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.logger.debug("searching for mappers annotated with @mapper");
               // 获取自动配置要处理的包
                list<string> packages = autoconfigurationpackages.get(this.beanfactory);
                if (org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.logger.isdebugenabled()) {
                    packages.foreach((pkg) -> {
                        org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration.logger.debug("using auto-configuration base package '{}'", pkg);
                    });
                }
                // 创建beandefinitionbuilder对象
                // 它帮助开发者以反射的方式创建任意类的实例
                // 此处就是帮助创建mapperscannerconfigurer类的实例
                beandefinitionbuilder builder = beandefinitionbuilder.genericbeandefinition(mapperscannerconfigurer.class);
               // 为要创建的对象设置属性
                builder.addpropertyvalue("processpropertyplaceholders", true);
                builder.addpropertyvalue("annotationclass", mapper.class);
                builder.addpropertyvalue("basepackage", stringutils.collectiontocommadelimitedstring(packages));
                beanwrapper beanwrapper = new beanwrapperimpl(mapperscannerconfigurer.class);
                set<string> propertynames = (set)stream.of(beanwrapper.getpropertydescriptors()).map(featuredescriptor::getname).collect(collectors.toset());
                if (propertynames.contains("lazyinitialization")) {
                    builder.addpropertyvalue("lazyinitialization", "${mybatis.lazy-initialization:false}");
                }

                if (propertynames.contains("defaultscope")) {
                    builder.addpropertyvalue("defaultscope", "${mybatis.mapper-default-scope:}");
                }

                boolean injectsqlsession = (boolean)this.environment.getproperty("mybatis.inject-sql-session-on-mapper-scan", boolean.class, boolean.true);
                if (injectsqlsession && this.beanfactory instanceof listablebeanfactory) {
                    listablebeanfactory listablebeanfactory = (listablebeanfactory)this.beanfactory;
                    optional<string> sqlsessiontemplatebeanname = optional.ofnullable(this.getbeannamefortype(sqlsessiontemplate.class, listablebeanfactory));
                    optional<string> sqlsessionfactorybeanname = optional.ofnullable(this.getbeannamefortype(sqlsessionfactory.class, listablebeanfactory));
                    if (!sqlsessiontemplatebeanname.ispresent() && sqlsessionfactorybeanname.ispresent()) {
                        builder.addpropertyvalue("sqlsessionfactorybeanname", sqlsessionfactorybeanname.get());
                    } else {
                        builder.addpropertyvalue("sqlsessiontemplatebeanname", sqlsessiontemplatebeanname.orelse("sqlsessiontemplate"));
                    }
                }

                builder.setrole(2);
                // 在容器中注册beandefinitionbuilder创建的mapperscannerconfigurer对象
                registry.registerbeandefinition(mapperscannerconfigurer.class.getname(), builder.getbeandefinition());
            }
        }

        // 获取spring容器和环境对象
        public void setbeanfactory(beanfactory beanfactory) {
            this.beanfactory = beanfactory;
        }

        public void setenvironment(environment environment) {
            this.environment = environment;
        }

        private string getbeannamefortype(class<?> type, listablebeanfactory factory) {
            string[] beannames = factory.getbeannamesfortype(type);
            return beannames.length > 0 ? beannames[0] : null;
        }
    }
}

开开发完自动配置类后,还需要使用meta-inf/spring.factories文件来定义自动配置类,比如:

# auto configure
org.springframework.boot.autoconfigure.enableautoconfiguration=\
org.mybatis.spring.boot.autoconfigure.mybatislanguagedriverautoconfiguration,\
org.mybatis.spring.boot.autoconfigure.mybatisautoconfiguration

自动配置类只能通过meta-inf/spring.factories来加载,并确保它们处于一个特殊的包空间内,尤其不能让他们变成普通@componentscan的目标。此外,自动配置类不应该使用@componentscan来扫描其他组件,如果需要加载其他配置文件,应使用@import来加载。

如果要为自动配置类指定加载顺序,可使用以下的注解:

  • @autoconfigureafter:指定被修饰的类必须在一个或多个自动配置类之后加载
  • @autoconfigurebefore:指定被修饰的类必须在一个或多个自动配置类之前加载

如果自动配置包中包含多个自动配置类,且以特定的顺序来加载,可使用@autoconfigureorder来修饰它们,@autoconfigureorder类似于@order注解,只不过专门修饰自动配置类。

条件注解

条件注解用于修饰@configuration类 或@bean方法等,表示只有条件有效时,被修饰的 配置类或配置方法才生效。springboot的条件 注解可支持如下几种条件:

  1. 类条件注解:@conditionalonclass(表示某些类存在时,可通过value或 name指定所要求存在的类,value属性是 被检查类的 class对象;name属性是被检查类的全限定类名的字符串形式)、@conditionalonmissingclass(某些类不存在时,只能通过value属性指定不存在的类,value属性值只能是被检查类的全限定类名的字符串形式)
  2. bean条件注解 :@conditionalonmissingbean、@conditionalonsinglecandidate、@conditionalonbean、@conditionalonmissingfilterbean
  3. 属性条件注解:@conditionalonproperity
  4. 资源条件注解:@conditionalonresource
  5. web应用条件注解:@conditionalonwebapplication、@conditionalonnotwebapplication、@conditionalonwardeployment
  6. spel表达式条件注解:@conditionalonexpression
  7. 特殊条件注解:@conditionaloncloudplatform、@conditionalonjava、@conditionalonjndi、@conditionalonrepositorytype

代码示例:

@configuration(proxybeanmethods = false)
// 仅当com.mysql.cj.jdbc.driver类存在时该配置类生效
@conditionalonclass(name = "com.mysql.cj.jdbc.driver")
public class fkconfig
{
   @bean
   public mybean mybean()
   {
      return new mybean();
   }
}

@conditionalonmissingbean、@conditionalonsinglecandidate、@conditionalonbean可指定 如下属性:

  1. class<? extends annotation>[] annotattion:指定 要检查的 bean必须用该属性指定的注解修饰
  2. class<?>[] ignored:指定要忽略哪些类型 的bean。该属性及ignoredtype 仅对@conditionalonmissingbean注解有效
  3. string[] ignoredtype :与ignored属性的作用相同,只不过该属性用字符串形式 的全限定类名
  4. string[] name:指定要检查的bean的id
  5. search:指定搜索目标bean的 搜索策略、支持current(仅在容器中搜索)、acestors(仅在祖先容器中搜索)、all(在所有容器中搜索)三个枚举值
  6. class<?> [] value:指定要检查的bean的类型
  7. string[] type:与value属性作用相同,只不过该属性用字符串形式 的全限定类名

@conditionalonsinglecandidate注解相当于@conditionalonmissingbean的增强版,不仅要求被检查的bean必须存在,而且只能有一个“候选者”--能满足bytype依赖注入条件。

如果@conditionalonmissingbean、@conditionalonbean注解不指定任何属性,默认根据目标bean的类型进行检查,默认检查被修饰的方法返回的bean类型,代码示例:

// 仅当容器中不存在名为myservice的bean时,才创建该bean   
@conditionalonmissingbean
@bean
public myservice myservice()
{
   ...
}

// 当容器中不存在名为jdbctemplate的bean时,才创建该bean
@conditionalonmissingbean(name="jdbctemplate")
@bean
public jdbctemplate jdbctemplate()
{
   ...
}

@conditionalonmissingfilterbean相当于@conditionalonmissingbean的特殊版本,专门检查容器中是否有指定类型的javax.servlet.filter,因此只能通过value指定要检查的filter的类型。

@conditionalonproperity注解 用于检查特定属性是否具有指定的属性值。该注解支持如下属性:

  1. string[] value:指定要检查的属性
  2. string[] name:指定value属性的别名
  3. string havingvalue:被检查属性必须具有的属性值
  4. string prefix:自动为各属性名添加该属性指定的前缀
  5. boolean matchmissing:指定当属性未设置属性值时,是否通过检查

代码示例:

@configuration(proxybeanmethods = false)
public class fkconfig
{
   @bean
   // 只有当org.fkjava.test属性具有foo属性值时,下面配置方法才会生效
   @conditionalonproperty(name = "test", havingvalue = "foo",
         prefix = "org.fkjava")
   public dateformat dateformat()
   {
      return dateformat.getdateinstance();
   }
}

启动类代码:

@springbootapplication
public class app
{
   public static void main(string[] args)
   {
      // 创建spring容器、运行spring boot应用
      var ctx = springapplication.run(app.class, args);
      system.out.println(ctx.getbean("dateformat"));
   }
}

此时直接运行程序会有异常。

在application.properties文件添加如下配置:

org.fkjava.test=foo

运行结果如下

@conditionalonresource的作用很简单,它要求指定的资源必须存在,修饰的配置类才会生效。使用该注解只需指定resource属性,该属性指定必须存在的资源。

@conditionalonwebapplication要求当前应用必须是web应用时,修饰 的配置类才会生效。可通过type属性指定web应用类型。该属性支持如下三个枚举值:

  1. any:任何web应用
  2. reactive:当应用时反应式web应用时
  3. servlet:基于servlet的web应用

代码示例:

@configuration(proxybeanmethods = false)
public class fkconfig
{
   @bean
   // 只有当前应用是反应式web应用时,该配置才会生效
   @conditionalonwebapplication(type = conditionalonwebapplication.type.reactive)
   public dateformat dateformat()
   {
      return dateformat.getdateinstance();
   }
}

启动类:

@springbootapplication
public class app
{
   public static void main(string[] args)
   {
      var app = new springapplication(app.class);
      // 设置web应用的类型,如果不设置则使用默认的类型:
      // 如果有sping web依赖,自动是基于servlet的web应用
      // 如果有sping webflux依赖,自动是反应式web应用
      app.setwebapplicationtype(webapplicationtype.reactive);  // ①
      // 创建spring容器、运行spring boot应用
      var ctx = app.run(args);
      system.out.println(ctx.getbean("dateformat"));
   }
}

@conditionalonnotwebapplication要求当前应用不是web应用时,修饰的配置类或方法才生效

@conditionalonwardeployment要求当前应用以war包部署 到web服务器或应用服务器中时(不以独立的java程序的方式运行),才生效。

@conditionalonnotwebapplication、@conditionalonwardeployment这2个注解使用简单,不需要指定任何属性

@conditionalonexpression要求指定spel表达式的值为true,所修饰的配置类或方法才会生效。代码示例:

@configuration(proxybeanmethods = false)
public class fkconfig
{
   @bean
   public user user()
   {
      return new user("fkjava", true);
   }
   @bean
   // 只有当user.active表达式为true时,该方法才生效。也就是容器中user bean的active属性为true时,该方法才生效
   @conditionalonexpression("user.active")
   public dateformat dateformat()
   {
      return dateformat.getdateinstance();
   }
}

@conditionaloncloudplatform要求应用被部署在特定云平台,修饰的配置类或方法才生效。可通过value属性指定要求的云平台,支持如下枚举值:

  1. cloud_foundry
  2. heroku
  3. kubernetes
  4. sap

@conditionalonjava对目标平台的java版本进行检测,既可以要求java版本是某个具体的版本,也可以要求高于或低于某个版本。可指定如下两个属性:

  1. javaversion value:指定要求的java版本
  2. conditionalonjava.range range:该属性支持equal_or_newer(大于或等于某版本)和older_than(小于某版本)两个枚举值。如果不指定该属性,则要求java版本必须是value属性所指定的版本。

代码示例:

@configuration(proxybeanmethods = false)
public class fkconfig
{
   @bean
   // 只有当目标平台的java版本是11或更新的平台时,该方法才生效
   @conditionalonjava(value = javaversion.eleven,range = conditionalonjava.range.equal_or_newer)
   public dateformat dateformat()
   {
      return dateformat.getdateinstance();
   }
}

@conditionalonjndi要求指定jndi必须存在,通过value属性指定要检查的jndi。

@conditionalonrepositorytype要求特定的spring data repository被启用时,修饰的配置类或方法才会生效。

自定义条件注解

自定义条件注解的关键就是要有一个condition实现类,该类负责条件注解的处理逻辑--它所实现的matches()方法决定了条件注解的要求是否得到满足。

代码示例:condition实现类

public class mycondition implements condition
{
   @override
   public boolean matches(conditioncontext context,
         annotatedtypemetadata metadata)
   {
      // 获取@conditionalcustom注解的全部属性
      map<string, object> map = metadata.getannotationattributes(
            conditionalcustom.class.getname());
      // 获取注解的value属性值(string[]数组)
      string[] vals = (string[]) map.get("value");
      environment env = context.getenvironment();
      // 遍历每个属性值
      for (object val : vals)
      {
         // 如果某个属性值对应的配置属性不存在,返回false
         if (env.getproperty(val.tostring()) == null)
         {
            return false;
         }
      }
      return true;
   }
}

此处逻辑是要求value属性所指定的所有配置属性必须存在,至于属性值是什么无所谓,这些属性是否有值也无所谓。

自定义条件注解的代码:

@target({ elementtype.type, elementtype.method })
@retention(retentionpolicy.runtime)
@documented
// 指定conditional的实现类
@conditional(mycondition.class)
public @interface conditionalcustom
{
   string[] value() default {};
}

使用自定义条件注解:

@configuration(proxybeanmethods = false)
public class fkconfig
{
   @bean
   // 只有当org.fkjava.test和org.crazyit.abc两个配置属性存在时该方法才生效
   @conditionalcustom({"org.fkjava.test", "org.crazyit.abc"})
   public dateformat dateformat()
   {
      return dateformat.getdateinstance();
   }
}

自定义自动配置

开发自定义的自动配置很简单,分为两步:

  1. 使用@configuration和条件注解自定义配置类
  2. 在meta-inf/spring.factories文件中注册自动配置类

为了演示,先自行开发一个funny框架,功能是用文件或数据库保存程序输出信息。

先新建一个maven项目,pom.xml如下

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
       xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelversion>4.0.0</modelversion>

   <groupid>org.crazyit</groupid>
   <artifactid>funny</artifactid>
   <version>1.0-snapshot</version>
   <name>funny</name>

   <properties>
      <!-- 定义所使用的java版本和源代码所用的字符集 -->
      <maven.compiler.source>11</maven.compiler.source>
      <maven.compiler.target>11</maven.compiler.target>
      <project.build.sourceencoding>utf-8</project.build.sourceencoding>
   </properties>

   <dependencies>
      <!-- mysql驱动依赖 -->
      <dependency>
         <groupid>mysql</groupid>
         <artifactid>mysql-connector-java</artifactid>
         <version>8.0.22</version>
      </dependency>
      <dependency>
         <groupid>org.slf4j</groupid>
         <artifactid>slf4j-api</artifactid>
         <version>1.7.30</version>
         <optional>true</optional>
      </dependency>
   </dependencies>
</project>

开发writertemplate类

public class writertemplate
{
   logger log = loggerfactory.getlogger(this.getclass());
   private final datasource datasource;
   private connection conn;
   private final file dest;
   private final charset charset;
   private randomaccessfile raf;

   public writertemplate(datasource datasource) throws sqlexception
   {
      this.datasource = datasource;
      this.dest = null;
      this.charset = null;
      if (objects.nonnull(this.datasource))
      {
         log.debug("==========获取数据库连接==========");
         this.conn = datasource.getconnection();
      }
   }

   public writertemplate(file dest, charset charset) throws filenotfoundexception
   {
      this.dest = dest;
      this.charset = charset;
      this.datasource = null;
      this.raf = new randomaccessfile(this.dest, "rw");
   }

   public void write(string message) throws ioexception, sqlexception
   {
      if (objects.nonnull(this.conn))
      {
         // 查询当前数据库的funny_message表是否存在
         resultset rs = conn.getmetadata().gettables(conn.getcatalog(), null,
               "funny_message", null);
         //  如果funny_message表不存在
         if (!rs.next())
         {
            log.debug("~~~~~~创建funny_message表~~~~~~");
            conn.createstatement().execute("create table funny_message " +
                  "(id int primary key auto_increment, message_text text)");
            rs.close();
         }
         log.debug("~~~~~~输出到数据表~~~~~~");
         // 插入要输出的字符串
         conn.createstatement().executeupdate("insert into " +
               "funny_message values (null, '" + message + "')");
      }
      else
      {
         log.debug("~~~~~~输出到文件~~~~~~");
         // 输出到文件
         raf.seek(this.dest.length());
         raf.write((message + "\n").getbytes(this.charset));
      }
   }
   // 关闭资源
   public void close() throws sqlexception, ioexception
   {
      if (this.conn != null)
      {
         this.conn.close();
      }
      if (this.raf != null)
      {
         this.raf.close();
      }
   }
}

然后使用 mvn install 命令打成jar包并安装到本地资源库。

在starter的项目中引入上面的jar包。pom.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<project xmlns="http://maven.apache.org/pom/4.0.0"
       xmlns:xsi="http://www.w3.org/2001/xmlschema-instance"
       xsi:schemalocation="http://maven.apache.org/pom/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelversion>4.0.0</modelversion>

   <!-- 指定继承spring-boot-starter-parent pom文件 -->
   <parent>
      <groupid>org.springframework.boot</groupid>
      <artifactid>spring-boot-starter-parent</artifactid>
      <version>2.4.2</version>
      <relativepath/>
   </parent>

   <!-- 定义基本的项目信息 -->
   <groupid>org.crazyit</groupid>
   <artifactid>funny-spring-boot-starter</artifactid>
   <version>0.0.1-snapshot</version>
   <name>funny-spring-boot-starter</name>

   <properties>
      <!-- 定义所使用的java版本和源代码所用的字符集 -->
      <java.version>11</java.version>
      <project.build.sourceencoding>utf-8</project.build.sourceencoding>
   </properties>

   <dependencies>
      <!-- spring boot starter依赖 -->
      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-starter</artifactid>
      </dependency>
      <!-- 依赖自定义的funny框架 -->
      <dependency>
         <groupid>org.crazyit</groupid>
         <artifactid>funny</artifactid>
         <version>1.0-snapshot</version>
      </dependency>
      <dependency>
         <groupid>org.springframework.boot</groupid>
         <artifactid>spring-boot-configuration-processor</artifactid>
         <optional>true</optional>
      </dependency>
   </dependencies>
</project>

然后在开发中编写自定义配置类:

@configuration
// 当writertemplate类存在时配置生效
// writertemplate类是自己编写的工具项目中的类
@conditionalonclass(writertemplate.class)
// 启用funnyproperties属性处理类
@enableconfigurationproperties(funnyproperties.class)
// 让该自动配置位于datasourceautoconfiguration自动配置之后处理
@autoconfigureafter(datasourceautoconfiguration.class)
public class funnyautoconfiguration
{
   // funnyproperties类负责加载配置属性
   private final funnyproperties properties;

   public funnyautoconfiguration(funnyproperties properties)
   {
      this.properties = properties;
   }

   @bean(destroymethod = "close")
   // 当单例的datasource bean存在时配置生效
   @conditionalonsinglecandidate(datasource.class)
   // 只有当容器中没有writertemplate bean时,该配置才会生效
   @conditionalonmissingbean
   // 通过@autoconfigureorder注解指定该配置方法
   // 比下一个配置writertemplate的方法的优先级更高
    // @autoconfigureorder 数值越小,优先级越高
   @autoconfigureorder(99)
   public writertemplate writertemplate(datasource datasource) throws sqlexception
   {
      return new writertemplate(datasource);
   }

   @bean(destroymethod = "close")
   // 只有当前面的writertemplate配置没有生效时,该方法的配置才会生效
   @conditionalonmissingbean
   @autoconfigureorder(199)
   public writertemplate writertemplate2() throws filenotfoundexception
   {
      file f = new file(this.properties.getdest());
      charset charset = charset.forname(this.properties.getcharset());
      return new writertemplate(f, charset);
   }
}

上面代码中的funnyproperties类

// 定义属性处理类
@configurationproperties(prefix = funnyproperties.funny_prefix)
public class funnyproperties
{
   public static final string funny_prefix = "org.crazyit.funny";
   private string dest;
   private string charset;
// 省略getter、setter
}

接下来在meta-inf/spring.factories文件中注册自动配置类

org.springframework.boot.autoconfigure.enableautoconfiguration=\
  org.crazyit.funny.autoconfigure.funnyautoconfiguration

然后使用 mvn install 命令打成jar包,并安装到maven本地资源库中,就会在自己的本地资源库中找到该jar包,这样就完成了自定义配置的实现。

创建自定义的starter

一个完整的springboot starter包含一下两个组件:

  • 自动配置模块(auto-configure):包含自动配置类和spring.factories文件
  • starter模块:负责管理自动配置模块和第三方依赖。简而言之,添加本starter就能使用该自动配置。

由此看出,starter不包含任何class文件,只管理愿意来。如果查看官方提供的jar就会发现,它所有自动配置类的class都由spring-boot-autoconfigure.jar提供,而各个xxx-starter.jar并未提供任何class文件,只是在这些jar下的相同路径下提供了一个xxx-starter.pom文件,该文件指定starter管理的自动依赖模块和第三方依赖。

springboot为自动配置包和starter包提供推荐命名

  • 自动配置包的推荐名:xxx-spring-boot
  • starter包的推荐名:xxx-spring-boot-starter

对于第三方starter不要使用spring-boot-starter-xxx这种方式,这是官方使用的。

有了自定义的starter后,使用起来和官方的没有区别,比如

添加依赖:

<!-- 自定义的funny-spring-boot-starter依赖 -->
<dependency>
   <groupid>org.crazyit</groupid>
   <artifactid>funny-spring-boot-starter</artifactid>
   <version>0.0.1-snapshot</version>
</dependency>

在application.properties文件添加配置

org.crazyit.funny.dest=f:/abc-98765.txt
org.crazyit.funny.charset=utf-8
# 指定连接数据库的信息
spring.datasource.url=jdbc:mysql://localhost:3306/funny?servertimezone=utc
spring.datasource.username=root
spring.datasource.password=32147
# 配置funny框架的日志级别为debug
logging.level.org.crazyit.funny = debug

主类的代码:

@springbootapplication
public class app
{
   public static void main(string[] args) throws ioexception, sqlexception
   {
      // 创建spring容器、运行spring boot应用
      var ctx = springapplication.run(app.class, args);
      // 获取自动配置的writertemplate
      writertemplate writertemplate = ctx.getbean(writertemplate.class);
      writertemplate.write("自动配置其实很简单");
   }
}

以上就是一文详解springboot如何创建自定义的自动配置的详细内容,更多关于springboot自动配置的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com