房地产行业政策,上海SEO网站优化推广公司,便捷网站建设价格,wordpress商城模板添加产品springboot使用起来确实很方便#xff0c;做到开箱即用#xff0c;减少了许多繁琐的配置。不过在使用过程中时常会想#xff0c;为啥会这样方便#xff0c;springboot为我们做哪些工作。或者是我们在使用的过程中#xff0c;会遇到springboot不满足的情况#xff0c;我们…springboot使用起来确实很方便做到开箱即用减少了许多繁琐的配置。不过在使用过程中时常会想为啥会这样方便springboot为我们做哪些工作。或者是我们在使用的过程中会遇到springboot不满足的情况我们要去了解内部实现机制然后才能改进。过去我们对这种方式确很少去了解或者是了解了一些但是没有彻底搞清楚。今天我们就学习一下springboot相关的几个问题希望能够揭开一些疑问。相信对这些看似比较基础的知识地理解会给我们设计程序带来好的思路。注解spring最开始大量使用xml进行配置当然也支持注解进行配置springboot做了很多自动化的工作进行默认配置在此过程中将注解发挥到极致。所以在此之前先回顾一下注解的基本知识。注解为代码添加信息提供了一种形式化的方法使得我们可以在后面某个时刻非常方便地使用这些数据。java5引入注解的有需要多好处完整地描述程序需要的信息相比于增加其他非java语言的文件对程序描述这样使得代码可读性变差并且不容易检查。可以生成新的描述符文件设置是新的类定义可以减少许多重复的模板代码。注解定义和常见的注解java中内置了几个常见的注解这也是我们经常在代码中见到的。Override 用来表示覆盖父类中的方法如果方法签名写错编译器将会报错该注解是可选的。Deprecated 表示废弃的方法或者字段如果程序中使用了会报出警告。SuppressWarnings 关闭不当的编译器警告信息注解的定义需要使用到元注解顾名思义元注解就是用来定义注解的注解,例如Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)public interface Test{}这样就定义了一个注解注解的定义和接口定义非常类似编译后也会生成一个class文件。如果像上面的注解一样不包含任何元素叫做标记注解。可以包含元素就类似于接口的方法定义。但是和方法定义又有一些区别访问权修饰符为默认或者public类型为八种基本类型和String,Enum,Class,annotations类型以及它们的数组。值得说明的是如果为annotation类型说明这个注解是嵌套注解。成员名字自定义另外有一点不同的是相比接口方法的定义这里可以设置默认值Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)public interface Test{public String value() default some value;public int test();//没有默认值}如果成员里面有一个名字是value并且是唯一一个被赋值的成员那么不需要写出成员名字直接赋值例如:Test(a)public void hello(){…}四种元注解Target 表示注解作用的对象ElementType参数包括CONSTRUCTOR,构造器声明FIELD域声明LOCAL_VARIABLE,局部变量声明METHOD方法声明PACKAGE包声明PARAMETER参数声明TYPE类接口包括注解类型或enum声明,对于注解类型特别要提到的是这样可以自定义注解的注解。Retention Retention是保留的意思这个注解说的是被注解的注解被保留的级别RetentionPolicy可选有SOURCE保留在源码中也就是说编译成class文件不会有该注解被编译器丢弃了CLASS保留在class文件中但是在虚拟机运行的时候会丢弃该注解RUNTIME保留在运行期一般都是通过反射机制读取注解的信息Documented 表示注解包含在javadoc中Inherited 表示子类可以继承父类的注解上面说到RUNTIME的注解类都实现了AnnotatedElement接口具有getAnnotation()的实现方法可以获取到某个类型的注解Test test method.getAnnocation(Test.class);Enable*注解既然我们搞清楚了注解的基本使用方法那么让我们还看一看springboot是怎样使用注解的。对于很多功能的启用的开关是加上了Enable*的注解例如开启eurekaEnableDiscoveryClient以此为例搞清楚这类注解的使用原理首先看这个注解的定义Target(ElementType.TYPE) //作用在类上面Retention(RetentionPolicy.RUNTIME) //jvm使用该注解Documented //javadoc包含该注解Inherited //子类可以继承该注解Import(EnableDiscoveryClientImportSelector.class) //自定义元注解public interface EnableDiscoveryClient {/*** If true, the ServiceRegistry will automatically register the local server.*/boolean autoRegister() default true; //将该应用注册到eureka server}元素就一个boolean类型的直接说明的是这里使用了一个自定义的元注解也就是该注解的Target是ElementType.TYPE,其中的TYPE指的是一个注解定义的类Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)Documentedpublic interface Import {/*** {link Configuration}, {link ImportSelector}, {link ImportBeanDefinitionRegistrar}* or regular component classes to import.*/Class[] value();}这个Import注解非常重要可以看到有一个唯一的默认的元素这个元素名字叫value后面为元素赋值的时候可以不写value类型是一个class数组。我们知道这个Import注解是为了引入一个类下面来看一下注解处理器做了什么操作package org.springframework.context.annotation;/*** Recursively collect all declared {code Import} values. Unlike most* meta-annotations it is valid to have several {code Import}s declared with* different values; the usual process of returning values from the first* meta-annotation on a class is not sufficient.* For example, it is common for a {code Configuration} class to declare direct* {code Import}s in addition to meta-imports originating from an {code Enable}* annotation.* param sourceClass the class to search* param imports the imports collected so far* param visited used to track visited classes to prevent infinite recursion* throws IOException if there is any problem reading metadata from the named class*/private void collectImports(SourceClass sourceClass, Set imports, Set visited)throws IOException {if (visited.add(sourceClass)) {for (SourceClass annotation : sourceClass.getAnnotations()) {String annName annotation.getMetadata().getClassName();if (!annName.startsWith(java) !annName.equals(Import.class.getName())) {collectImports(annotation, imports, visited);}}imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), value));}}上面的代码片段是从spring中摘录出来将Import注解中value的类加到集合中后续生成bean进行加载。也就是说会把EnableDiscoveryClientImportSelector这个类生成bean加载到上下文中。Order(Ordered.LOWEST_PRECEDENCE - 100)public class EnableDiscoveryClientImportSelectorextends SpringFactoryImportSelector {Overridepublic String[] selectImports(AnnotationMetadata metadata) {String[] imports super.selectImports(metadata);AnnotationAttributes attributes AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(getAnnotationClass().getName(), true));boolean autoRegister attributes.getBoolean(autoRegister);if (autoRegister) {List importsList new ArrayList(Arrays.asList(imports));importsList.add(org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration);imports importsList.toArray(new String[0]);}return imports;}Overrideprotected boolean isEnabled() {return new RelaxedPropertyResolver(getEnvironment()).getProperty(spring.cloud.discovery.enabled, Boolean.class, Boolean.TRUE);}Overrideprotected boolean hasDefaultFactory() {return true;}}这个类由于继承了SpringFactoryImportSelectorspring会进行import操作在这个类里面引入了org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationConfiguration这个类所以使得该应用开启了eureka客户端的功能。自动配置通过源码可以发现SpringBootApplication注解定义中有有元注解EnableAutoConfiguration让我们看一下这个定义package org.springframework.boot.autoconfigure;SuppressWarnings(deprecation)Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedInheritedAutoConfigurationPackageImport(EnableAutoConfigurationImportSelector.class)public interface EnableAutoConfiguration {String ENABLED_OVERRIDE_PROPERTY spring.boot.enableautoconfiguration;/*** Exclude specific auto-configuration classes such that they will never be applied.* return the classes to exclude*/Class[] exclude() default {};/*** Exclude specific auto-configuration class names such that they will never be* applied.* return the class names to exclude* since 1.3.0*/String[] excludeName() default {};}所有自动配置功能都由AutoConfigurationPackage和Import(EnableAutoConfigurationImportSelector.class)这两个注解来决定。AutoConfigurationPackage这个注解的源码如下package org.springframework.boot.autoconfigure;import java.lang.annotation.Documented;import java.lang.annotation.ElementType;import java.lang.annotation.Inherited;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;import org.springframework.context.annotation.Import;/*** Indicates that the package containing the annotated class should be registered with* {link AutoConfigurationPackages}.** author Phillip Webb* since 1.3.0* see AutoConfigurationPackages*/Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedInheritedImport(AutoConfigurationPackages.Registrar.class)public interface AutoConfigurationPackage {}通过注释可以知道这个注解的作用是将被该注解进行注解的类所在的包进行扫描。EnableAutoConfiguration被注解了而该类在org.springframework.boot.autoconfigure包下面所以会扫描该包下的类声明成bean的将会生成bean。/*** {link ImportBeanDefinitionRegistrar} to store the base package from the importing* configuration.*/static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {Overridepublic void registerBeanDefinitions(AnnotationMetadata metadata,BeanDefinitionRegistry registry) {register(registry, new PackageImport(metadata).getPackageName());}Overridepublic Set determineImports(AnnotationMetadata metadata) {return Collections.singleton(new PackageImport(metadata));}}可以看到将BasePackages注册在了注册中心,BasePackages中含有包名public static void register(BeanDefinitionRegistry registry, String... packageNames) {if (registry.containsBeanDefinition(BEAN)) {BeanDefinition beanDefinition registry.getBeanDefinition(BEAN);ConstructorArgumentValues constructorArguments beanDefinition.getConstructorArgumentValues();constructorArguments.addIndexedArgumentValue(0,addBasePackages(constructorArguments, packageNames));}else {GenericBeanDefinition beanDefinition new GenericBeanDefinition();beanDefinition.setBeanClass(BasePackages.class);beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);registry.registerBeanDefinition(BEAN, beanDefinition);}}这个bean的名字叫做AutoConfigurationPackages需要说明的是此时并没有扫描包。正如该类的注释所说是为了存储需要自动配置的包以便于后面的扫描器进行扫描。* Class for storing auto-configuration packages for reference later (e.g. by JPA entity* scanner).也许此时会有个疑问为啥不用ComponentScan进行包扫描实际上在org.springframework.boot.autoconfigure包下面并不是所有的组件都需要包扫描只有几个组件的集成才需要例如cassandra、jpa等一般都是这样的代码BeanConditionalOnMissingBeanpublic CassandraMappingContext cassandraMapping(CassandraCustomConversions conversions) throws ClassNotFoundException {CassandraMappingContext context new CassandraMappingContext();List packages EntityScanPackages.get(this.beanFactory).getPackageNames();if (packages.isEmpty() AutoConfigurationPackages.has(this.beanFactory)) {packages AutoConfigurationPackages.get(this.beanFactory);}if (!packages.isEmpty()) {context.setInitialEntitySet(CassandraEntityClassScanner.scan(packages));}if (StringUtils.hasText(this.properties.getKeyspaceName())) {context.setUserTypeResolver(new SimpleUserTypeResolver(this.cluster,this.properties.getKeyspaceName()));}context.setCustomConversions(conversions);return context;}上述代码说明如果存在EntityScan注解的包则只需要scan该包即可如果不存在则将autoconfigure都scan了。需要说明的是该注解不是每个组件的自动配置都会用到只有一部分用到比较重要的是下面的自动配置功能。EnableAutoConfigurationImportSelector的作用下面是最关键的源码Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {if (!isEnabled(annotationMetadata)) {return NO_IMPORTS;}try {AutoConfigurationMetadata autoConfigurationMetadata AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);AnnotationAttributes attributes getAttributes(annotationMetadata);List configurations getCandidateConfigurations(annotationMetadata,attributes);configurations removeDuplicates(configurations);configurations sort(configurations, autoConfigurationMetadata);Set exclusions getExclusions(annotationMetadata, attributes);checkExcludedClasses(configurations, exclusions);configurations.removeAll(exclusions);configurations filter(configurations, autoConfigurationMetadata);fireAutoConfigurationImportEvents(configurations, exclusions);return configurations.toArray(new String[configurations.size()]);}catch (IOException ex) {throw new IllegalStateException(ex);}}protected boolean isEnabled(AnnotationMetadata metadata) {if (getClass().equals(AutoConfigurationImportSelector.class)) {return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,true);}return true;}这个类会选择哪些类会被引入并且生成一个Bean如果属性spring.boot.enableautoconfiguration为false将会关闭自动配置。如果开启的话会将META-INFO目录下的配置文件的配置读进来选择需要引入的Bean。org.springframework.boot.autoconfigure.EnableAutoConfiguration\org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\...以RabbitMq进行举例说明需要引入RabbitAutoConfiguration这个Bean。RabbitAutoConfiguration的作用下面是该类的定义的一部分ConfigurationConditionalOnClass({ RabbitTemplate.class, Channel.class })EnableConfigurationProperties(RabbitProperties.class)Import(RabbitAnnotationDrivenConfiguration.class)public class RabbitAutoConfiguration {ConfigurationConditionalOnMissingBean(ConnectionFactory.class)Configuration注解说明是一个配置Bean。EnableConfigurationProperties(RabbitProperties.class),该注解的源码如下Target(ElementType.TYPE)Retention(RetentionPolicy.RUNTIME)DocumentedImport(EnableConfigurationPropertiesImportSelector.class)public interface EnableConfigurationProperties {/*** Convenient way to quickly register {link ConfigurationProperties} annotated beans* with Spring. Standard Spring Beans will also be scanned regardless of this value.* return {link ConfigurationProperties} annotated beans to register*/Class[] value() default {};}其中EnableConfigurationPropertiesImportSelector在生成Bean的时候如果发现没有RabbitProperties这个bean会生成这个bean然后我们又发现这个bean有ConfigurationProperties凡事有这个注解的Bean都会将配置文件的属性映射到这个Bean的字段上。ConfigurationProperties(prefix spring.rabbitmq)public class RabbitProperties {所以此时会有RabbitProperties这个Bean存在。基于此会生成许多其他配置Bean例如CachingConnectionFactory这个Bean就含有文件的配置信息供后面RabbitMq连接通信使用。这样就完成了自动配置。Conditional条件注解上面的注解中用到了条件注解是值得关注的地方。上面通过自动配置得到了RabbitMq的连接配置Bean也就是CachingConnectionFactory这个类的定义是在org.springframework.amqp.rabbit.connection中实际上使用rabbit的核心类可能是在另外一个jar包中也就是说了配置类但是没有rabbitmq的操作类也没有作用怎样确保有核心类然后再进行加载配置Bean呢还有一个问题需要思考如果用户自定义了一个连接配置Bean而不是使用自动配置Bean这些问题该怎么解决呢实际上springboot定义了非常灵活的条件注解ConditionalOnMissingBean(ConnectionFactory.class)再去看看这个注解的定义Target({ ElementType.TYPE, ElementType.METHOD })Retention(RetentionPolicy.RUNTIME)DocumentedConditional(OnBeanCondition.class)public interface ConditionalOnMissingBean {/*** The class type of bean that should be checked. The condition matches when each* class specified is missing in the {link ApplicationContext}.* return the class types of beans to check*/Class[] value() default {};/*** The class type names of bean that should be checked. The condition matches when* each class specified is missing in the {link ApplicationContext}.* return the class type names of beans to check*/String[] type() default {};/**Conditional这个注解是spring中定义的它的成员是一些条件类比如现在是OnBeanCondition.class。这里面核心的方法是判断ConditionalOnMissingBean中的value这个Bean是否存在if (metadata.isAnnotated(ConditionalOnMissingBean.class.getName())) {BeanSearchSpec spec new BeanSearchSpec(context, metadata,ConditionalOnMissingBean.class);MatchResult matchResult getMatchingBeans(context, spec);if (matchResult.isAnyMatched()) {String reason createOnMissingBeanNoMatchReason(matchResult);return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingBean.class, spec).because(reason));}matchMessage matchMessage.andCondition(ConditionalOnMissingBean.class, spec).didNotFind(any beans).atAll();}除了这些条件以外还有其他条件列举如下ConditionalOnJava 系统的java版本是否符合要求ConditionalOnBean 容器中存在指定BeanConditionalOnMissingBean 容器中不存在指定BeanConditionalOnExpression 满足SpEL表达式指定ConditionalOnClass 系统中有指定的类ConditionalOnMissingClass 系统中没有指定的类ConditionalOnSingleCandidate 容器中只有一个指定的Bean或者这个Bean是首选BeanConditionalOnProperty 系统中指定的属性是否有指定的值ConditionalOnResource 类路径下是否存在指定资源文件ConditionalOnWebApplication 当前是web环境ConditionalOnNotWebApplication 当前不是web环境ConditionalOnJndi JNDI存在指定项这样我们就把整个自动配置大致流程搞清楚了。4