SpringBoot进阶篇
今天的博客主题
SpringBoot ——》SpringBoot进阶篇
springBoot是如何整合springMVC
1)回顾下什么是springMVC
SpringMVC 全称(Spring Web MVC框架)是一个丰富的“模型-视图-控制器”Web框架。
SpringMVC使您可以创建特殊的@Controller或@RestController Bean来处理传入的HTTP请求。
控制器中的方法使用@RequestMapping注释映射到HTTP。
SpringMVC是Spring框架核心的一部分。
SpringBoot对spring框架做的封装,自然要为springMVC做封装进行自动装配。
2)SpringMVC的自动装配
SpringBoot为SpringMVC提供了自动配置,可以很好地与大多数应用程序配合使用。
自动配置在Spring默认设置的基础上增加了以下特性:
1)包含 ContentNegotingViewResolver 和 BeanNameViewResolver(两个试图解析器)
2)支持服务静态资源和WebJars
3)自动注册Converter,GenericConverter,Formatter
4)支持httpMessageConverter
5)自动注册MessageCodesResolver
6)静态index.html(设置首页)
7)自定义Favicon支持(图标支持)
8)自动使用可配置的WebBindingInitializerbean
如果想保留Spring Boot MVC特性,并且只想添加额外的MVC配置(比如:拦截器、格式化程序、视图控制器等),只需要添加一个类型为WebMvcConfigurerAdapter的@Configuration类,但不添加@EnableWebMvc。如果希望提供RequestMappingHandlerMapping,RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义实例,则可以声明一个提供此类组件的WebMvcRegistrationsAdapter实例。
如果要完全控制 SpringMVC,在自定义的 @Configuration 配置类 增加 @EnableWebMvc 注解
看来springBoot控制的很好,你要觉得我不行,你自己来。
WebMvcAutoConfiguration 就是对 SpringMVC 自动装配类。
新增的组件特性,上面也提到了。可以对照看下
扩展springMVC在《springboot入门篇(二)》也都有示例。
3)全面自己控制SpringMVC
为什么加个 @EnableWebMvc 注解标识到配置类,就导致springboot装配的MVC失效呢?
原理剖析:
在这个注解上通过Import注解导入:DelegatingWebMvcConfiguration 组件
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
...
}
DelegatingWebMvcConfiguration是WebMvcConfiurationSupport(只保证了springmvc的基本功能)类型的。
回头看下WebMvcAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
// 容器中没有WebMvcConfigurationSupport该配置文件才生生效,但是使用了@EnableWebMvc,就会导入WebMvcConfiurationSuppor
// 只保存了springmvc的最基本的功能
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
...
}
不推荐这么做》。。
springBoot集成第三方库
至于springboot是如何整合第三方库的就不一一写了。度娘谷歌一搜一大堆
比如你想整合redis,那就搜索引擎:springboot集成redis,太多了,千篇一律。
授人与鱼不如授人以渔。那我们什么时候想整合第三方库不搜索引擎呢?
只需三招
第一招式:找jar包
Spring官方Starter通常命名为spring-boot-starter-{name},例如 spring-boot-starter-web,
Spring官方建议非官方Starter命名应遵循{name}-spring-boot-starter的格式,如mybatis-plus-spring-boot-starter。
那就看你所用的第三方库是springboot帮你自动装配完了呢,还是第三方库自己往springboot开发方式靠拢呢。那就遵循这个命名方式去maven*仓库去找。一般你要用的这个第三方库的官方网站或者GitHub上也会提供pom的。
以redis为栗吧
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
第二招式:寻找自动装配的类和配置映射类
自动装配的类的命名方式也是有规范的,哪怕你自己实现个自动装配也要遵循。
xxxAutoConfiguration 以这种命名方式来命名你的自动装配类
比如redis的自动转配:RedisAutoConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
// 加载配置类
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
在自动装配类里面 会通过注解 @EnableConfigurationProperties(RedisProperties.class)
来加载配置映射类。配置映射类也是有规范的:xxxProperties
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {}
@ConfigurationProperties 注解在入门篇里面也有示例。
第三招式:配置文件增加相应配置参数,使用提供的组件完成需求
配置映射类都找到了,那需要什么参数也就一目了然了。
redis的需要参数:连接url,redis server host,password,port等等配置类里的都是可配的。
不配置要么默认要么启动抛出错误,你必须要配置某些指定参数。
配置完成之后,回到自动装配类。
那些有 @Bean 注解的都是给你提供的组件。
以 Bean 的方式来提供。通过注入方式给注入进来就可以使用了。。
如果按照这些都完成了,你还是不会用。
那你就去你使用的第三方库的官网文档找些Demo吧。
springBoot的自动装配
上面说了springboot整合第三方库,整合起来是非常方便的。
其究竟是怎么实现的这么方便?
说起来方便?直接说,你说啥就是啥了?
没有对比就没有伤害。。。
回想下 SSM 框架是怎么整合redis的?
第一步:导jar包。(这个都少不了)
第二步:编写 applicationContext-redis.xml 配置各种<bean></bean>标签然后在使用
看这就是没哟对比就没有伤害。
自动装配前的几个开胃菜
1)通过 @Import 注解导入 ImportSelector 组件
写一个配置类,在配置类上标注 @Import 注解,value值写需要导入的组件
@Configuration
@Import(value = SelectorTest.class)
public class SpringBootUziConfig {}
SelectorTest 实现 ImportSelector 重写 selectImports 方法,方法中就是需要导入组件的全类名
public class SelectorTest implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{"com.springboot.service.SpringBootService"};
}
}
SpringBootService 已经被注册到容器中去了,直接进行自动注入
@RestController
public class SpringBootController {
@Autowired
private SpringBootService springBootService;
@RequestMapping("/testSelector")
public String testSelector(){
springBootService.testService();
return "SUCCESS";
}
}
SpringBootService 无任何注解
public class SpringBootService {
public void testService(){
System.out.println("通过ImportSelector导入容器中的Bean");
}
}
启动工程,访问 controller 方法,控制台输出:通过ImportSelector导入容器中的Bean
2)通过 @Import 注解导入 ImportBeanDefinitionRegistrar 组件
@Configuration
@Import(value = {SelectorTest.class, SpringBootImportBeanDefinitionRegistrar.class})
public class SpringBootUziConfig {}
SpringBootImportBeanDefinitionRegistrar 实现 ImportBeanDefinitionRegistrar
public class SpringBootImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 定义一个BeanDefinition
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(SpringBootDao.class);
// 把自定义的bean定义导入到容器中
beanDefinitionRegistry.registerBeanDefinition("tulingDao",rootBeanDefinition);
}
}
同样进行直接自动注入
public class SpringBootService {
@Autowired
SpringBootDao springBootDao;
public void testService(){
System.out.println("通过ImportSelector导入容器中的Bean");
springBootDao.testDao();
}
}
同样不加任何注解
public class SpringBootDao {
public void testDao(){
System.out.println("通过ImportBeanDefinitionRegistrar导入容器中的Bean");
}
}
启动工程,访问测试,控制台输出:通过ImportBeanDefinitionRegistrar导入容器中的Bean
3)Spring 底层条件装配 @Conditional
加入两个组件:A组件,B组件。 A组件需要依赖B组件,也就是说容器中有B组件了,才会加载A组件
实现 Condition 重写 matches 方法
public class SpringBootConditional implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
// 容器中包含B组件 才返回true
if(conditionContext.getBeanFactory().containsBean("assemblyB")){
return true;
}else{
return false;
}
}
}
配置Bean
@Configuration
public class SpringBootUziConfig {
@Bean
public AssemblyB assemblyB(){
System.out.println("AssemblyB 组件加载到容器");
return new AssemblyB();
}
@Bean
@Conditional(value = SpringBootConditional.class)
public AssemblyA assemblyA(){
System.out.println("AssemblyA 组件加载到容器");
return new AssemblyA();
}
}
启动工程,访问测试,控制台输出:AssemblyB 组件加载到容器 AssemblyA 组件加载到容器
把 assemblyB() 方法上的注解注释掉,启动工程,控制台未出现 XXX 组件。
总结下spring注解导入Bean的方式
1)@Bean 注解
2)包扫描来加载Bean 比如标识了@Controller @Service @Repository @Coment
3)@Import 来注册bean
3-1)实现ImportSelector接口的类
3-2)实现ImportBeanDefinitionRegistrar 接口来注册Bean
4)实现factoryBeande的方式来导入组件
springBoot是如何进行自动装配的呢?
AutoConfigurationImportSelector.class
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
getAutoConfigurationEntry() 方法
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 去mata-info/spring.factories文件中 查询EnableAutoConfiguration对应的值
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去除重复的配置类,若我们自己写的starter可能存在重复的
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 根据maven导入的启动器过滤出需要导入的配置类
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
loadSpringFactories 去 META-INF/spring.factories 中去查询EnableAutoConfirution类
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 {
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 factoryTypeName = ((String)entry.getKey()).trim();
String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
int var10 = var9.length;
for(int var11 = 0; var11 < var10; ++var11) {
String factoryImplementationName = var9[var11];
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
} catch (IOException var13) {
throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
}
}
}
不耻下问
推荐阅读