spring boot 之自动装配
前言
在最初接触spring 的时候,还是使用xml进行装配,我记得很清楚,当时分别配置了spring-dao.xml , spring-service.xml , spring-controller.xml。然后把所有需要用到的扫包,注入bean,以及配置,全都一股脑的塞进xml中,虽然出发点很好,不用在java代码中硬编码了,但是xml的可读性并不是很好,那阵子,真是痛苦的要命。
正文
后来逐渐的接触到了spring boot,发现这个东西开发起来简单的要命,几乎自己不需要过多的考虑spring 与别的框架整合的问题,所有的一切spring boot已经帮我们做好了,只需要在application.properties 配置一下就完事。
但是spring boot 因为大部分的东西,已经不需要我们在着手了,所以对我们而言,“易学难精”。所以我决定好好把spring boot的原理搞清楚,今天就来说一说自动装配。
这里提前说一下,Spring boot的注入一共有四种方式:
1.通过 spring 模式注解 装配
2.通过@Enable* 注解进行装配
3.通过条件注解装配
4.通过工厂类去加载
下面我会逐一说到以上这几点。
通过 spring 模式注解 装配
模式注解的方式,尽管我们刚开始接触spring,spring mvc 框架,我们也应该会知道他们到底是怎么使用的。
以@Component为首 派生的 @Controller,@Service, @Repository。分别对应了我们工程中的controller层,业务层,和数据持久层,这里我就不做过多结束了,只要把注解放在类上就可以被自动的注入到spring 的context中。
通过@Enable* 注解进行装配
很多人会问为什么有了模式注解的装备,还需要@Enable* 这个注解呢?
这个问题问的非常好,纵观整个Web的发展,我们可以看出来,是一个化繁为简的模块化趋势。
所以@Enable* 也是具有同样道理的,例如@EnableWebMvc就是一个web组件的集合。
@Enable*注解有很多种:
例如说 @EnableAutoConfiguration,@EnableWebMvc,@EnableCaching,@EnableAsync等。
@Enable* 的注入主要又可以分成两种:
1.使用注解的方式注入
2.使用接口编程的方式注入
Tips:但是不管是哪一种方式都是通过 @Enable* 注解定义上的@Import注解引入的。
使用注解的实现(是在Serlet3.0出现的)就是说在@Enable*的@Import注解中,加载的那个类是被@Configuration注入的,例如@EnableWebMvc。
代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet.config.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//这就是上面说的哪个@Import注解,我们继续查看下DelegatingWebMvcConfiguration 这个类
@Import({DelegatingWebMvcConfiguration.class})
public @interface EnableWebMvc {
}
DelegatingWebMvcConfiguration 代码如下(只是摘取了部分,有所省略):
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet.config.annotation;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
public DelegatingWebMvcConfiguration() {
}
@Autowired(
required = false
)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
protected void configurePathMatch(PathMatchConfigurer configurer) {
this.configurers.configurePathMatch(configurer);
}
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
this.configurers.configureContentNegotiation(configurer);
}
//省略了之后的方法
}
在这里可以很清楚的看到使用@Configuration(这个注解是替代原来在spring xml中注入而出现的注解)进行注入,虽然可以实现功能,但是每次只能注入指定的Bean。
使用编程的方式(是在Serlet3.1出现的)是在@Enable*的@Import注解中,加载的那个类,实现了ImportSelector接口,并且重写 selectImports(AnnotationMetadata importingClassMetadata)方法,可以将需要注入的class名称通过条件筛选后放在一个数组中通过返回值进行注入,这是一个我们一直在用的注解,但是为什么我们并不能在朝夕相处的spring boot 中找到相关的代码呢?因为Spring boot 对其进行了封装,下面我们慢慢顺藤摸瓜的去找一下。
新创建一个工程之后,映入眼帘的就是启动类 *Application.java:
代码如下:
package com.harry.springtest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringtestApplication {
public static void main(String[] args) {SpringApplication.run(SpringtestApplication.class, args);
}
}
在main方法中只有一个类的启动方法,那么我们就应该把关注点放在@SpringBootApplication上了
@SpringBootApplication 注解代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
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.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
}
在这里我们可以看出来@SpringBootApplication是一个组合注解里面运用到了@EnableAutoConfiguration,我们在继续看一下@EnableAutoConfiguration:
package org.springframework.boot.autoconfigure;
//省略应该引入的包
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
private static final AutoConfigurationImportSelector.AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationImportSelector.AutoConfigurationEntry();
private static final String[] NO_IMPORTS = new String[0];
private static final Log logger = LogFactory.getLog(AutoConfigurationImportSelector.class);
private static final String PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE = "spring.autoconfigure.exclude";
private ConfigurableListableBeanFactory beanFactory;
private Environment environment;
private ClassLoader beanClassLoader;
private ResourceLoader resourceLoader;
public AutoConfigurationImportSelector() {
}
}
//省略以下的所有方法。
DeferredImportSelector继承了ImportSelector
spring 条件装配
条件装配一共有两种方法,可以供我们使用:
1.使用@Profile注解
2.使用@Conditional注解
@Profile只是一个站在大局上的条件装配,通过环境变量的某些值,或者是系统变量某些定值,我们可以使用不同的实现方法。这就类似于我们定义了一个接口,通过多态,定义了两个实现类实现了相同的这个接口,但是我们可以通过环境变量去决定,到底是使用哪一个实现类去实现这个方法。
@Conditional注解,就更佳的灵活一些,类似于我们平常代码中的if else 通过判断出来的boolean值,决定到底是不是应该注入这个bean。
@Conditional代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
我们可以看出来,需要往这个注解中加入一个值,这个值要实现Condition接口。
Condition接口的代码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation;
import org.springframework.core.type.AnnotatedTypeMetadata;
@FunctionalInterface
public interface Condition {
//关键之处
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
关键的地方,就在这里了,我们在实现了这个接口的时候,得去重写这个match方法,如果需要注入的地方,就应该返回true,如果是觉得不符合条件,不应该注入的就返回false。
spring boot 通过封装 @Conditional 还有一些派生注解,如下:
@ConditionalOnBean(如果在当前context中存在这个bean,实例化bean)
@ConditionalOnClass(如果在classpath中有这个class,实例化 bean)
@ConditionalOnExpression(当表达式为true的时候,实例化一个bean)
@ConditionalOnMissingBean(如果在当前context中不存在这个bean,实例化一个bean)
@ConditionalOnMissingClass(如果在classpath中没有这个class,实例化一个bean)
@ConditionalOnNotWebApplication(不是web应用,实例化bean)
spring 工厂加载机制
在auto-configuration这个jar包下,或者其他自动注入的jar下,我们都可以看到spring.factories这个文件,例如说下图:
正是通过这个文件,我们把需要注入的类的全路径配在这里,但是问题来了,谁去读这个文件的呢?
在AutoConfigurationImportSelector.class中,我们可以看到以下方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
我们继续刨根问底的看看SpringFactoriesLoader.loadFactoryNames 这个方法:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
继续看loadSpringFactories 这个方法:
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
//就在这个地方将META-INF/spring.factories读入进来的。
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
while(var6.hasNext()) {
Entry<?, ?> entry = (Entry)var6.next();
String factoryClassName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryName = var9[var11];
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
这下子终于真相大白了。
后记
通过这一次动手实践,根据源码追根溯源,我才终于明白,年轻真的得多看看源码,因为源码能带给我们的不光是在使用过程中减少bug的出现,以下解决bug的事件,更重要的是在设计上以及代码规范上的潜移默化,虽然说现在有一些还不是理解的很透彻,但是不积跬步,无以至千里。
在这里也要感觉 慕课网上小马哥的Spring Boot 2.0深度实践之核心技术篇。
在收听的过程中受益匪浅。
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
针对nginx的一系列优化方法及nginx服务器状态的查看
优化 nginx 的并发:1.修改 nginx 配置文件 [root@proxy nginx]# vim conf/nginx.conf worker_processes 1; #线程数,最大匹配 CPU 核心数,通常设 置为 auto worker_rlimit_nofile 16384; #设置文件描述符,默认 1024 events { use epoll; #使用 epoll 模式 worker_connections 1024;} #每线程并发量,最大设置 65535,匹配 最大端口号 2.修改 linux 内核参数 [root@proxy ~]# ulimit -a #查看所有属性值 [root@proxy ~]# ulimit -Hn 100000 #设置硬限制,非 root 用户允许修改的软 限制上限 [root@proxy ~]# ulimit -Sn 100000 #设置软限制,非 root 用户可以自行修改 优化 nginx 数据包头缓存 [root@proxy nginx]# vim conf/nginx.conf http{ client_header_buf...
-
下一篇
Binary Gap(二进制空白)
中文标题【二进制空白】 英文描述 A binary gap within a positive integer N is any maximal sequence of consecutive zeros that is surrounded by ones at both ends in the binary representation of N. For example, number 9 has binary representation 1001 and contains a binary gap of length 2. The number 529 has binary representation 1000010001 and contains two binary gaps: one of length 4 and one of length 3. The number 20 has binary representation 10100 and contains one binary gap of length 1. The number 15 has binary...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- SpringBoot2整合Redis,开启缓存,提高访问速度
- MySQL数据库中FOR UPDATE的使用
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2全家桶,快速入门学习开发网站教程
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- 2048小游戏-低调大师作品


微信收款码
支付宝收款码