个人网站可以做企业网站吗,wordpress提交工单,黑wordpress网站,档案网站的建设方案1、概述
Spring Boot是Spring旗下众多的子项目之一#xff0c;其理念是约定优于配置#xff0c;它通过实现了自动配置#xff08;大多数用户平时习惯设置的配置作为默认配置#xff09;的功能来为用户快速构建出标准化的应用。Spring Boot的特点可以概述为如下几点#x…1、概述
Spring Boot是Spring旗下众多的子项目之一其理念是约定优于配置它通过实现了自动配置大多数用户平时习惯设置的配置作为默认配置的功能来为用户快速构建出标准化的应用。Spring Boot的特点可以概述为如下几点 内置了嵌入式的Tomcat、Jetty等Servlet容器应用可以不用打包成War格式而是可以直接以Jar格式运行。 提供了多个可选择的”starter”以简化Maven的依赖管理也支持Gradle让您可以按需加载需要的功能模块。 尽可能地进行自动配置减少了用户需要动手写的各种冗余配置项Spring Boot提倡无XML配置文件的理念使用Spring Boot生成的应用完全不会生成任何配置代码与XML配置文件。 提供了一整套的对应用状态的监控与管理的功能模块通过引入spring-boot-starter-actuator包括应用的线程信息、内存信息、应用是否处于健康状态等为了满足更多的资源监控需求Spring Cloud中的很多模块还对其进行了扩展。
有关Spring Boot的使用方法就不做多介绍了如有兴趣请自行阅读官方文档Spring Boot或其他文章。
如今微服务的概念愈来愈热转型或尝试微服务的团队也在如日渐增而对于技术选型Spring Cloud是一个比较好的选择它提供了一站式的分布式系统解决方案包含了许多构建分布式系统与微服务需要用到的组件例如服务治理、API网关、配置中心、消息总线以及容错管理等模块。可以说Spring Cloud”全家桶”极其适合刚刚接触微服务的团队。似乎有点跑题了不过说了这么多我想要强调的是Spring Cloud中的每个组件都是基于Spring Boot构建的而理解了Spring Boot的自动配置的原理显然也是有好处的。
Spring Boot的自动配置看起来神奇其实原理非常简单背后全依赖于Conditional注解来实现的。
2、什么是Conditional
Conditional是由Spring 4提供的一个新特性用于根据特定条件来控制Bean的创建行为。而在我们开发基于Spring的应用的时候难免会需要根据条件来注册Bean。
例如你想要根据不同的运行环境来让Spring注册对应环境的数据源Bean对于这种简单的情况完全可以使用Profile注解实现就像下面代码所示
Configuration
public class AppConfig {BeanProfile(DEV)public DataSource devDataSource() {...}BeanProfile(PROD)public DataSource prodDataSource() {...}
}剩下只需要设置对应的Profile属性即可设置方法有如下三种
通过context.getEnvironment().setActiveProfiles(“PROD”)来设置Profile属性。通过设定jvm的spring.profiles.active参数来设置环境Spring Boot中可以直接在application.properties配置文件中设置该属性。通过在DispatcherServlet的初始参数中设置。
servletservlet-namedispatcher/servlet-nameservlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-classinit-paramparam-namespring.profiles.active/param-nameparam-valuePROD/param-value/init-param
/servlet但这种方法只局限于简单的情况而且通过源码我们可以发现Profile自身也使用了Conditional注解。
package org.springframework.context.annotation;
Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
Documented
Conditional({ProfileCondition.class}) // 组合了Conditional注解
public interface Profile {String[] value();
}
package org.springframework.context.annotation;
class ProfileCondition implements Condition {ProfileCondition() {}// 通过提取出Profile注解中的value值来与profiles配置信息进行匹配public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {if(context.getEnvironment() ! null) {MultiValueMap attrs metadata.getAllAnnotationAttributes(Profile.class.getName());if(attrs ! null) {Iterator var4 ((List)attrs.get(value)).iterator();Object value;do {if(!var4.hasNext()) {return false;}value var4.next();} while(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));return true;}}return true;}
}在业务复杂的情况下显然需要使用到Conditional注解来提供更加灵活的条件判断例如以下几个判断条件
在类路径中是否存在这样的一个类。在Spring容器中是否已经注册了某种类型的Bean如未注册我们可以让其自动注册到容器中上一条同理。一个文件是否在特定的位置上。一个特定的系统属性是否存在。在Spring的配置文件中是否设置了某个特定的值。
举个栗子假设我们有两个基于不同数据库实现的DAO它们全都实现了UserDao其中JdbcUserDAO与MySql进行连接MongoUserDAO与MongoDB进行连接。现在我们有了一个需求需要根据命令行传入的系统参数来注册对应的UserDao就像java -jar app.jar -DdbTypeMySQL会注册JdbcUserDao而java -jar app.jar -DdbTypeMongoDB则会注册MongoUserDao。使用Conditional可以很轻松地实现这个功能仅仅需要在你自定义的条件类中去实现Condition接口让我们来看下面的代码。以下案例来自https://dzone.com/articles/how-springboot-autoconfiguration-magic-works
public interface UserDAO {....
}
public class JdbcUserDAO implements UserDAO {....
}
public class MongoUserDAO implements UserDAO {....
}
public class MySQLDatabaseTypeCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {String enabledDBType System.getProperty(dbType); // 获得系统参数 dbType// 如果该值等于MySql则条件成立return (enabledDBType ! null enabledDBType.equalsIgnoreCase(MySql));}
}
// 与上述逻辑一致
public class MongoDBDatabaseTypeCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {String enabledDBType System.getProperty(dbType);return (enabledDBType ! null enabledDBType.equalsIgnoreCase(MongoDB));}
}
// 根据条件来注册不同的Bean
Configuration
public class AppConfig {BeanConditional(MySQLDatabaseTypeCondition.class)public UserDAO jdbcUserDAO() {return new JdbcUserDAO();}BeanConditional(MongoDBDatabaseTypeCondition.class)public UserDAO mongoUserDAO() {return new MongoUserDAO();}
}现在我们又有了一个新需求我们想要根据当前工程的类路径中是否存在MongoDB的驱动类来确认是否注册MongoUserDAO。为了实现这个需求可以创建检查MongoDB驱动是否存在的两个条件类。
public class MongoDriverPresentsCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {try {Class.forName(com.mongodb.Server);return true;} catch (ClassNotFoundException e) {return false;}}
}
public class MongoDriverNotPresentsCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {try {Class.forName(com.mongodb.Server);return false;} catch (ClassNotFoundException e) {return true;}}
}假如你想要在UserDAO没有被注册的情况下去注册一个UserDAOBean那么我们可以定义一个条件类来检查某个类是否在容器中已被注册。
public class UserDAOBeanNotPresentsCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {UserDAO userDAO conditionContext.getBeanFactory().getBean(UserDAO.class);return (userDAO null);}
}如果你想根据配置文件中的某项属性来决定是否注册MongoDAO例如app.dbType是否等于MongoDB我们可以实现以下的条件类。
public class MongoDbTypePropertyCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {String dbType conditionContext.getEnvironment().getProperty(app.dbType);return MONGO.equalsIgnoreCase(dbType);}
}我们已经尝试并实现了各种类型的条件判断接下来我们可以选择一种更为优雅的方式就像Profile一样以注解的方式来完成条件判断。首先我们需要定义一个注解类。
Target({ ElementType.TYPE, ElementType.METHOD })
Retention(RetentionPolicy.RUNTIME)
Documented
Conditional(DatabaseTypeCondition.class)
public interface DatabaseType {String value();
}具体的条件判断逻辑在DatabaseTypeCondition类中它会根据系统参数dbType来判断注册哪一个Bean。
public class DatabaseTypeCondition implements Condition {Overridepublic boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata metadata) {MapString, Object attributes metadata.getAnnotationAttributes(DatabaseType.class.getName());String type (String) attributes.get(value);// 默认值为MySqlString enabledDBType System.getProperty(dbType, MySql);return (enabledDBType ! null type ! null enabledDBType.equalsIgnoreCase(type));}
}最后在配置类应用该注解即可。
Configuration
ComponentScan
public class AppConfig {BeanDatabaseType(MySql)public UserDAO jdbcUserDAO() {return new JdbcUserDAO();}BeanDatabaseType(mongoDB)public UserDAO mongoUserDAO() {return new MongoUserDAO();}
}下面列举Spring Boot对Conditional的扩展注解
3、AutoConfigure源码分析
通过了解Conditional注解的机制其实已经能够猜到自动配置是如何实现的了接下来我们通过源码来看看它是怎么做的。本文中讲解的源码基于Spring Boot 1.5.9版本最新的正式版本。
SpringBoot 自动配置主要通过 EnableAutoConfiguration, Conditional, EnableConfigurationProperties 或者 ConfigurationProperties 等几个注解来进行自动配置完成的。
EnableAutoConfiguration 开启自动配置主要作用就是调用 Spring-Core 包里的 loadFactoryNames()将 autoconfig 包里的已经写好的自动配置加载进来。
Conditional 条件注解通过判断类路径下有没有相应配置的 jar 包来确定是否加载和自动配置这个类。
EnableConfigurationProperties 的作用就是给自动配置提供具体的配置参数只需要写在 application.properties 中就可以通过映射写入配置类的 POJO 属性中。
EnableAutoConfiguration
Enable*注释并不是SpringBoot新发明的注释Spring 3框架就引入了这些注释用这些注释替代XML配置文件。比如 EnableTransactionManagement注释它能够声明事务管理 EnableWebMvc注释它能启用Spring MVC EnableScheduling注释它可以初始化一个调度器。
这些注释事实上都是简单的配置通过Import注释导入。 从启动类的SpringBootApplication进入在里面找到了EnableAutoConfiguration, EnableAutoConfiguration里通过Import导入了 EnableAutoConfigurationImportSelector 进入他的父类AutoConfigurationImportSelector 找到selectImports()方法他调用了getCandidateConfigurations()方法在这里这个方法又调用了Spring Core包中的loadFactoryNames()方法。这个方法的作用是会查询META-INF/spring.factories文件中包含的JAR文件。 找到spring.factories文件后SpringFactoriesLoader将查询配置文件命名的属性。 Jar文件在org.springframework.boot.autoconfigure的spring.factories spring.factories内容如下(截取部分),在这个文件中可以看到一系列Spring Boot自动配置的列表
org.springframework.boot.autoconfigure.EnableAutoConfiguration\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
自动配置类中的条件注解
接下来我们在spring.factories文件中随便找一个自动配置类来看看是怎样实现的。我查看了MongoDataAutoConfiguration的源码发现它声明了ConditionalOnClass注解通过看该注解的源码后可以发现这是一个组合了Conditional的组合注解它的条件类是OnClassCondition。
Configuration
ConditionalOnClass({Mongo.class, MongoTemplate.class})
EnableConfigurationProperties({MongoProperties.class})
AutoConfigureAfter({MongoAutoConfiguration.class})
public class MongoDataAutoConfiguration {....
}
Target({ElementType.TYPE, ElementType.METHOD})
Retention(RetentionPolicy.RUNTIME)
Documented
Conditional({OnClassCondition.class})
public interface ConditionalOnClass {Class?[] value() default {};String[] name() default {};
}然后我们开始看OnClassCondition的源码发现它并没有直接实现Condition接口只好往上找发现它的父类SpringBootCondition实现了Condition接口。
class OnClassCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {.....
}
public abstract class SpringBootCondition implements Condition {private final Log logger LogFactory.getLog(this.getClass());public SpringBootCondition() {}public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {String classOrMethodName getClassOrMethodName(metadata);try {ConditionOutcome ex this.getMatchOutcome(context, metadata);this.logOutcome(classOrMethodName, ex);this.recordEvaluation(context, classOrMethodName, ex);return ex.isMatch();} catch (NoClassDefFoundError var5) {throw new IllegalStateException(Could not evaluate condition on classOrMethodName due to var5.getMessage() not found. Make sure your own configuration does not rely on that class. This can also happen if you are ComponentScanning a springframework package (e.g. if you put a ComponentScan in the default package by mistake), var5);} catch (RuntimeException var6) {throw new IllegalStateException(Error processing condition on this.getName(metadata), var6);}}public abstract ConditionOutcome getMatchOutcome(ConditionContext var1, AnnotatedTypeMetadata var2);
}SpringBootCondition实现的matches方法依赖于一个抽象方法this.getMatchOutcome(context, metadata)我们在它的子类OnClassCondition中可以找到这个方法的具体实现。
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {ClassLoader classLoader context.getClassLoader();ConditionMessage matchMessage ConditionMessage.empty();// 找出所有ConditionalOnClass注解的属性List onClasses this.getCandidates(metadata, ConditionalOnClass.class);List onMissingClasses;if(onClasses ! null) {// 找出不在类路径中的类onMissingClasses this.getMatches(onClasses, OnClassCondition.MatchType.MISSING, classLoader);// 如果存在不在类路径中的类匹配失败if(!onMissingClasses.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind(required class, required classes).items(Style.QUOTE, onMissingClasses));}matchMessage matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found(required class, required classes).items(Style.QUOTE, this.getMatches(onClasses, OnClassCondition.MatchType.PRESENT, classLoader));}// 接着找出所有ConditionalOnMissingClass注解的属性// 它与ConditionalOnClass注解的含义正好相反所以以下逻辑也与上面相反onMissingClasses this.getCandidates(metadata, ConditionalOnMissingClass.class);if(onMissingClasses ! null) {List present this.getMatches(onMissingClasses, OnClassCondition.MatchType.PRESENT, classLoader);if(!present.isEmpty()) {return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found(unwanted class, unwanted classes).items(Style.QUOTE, present));}matchMessage matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind(unwanted class, unwanted classes).items(Style.QUOTE, this.getMatches(onMissingClasses, OnClassCondition.MatchType.MISSING, classLoader));}return ConditionOutcome.match(matchMessage);
}
// 获得所有annotationType注解的属性
private ListString getCandidates(AnnotatedTypeMetadata metadata, Class? annotationType) {MultiValueMap attributes metadata.getAllAnnotationAttributes(annotationType.getName(), true);ArrayList candidates new ArrayList();if(attributes null) {return Collections.emptyList();} else {this.addAll(candidates, (List)attributes.get(value));this.addAll(candidates, (List)attributes.get(name));return candidates;}
}
private void addAll(ListString list, ListObject itemsToAdd) {if(itemsToAdd ! null) {Iterator var3 itemsToAdd.iterator();while(var3.hasNext()) {Object item var3.next();Collections.addAll(list, (String[])((String[])item));}}
}
// 根据matchType.matches方法来进行匹配
private ListString getMatches(CollectionString candidates, OnClassCondition.MatchType matchType, ClassLoader classLoader) {ArrayList matches new ArrayList(candidates.size());Iterator var5 candidates.iterator();while(var5.hasNext()) {String candidate (String)var5.next();if(matchType.matches(candidate, classLoader)) {matches.add(candidate);}}return matches;
}关于match的具体实现在MatchType中它是一个枚举类提供了PRESENT和MISSING两种实现前者返回类路径中是否存在该类后者相反。
private static enum MatchType {PRESENT {public boolean matches(String className, ClassLoader classLoader) {return OnClassCondition.MatchType.isPresent(className, classLoader);}},MISSING {public boolean matches(String className, ClassLoader classLoader) {return !OnClassCondition.MatchType.isPresent(className, classLoader);}};private MatchType() {}// 跟我们之前看过的案例一样都利用了类加载功能来进行判断private static boolean isPresent(String className, ClassLoader classLoader) {if(classLoader null) {classLoader ClassUtils.getDefaultClassLoader();}try {forName(className, classLoader);return true;} catch (Throwable var3) {return false;}}private static Class? forName(String className, ClassLoader classLoader) throws ClassNotFoundException {return classLoader ! null?classLoader.loadClass(className):Class.forName(className);}public abstract boolean matches(String var1, ClassLoader var2);
}现在终于真相大白ConditionalOnClass的含义是指定的类必须存在于类路径下MongoDataAutoConfiguration类中声明了类路径下必须含有Mongo.class, MongoTemplate.class这两个类否则该自动配置类不会被加载。
在Spring Boot中到处都有类似的注解像ConditionalOnBean容器中是否有指定的BeanConditionalOnWebApplication当前工程是否为一个Web工程等等它们都只是Conditional注解的扩展。当你揭开神秘的面纱去探索本质时发现其实Spring Boot自动配置的原理就是如此简单在了解这些知识后你完全可以自己去实现自定义的自动配置类然后编写出自定义的starter。
4、总结
SpringBoot 的 自动配置得益于 SpringFramework 强大的支撑框架早已有很多工具和注解可以自动装配 Bean 。SpringBoot 通过 一个封装将市面上通用的组件直接写好了配置类。当我们程序去依赖了这些组件的 jar 包后启动 SpringBoot应用于是自动加载开始了。
我们也可以定义自己的自动装配组件依赖之后Spring直接可以加载我们定义的 starter 。
加载步骤
1SpringBoot启动会加载大量的自动配置类 2我们看我们需要的功能有没有SpringBoot默认写好的自动配置类 3我们再来看这个自动配置类中到底配置了哪些组件只要我们要用的组件有我们就不需要再来配置了 4给容器中自动配置类添加组件的时候会从properties类中获取某些属性。我们就可以在配置文件中指定这 些属性的值