zoukankan      html  css  js  c++  java
  • SpringBoot学习笔记(二)

    本文主要浅析SpringBoot:

    (如需深入理解,还需要从实践和学习中获得)

    1.pom.xml

    (查看其)父依赖

    <!--父依赖-->
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.3</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    

    点进去发现,其还有一个父依赖

      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.4.3</version>
      </parent>
    

    这里才是整整管理SpringBoot应用里面所有依赖版本的地方,SpringBoot的版本控制中心;

    2.启动器

    springboot-boot-starter-xxxx:就是SpringBoot的场景启动器。比如:

    <!--web场景启动器-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    SpringBoot将所有的功能场景都抽取出来,做成一个个启动器,项目中需要什么就可依照需求来定制,开箱即用。

    3.主启动类

    (默认如下:)

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    
    @SpringBootApplication
    public class SpringBootDemoApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBootDemoApplication.class, args);
    	}
    

    当然,默认的启动器,我们也可以更改,只需要在自定义的启动器类上加上@SpringBootApplication注解,再删掉默认的即可。

    4.部分注解解析:

    @SpringBootApplication标注在某个类上,说明这个类是SpringBoot的主配置类;
    image

    @EnableAutoConfiguration:开启自动配置功能。源码:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    
    	Class<?>[] exclude() default {};
    
    	String[] excludeName() default {};
    
    }
    

    @Import(AutoConfigurationImportSelector.class)中,AutoConfigurationImportSelector:表示自动配置导入选择器。根据源码可以找到,它有一个获得候选配置的方法getCandidateConfigurations

    public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
    		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
    		//省略部分代码
    		//获得候选的配置
    		protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
    				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;
    	}
    

    1.查看上面源码中的getSpringFactoriesLoaderFactoryClass()方法,得知它的返回值是启动自动导入配置文件的注解类;EnableAutoConfiguration
    image
    2.查看上面源码,在getCandidateConfigurations()方法内调用:SpringFactoriesLoader类的静态方法。我们进入SpringFactoriesLoader类loadFactoryNames() 方法,它里面又调用了loadSpringFactories 方法。
    image

    loadSpringFactories源码:

    private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
            Map<String, List<String>> result = (Map)cache.get(classLoader);
            if (result != null) {
                return result;
            } else {
                HashMap result = new HashMap();
    
                try {
    				//去获取一个资源 "META-INF/spring.factories"(多次发现spring.factories文件,可以全局搜索它)
                    Enumeration urls = classLoader.getResources("META-INF/spring.factories");
    				//将读取的资源遍历,封装成一个properties
                    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[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                            String[] var10 = factoryImplementationNames;
                            int var11 = factoryImplementationNames.length;
    
                            for(int var12 = 0; var12 < var11; ++var12) {
                                String factoryImplementationName = var10[var12];
                                ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                    return new ArrayList();
                                })).add(factoryImplementationName.trim());
                            }
                        }
                    }
    
                    result.replaceAll((factoryType, implementations) -> {
                        return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                    });
                    cache.put(classLoader, result);
                    return result;
                } catch (IOException var14) {
                    throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
                }
            }
        }
    

    @AutoConfigurationPackage:自动配置包。源码:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @Import(AutoConfigurationPackages.Registrar.class)
    public @interface AutoConfigurationPackage {
    
    	String[] basePackages() default {};
    
    	Class<?>[] basePackageClasses() default {};
    
    }
    

    @Import(AutoConfigurationPackages.Registrar.class)中的,@Import:是Spring底层注解,给容器中导入一个组件。Registrar.class作用:将主启动类的所在包及包下面所有子包里面的所有组件扫描到Spring容器。

    5.spring.factories

    1.根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在。
    image
    2.可以通过SpringApplication的类加载器获取

    @SpringBootApplication
    public class SpringBootDemoApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBootDemoApplication.class, args);
    		System.out.println(SpringApplication.class.getClassLoader().getResource("META-INF/spring.factories"));
    	}
    }
    

    image
    所以,自动配置的真正实现是从classpath中搜索所有的META-INF/spring.factories配置文件,并将其中对应的org.springframework.boot.autofonfigure.包下的配置项,通过反射实例化为对应标注了@Configuration的JavaConfig形式的IOC容器配置类,然后将这些都汇总成为一个实例并加载到IOC容器中。

    6.SpringApplication.run()方法

    image
    该方法主要分为两部分:1.SpringApplication的实例化,2.run方法的执行

    7.SpringApplication

    这个类主要做了一下四件事情:

    • 推断应用的类型是普通的项目还是Web项目;
    • 查找并加载所有可用初始化器,设置到initializers属性中;
    • 找出所有应用程序的监听器,设置到listeners属性中;
    • 推断并设置main方法的定义类,找到运行的主类;
      查看其构造器,源码:
     public SpringApplication(Class<?>... primarySources) {
            this((ResourceLoader)null, primarySources);
        }
    
        public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
            //省略部分代码。deduce:演绎,推断,推演
            this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
            this.webApplicationType = WebApplicationType.deduceFromClasspath();
            this.bootstrappers = new ArrayList(this.getSpringFactoriesInstances(Bootstrapper.class));
            this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
            this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
            this.mainApplicationClass = this.deduceMainApplicationClass();
        }
    

    8.run方法流程分析

    image

    9.浅析SpringBoot的自动配置原理

    SpringBoot官方文档中有大量的配置,我们无法全部记住。
    image

    下面以HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理:
    源码:

    /**
     * {@link EnableAutoConfiguration Auto-configuration} for configuring the encoding to use
     * in web applications.
     *
     * @author Stephane Nicoll
     * @author Brian Clozel
     * @since 2.0.0
     */
    @Configuration(proxyBeanMethods = false)
    @EnableConfigurationProperties(ServerProperties.class)
    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
    @ConditionalOnClass(CharacterEncodingFilter.class)
    @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
    public class HttpEncodingAutoConfiguration {
    	//和SpringBoot的配置文件映射
    	private final Encoding properties;
    	 //只有一个有参构造器的情况下,参数的值就会从容器中拿
    	public HttpEncodingAutoConfiguration(ServerProperties properties) {
    		this.properties = properties.getServlet().getEncoding();
    	}
    	//给容器中添加一个组件,这个组件的某些值需要从properties中获取
    	@Bean
    	@ConditionalOnMissingBean//判断容器没有这个组件?
    	public CharacterEncodingFilter characterEncodingFilter() {
    		CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
    		filter.setEncoding(this.properties.getCharset().name());
    		filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
    		filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
    		return filter;
    	}
    
    	@Bean
    	public LocaleCharsetMappingsCustomizer localeCharsetMappingsCustomizer() {
    		return new LocaleCharsetMappingsCustomizer(this.properties);
    	}
    
    	static class LocaleCharsetMappingsCustomizer
    			implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
    
    		private final Encoding properties;
    
    		LocaleCharsetMappingsCustomizer(Encoding properties) {
    			this.properties = properties;
    		}
    
    		@Override
    		public void customize(ConfigurableServletWebServerFactory factory) {
    			if (this.properties.getMapping() != null) {
    				factory.setLocaleCharsetMappings(this.properties.getMapping());
    			}
    		}
    
    		@Override
    		public int getOrder() {
    			return 0;
    		}
    
    	}
    
    }
    

    首先,分析HttpEncodingAutoConfiguration类上的注解:

    @Configuration(proxyBeanMethods = false):表示这是一个配置类,和以前编写的配置文件一样,也可以给容器中添加组件;
    @EnableConfigurationProperties(ServerProperties.class):表示启用指定类的ConfigurationProperties功能;查看ServerProperties:
    image

    @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET):Spring底层@Conditional注解,根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效。这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效。
    @ConditionalOnClass(CharacterEncodingFilter.class):判断当前项目有没有这个类CharacterEncodingFilter(SpringMVC中它可以是解决乱码问题的过滤器)
    image

    @ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true):表示判断配置文件中是否存在某个配置:server.servlet.encoding.enabled;如果不存在,判断也是成立的
    。即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;(matchIfMissing:match匹配、If如果、Missing缺失)

    我们去配置文件里面试试前缀,看提示:

    image

    这就是自动装配的原理!

    10.了解:@Conditional

    Spring4.0 介绍了一个新的注解@Conditional,它的逻辑语义可以作为"If…then…else…"来对bean的注册起作用。
    Conditional是由SpringFramework 提供的一个注解,位于 org.springframework.context.annotation 包内,定义如下:
    image

    SpringBoot 模块大量的使用@Conditional 注释,我们可以将Spring的@Conditional注解用于以下场景:

    • 可以作为类级别的注解直接或者间接的与@Component相关联,包括@Configuration类;
    • 可以作为元注解,用于自动编写构造性注解;
    • 作为方法级别的注解,作用在任何@Bean方法上。

    例子:

    //实现 Condition 接口
    class ConditionTest implements Condition {
       //根据Condition接口中的 matches 方法进行判断 ,如果 matches 为true 则注册Bean , 为false 则不注册Bean
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
            // 业务逻辑 ,不同的逻辑做不同的判断,根据逻辑判断是否需要注册bean
            return false ;
        }
    }
    //使用 Conditional 注解
    @Conditional(ConditionTest.class)
    class Test1 {
        public Test1() {
            System.out.println( "AbcTest");
        }
    }
    public static void main(String[] args) {
            AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Test1.class);
    }
    

    @Conditional派生注解(Spring注解版原生的@Conditional作用)
    作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

    @Conditional扩展注解 作用(判断是否满足当前指定条件)
    @ConditionalOnJava 系统的java版本是否符合要求
    @ConditionalOnBean 容器中存在指定Bean
    @ConditionalOnMissingBean 容器中不存在指定Bean
    @ConditionalOnExpression 满足SpEL表达式指定
    @ConditionalOnClass 系统中有指定的类
    @ConditionalOnMissingClass 系统中没有指定的类
    @ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
    @ConditionalOnProperty 系统中指定的属性是否有指定的值
    @ConditionalOnResource 类路径下是否存在指定资源文件
    @ConditionalOnWebApplication 当前是web环境
    @ConditionalOnNotWebApplication 当前不是web环境
    @ConditionalOnJndi JNDI存在指定项

    这也说明了,那么多的自动配置类,必须在一定的条件下才能生效;也就是说,我们加载了这么多的配置类,但不是所有的都生效了。
    如何哪些自动配置类生效?
    我们可以通过启用debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效。
    image

    控制台输出(部分截图):
    image

    说明:

    • Positive matches:自动配置类启用的:正匹配
    • Negative matches:没有启动,没有匹配成功的自动配置类:负匹配
    • Unconditional classes: 没有条件的类

    个人学习笔记,针对本人在自学中遇到的问题。

  • 相关阅读:
    switch的使用
    ArrayAdapter的使用
    android的xml中怎么实现按钮按下去变颜色
    Intent跳转的设置和Bundle的使用
    监听JList列表项的单击事件
    草稿
    Android背景图覆盖状态栏(我的手机安卓版本是4.2.2)
    RSA加密解密 (输入数值)
    仿射密码加密解密 (输入字母数值)
    Intent.ACTION_PICK
  • 原文地址:https://www.cnblogs.com/1693977889zz/p/14557599.html
Copyright © 2011-2022 走看看