zoukankan      html  css  js  c++  java
  • SpringBoot笔记

    1.Spring Boot 简介

    Spring Boot是由Pivotal团队提供的快速开发框架,基于SpringMVC通过注解+内置Http服务器如:tomcat-embed-core,简化了XML配置,快速将一些常用的第三方依赖整合(通过Maven继承依赖关系),最终实现以Java应用程序的方式进行执行。

    1.1 SpringBoot起源

    • Spring框架:Spring框架从早期的IOC与AOP衍生出了很多产品例如Spring (boot、security、jpa)等等
    • SpringMVC框架:Spring MVC提供了一种轻度耦合的方式来开发web应用,它是Spring的一个web框架。通过Dispatcher Servlet, ModelAndView 和 View Resolver,开发web应用变得很容易。解决的问题领域是网站应用程序或者服务开发——URL路由、Session、模板引擎、静态Web资源等等,是基于Spring的一个 MVC 框架。
    • SpringBoot框架:Spring Boot实现了自动配置,降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题,所以它并不是用来替代Spring的解决方案,而是和Spring框架紧密结合用于提升Spring开发者体验的工具。同时它集成了大量常用的第三方库配置(例如Jackson, JDBC, Mongo, Redis, Mail等等),是基于Spring4的条件注册的一套快速开发整合包。

    1.2 便捷的starter poms (启动器)

    starter包含了搭建项目快速运行所需的依赖。它是一个依赖关系描述符的集合。当应用需要一种spring的其它服务时,不需要粘贴拷贝大量的依赖关系描述符。例如想在spring中使用redis,只需要在项目中包含 spring-boot-starter-redis 依赖就可以使用了,所有的starters遵循一个相似的命名模式:spring-boot-starter-,在这里是一种特殊类型的应用程序。该命名结构可以帮你找到需要的starter。很多IDEs集成的Maven允许你通过名称搜索依赖。

    1.3 SpringBoot启动流程

    2. 元注解

    在JDK 1.5中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为 元注解(meta-annotation)。

    • @Target:描述注解的使用范围

      public enum ElementType {
          TYPE, // 类、接口、枚举类
          FIELD, // 成员变量(包括:枚举常量)
          METHOD, // 成员方法
          PARAMETER, // 方法参数
          CONSTRUCTOR, // 构造方法
          LOCAL_VARIABLE, // 局部变量
          ANNOTATION_TYPE, // 注解类
          PACKAGE, // 可用于修饰:包
          TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
          TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
      }
      
    • @Retention:描述注解保留的时间范围(即:被描述的注解在它所修饰的类中可以被保留到哪个阶段) 。

      public enum RetentionPolicy {
          SOURCE,    // 只在源代码级别保留,编译时就会被忽略
          CLASS,     // 编译期保留,在class文件中存在,但JVM将会忽略,默认值
          RUNTIME    // 运行期保留,被JVM保留,可通过反射去获取注解信息
      }
      
    • @Documented:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中。

    • @Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)。允许子类继承父类的注解,仅限于类注解有用,对于方法和属性无效。

    3. @SpringBootApplication

    是一个组合注解

    @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 {
    

    3.1 @SpringBootConfiguration

    查看该注解的源码注解可知,该注解与@Configuration 注解功能相同,仅表示当前类为一个JavaConfig类,其就是为Spring Boot专门创建的一个注解。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {}
    

    3.2 @ComponentScan

    用于完成组件扫描。不过需要注意,其仅仅用于配置组件扫描指令,并没有真正扫描,更没有装配其中的类,这个真正扫描是由@EnableAutoConfiguration完成的。

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
        @AliasFor("basePackages")
        String[] value() default {};
    
        @AliasFor("value")
        String[] basePackages() default {};
        
        Class<?>[] basePackageClasses() default {};
        
        ComponentScan.Filter[] includeFilters() default {};
        ComponentScan.Filter[] excludeFilters() default {};
    

    相当于Spring XML配置文件中的:<context:component-scan>,可使用basePackages属性指定要扫描的包,及扫描的条件。如果不设置则默认扫描@ComponentScan注解所在类的同级类和同级目录下的所有类,所以我们的Spring Boot项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。

    3.3 @EnableXxx

    @EnableXxx注解一般用于开启某一项功能,是为了简化代码的导入,即使用了该类注解,就会自动导入某些类。所以该类注解是组合注解,一般都会组合一个@Import注解,用于导入指定的多个类。@EnableXxx的功能主要体现在这些被导入的类上,而被导入的类一般分为三种:

    配置类

    @Import中指定的类一般以Configuration结尾,且该类上还会注解@Configuration,表示当前类是一个配置类,是一个JavaConfig类。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Import({SchedulingConfiguration.class})
    @Documented
    public @interface EnableScheduling {
    }
    
    @Configuration
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public class SchedulingConfiguration {
    

    选择器

    @Import中指定的类一般以Selector结尾,且该类一般还实现了ImportSelector接口,表示当前类会根据条件选择导入不同的类。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import({CachingConfigurationSelector.class})
    public @interface EnableCaching {
    
    public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
        private static final String PROXY_JCACHE_CONFIGURATION_CLASS = "org.springframework.cache.jcache.config.ProxyJCacheConfiguration";
        private static final String CACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJCachingConfiguration";
        private static final String JCACHE_ASPECT_CONFIGURATION_CLASS_NAME = "org.springframework.cache.aspectj.AspectJJCacheConfiguration";
        
        public String[] selectImports(AdviceMode adviceMode) {
            switch(adviceMode) {
            case PROXY:
                return this.getProxyImports();
            case ASPECTJ:
                return this.getAspectJImports();
            default:
                return null;
            }
        }
    }
    

    注册器

    @Import中指定的类一般以Registrar结尾,且该类实现了ImportDeanDefinitionRegistrar接口,用于导入注册器。该类可以在代码运行时动态注册指定类的实例。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import({AspectJAutoProxyRegistrar.class})
    public @interface EnableAspectJAutoProxy {
    
    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
        AspectJAutoProxyRegistrar() {}
        
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
            AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
            if (enableAspectJAutoProxy != null) {
                if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }
                if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                    AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
                }
            }
        }
    }
    

    3.4 @EnableAutoConfiguration

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import({AutoConfigurationImportSelector.class})
    public @interface EnableAutoConfiguration {
    

    该注解用于开启自动配置,是SpringBoot的核心注解,是一个组合注解。所谓自动配置是指,其会自动找到其所需要的类,然后交给Spring容器完成这些类的装配。

    • @AutoConfigurationPackage 注解用于保存自动配置类以供之后的使用,比如给JPA entity扫描器,用来扫描开发人员通过注解@Entity定义的entity类。通俗的讲就是,注册bean定义到容器中。
    • @Import(AutoConfigurationImportSelector.class)是EnableAutoConfiguration注解中最关键的来,它借助AutoConfigurationImportSelector,可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器中。

    3.4.1 @Import

    用于导入框架本身所包含的自动配置相关的类。其参数AutoConfigurationImportSelector类,该类用于导入自动配置的类。

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
        // 返回应该导入的自动配置的类名
    	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;
        }
    
    public final class SpringFactoriesLoader {
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
        
        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(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION); // META-INF/spring.factories
                    LinkedMultiValueMap result = new LinkedMultiValueMap();
                    
           ...
        }
    



    3.4.2 @AutoConfigurationPackage

    用于导入用户自定义类,即自动扫描包中的类。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import({Registrar.class})
    public @interface AutoConfigurationPackage {
    }
    
    static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {}
    
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());
        }
    
        public Set<Object> determineImports(AnnotationMetadata metadata) {
            return Collections.singleton(new AutoConfigurationPackages.PackageImport(metadata));
        }
    }
    

    在AutoConfigurationPackages.register(registry, (new AutoConfigurationPackages.PackageImport(metadata)).getPackageName());打断点会发现getPackageName()就是启动类所在的包。

    4. application.yml的加载

    application.yml文件对于SpringBoot来说是核心配置文件,至关重要,那么,改文件是如何加载到内存的呢?需要从启动类的run()方法开始跟踪。

    1. 启动方法run()的跟踪

    SpringApplication #

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }
    
    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return (new SpringApplication(primarySources)).run(args);
    }
    
    2. 准备运行环境

    3.让监听器监听环境准备过程

    SpringApplicationRunListeners#

    public void environmentPrepared(ConfigurableEnvironment environment) {
        Iterator var2 = this.listeners.iterator();
        while(var2.hasNext()) {
            SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next();
            listener.environmentPrepared(environment);
        }
    }
    
    4. 广播环境准备事件

    EventPublishingRunListener#

    public void environmentPrepared(ConfigurableEnvironment environment) {
        this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
    }
    

    SimpleApplicationEventMulticaster#

    public void multicastEvent(ApplicationEvent event) {
        this.multicastEvent(event, this.resolveDefaultEventType(event));
    }
    public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
        Iterator var4 = this.getApplicationListeners(event, type).iterator();
    
        while(var4.hasNext()) {
            ApplicationListener<?> listener = (ApplicationListener)var4.next();
            Executor executor = this.getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> {
                    this.invokeListener(listener, event); // 
                });
            } else {
                this.invokeListener(listener, event);
            }
        }
    }
    
    5. 触发监听器






    6.加载配置文件
    protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
        RandomValuePropertySource.addToEnvironment(environment);
        (new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load();
    }
    

    private void load(ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.DocumentFilterFactory filterFactory, ConfigFileApplicationListener.DocumentConsumer consumer) {
        this.getSearchLocations().forEach((location) -> {
            boolean isFolder = location.endsWith("/");
            Set<String> names = isFolder ? this.getSearchNames() : ConfigFileApplicationListener.NO_SEARCH_NAMES;
            names.forEach((name) -> {
                this.load(location, name, profile, filterFactory, consumer); //
            });
        });
    }
    

    选择是YamlProperty还是Properties


    开始加载啦~

    在return propertySources;加断点调试可以看到加载的yml文件。

    5.SpringBoot整合Redis

    在spring.factories中有一个RedisAutoConfiguration类,Spring容器自动装配该类。

    @Configuration
    @ConditionalOnClass({RedisOperations.class})
    @EnableConfigurationProperties({RedisProperties.class})
    @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
    public class RedisAutoConfiguration {
        public RedisAutoConfiguration() {}
    
        @Bean
        @ConditionalOnMissingBean(name = {"redisTemplate"} )//如果当前容器没有这个Bean则创建之
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
            RedisTemplate<Object, Object> template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    5.1 @ConditionalOnClass({RedisOperations.class})

    @ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
    @ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
    @ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
    @ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean

    @ConditionalOnClass({RedisOperations.class})

    这个接口的实现类就是RedisTemplate,提供了一些对Redis命令的一些操作。

    5.2 @EnableConfigurationProperties({RedisProperties.class})
    @ConfigurationProperties(prefix = "spring.redis")
    public class RedisProperties {
        private int database = 0;
        private String url;
        private String host = "localhost";
        private String password;
        private int port = 6379;
        private boolean ssl;
        private Duration timeout;
        private RedisProperties.Sentinel sentinel;
        private RedisProperties.Cluster cluster;
        private final RedisProperties.Jedis jedis = new RedisProperties.Jedis();
        private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
    
    

    6.Mybatis与SpringBoot的整合

    # Auto Configure
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
    
    @Configuration
    @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
    @ConditionalOnBean({DataSource.class})
    @EnableConfigurationProperties({MybatisProperties.class})
    //在加载MybatisAutoConfiguration之前先加载DataSourceAutoConfiguration
    @AutoConfigureAfter({DataSourceAutoConfiguration.class})
    public class MybatisAutoConfiguration {
        private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
        private final MybatisProperties properties;
    

    该类还包含两个创建的Bean

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
    
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        ExecutorType executorType = this.properties.getExecutorType();
        return executorType != null ? 
            new SqlSessionTemplate(sqlSessionFactory, executorType) 
            : new SqlSessionTemplate(sqlSessionFactory);
    }
    

    区别:SpringBoot整合redisMybatis与SpringBoot的整合最大的区别就是,redis的自动配置类是SpringBoot提供的,而mybatis则是自己提供的。

    7.自定义Starter

    7.1 Starter工程命名

    Spring官方定义的Starter通常命名遵循的格式为spring-boot-starter-{name},例如spring-boot-starter-web。

    Spring官方建议,非官方Starter命名应遵循{name}-spring-boot-starter的格式。例如,dubbo-spring-boot-starter。

    7.2 实现

    实现功能:为用户提供的字符串添加前后缀,前缀后缀定义在yml或properties配置文件。

    1.创建工程,导入Configuration Processor依赖。

    2.定义Service

    public class SomeService {
        private String before;
        private String after;
    
        public SomeService(String before, String after) {
            this.before = before;
            this.after = after;
        }
    
        public String wrap(String word) {
            return before + word + after;
        }
    }
    

    3.定义配置属性封住类

    @ConfigurationProperties("some.service")
    public class SomeServiceProperties {
        // 读取配置文件中的如下两个属性值
        // some.service.prefix
        // some.service.surfix
        private String prefix;
        private String surfix;
    
        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }
    
        public void setSurfix(String surfix) {
            this.surfix = surfix;
        }
    
        public String getPrefix() {
            return prefix;
        }
    
        public String getSurfix() {
            return surfix;
        }
    }
    

    4.定义自动配置类

    @Configuration
    @ConditionalOnClass(SomeService.class)
    @EnableConfigurationProperties(SomeServiceProperties.class)
    public class SomeServiceAutoConfiguration {
        @Autowired
        private SomeServiceProperties properties;
        // 注意,以下两个方法的顺序是不能颠倒的
        @Bean
        @ConditionalOnProperty(name = "some.service.enable", havingValue = "true", matchIfMissing = true)
        public SomeService someService() {
            return new SomeService(properties.getPrefix(), properties.getSurfix());
        }
    
        @Bean
        @ConditionalOnMissingBean
        public SomeService someService2() {
            return new SomeService("", "");
        }
    }
    

    5.创建spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.hdu.config.SomeServiceAutoConfiguration
    

    代码:https://github.com/kuotian/TestSpring/tree/master/02wrap-spring-boot-starter

    测试代码:https://github.com/kuotian/TestSpring/tree/master/02wrap-test

    效果:

    参考资料

    Spring Boot源码分析-启动原理
    https://segmentfault.com/a/1190000020359093

  • 相关阅读:
    共享单车新规出台,或将引领行业走向正轨
    共享单车新规出台,或将引领行业走向正轨
    C#基础——谈谈.NET异步编程的演变史
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
  • 原文地址:https://www.cnblogs.com/kuotian/p/13184638.html
Copyright © 2011-2022 走看看