欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SpringBoot进阶篇

程序员文章站 2022-07-15 11:09:20
...

今天的博客主题

       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>标签然后在使用

SpringBoot进阶篇

看这就是没哟对比就没有伤害。

 

自动装配前的几个开胃菜

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是如何进行自动装配的呢?

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);
        }
    }
}

不耻下问

相关标签: SpirngBoot