Springboot

quickStart

前置条件

  • jdk8+
  • maven3.3+

导入依赖

对于springboot而言,想要快速构建某个模块,便引入

  • 官方:spring-boot-starter-xxx
  • 第三方:xxx-spring-boot-starter

其配置了大量的相关依赖默认配置以及版本仲裁

<!--springboot的父工程-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>


<!--用于快速启动web模块-->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

创建主程序

/**
 * 主程序类
 * @SpringBootApplication:这是一个SpringBoot应用
 * 启动springboot容器。
 */
@SpringBootApplication
public class MainApplication {

    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

编写业务

//@RestController = @Controller + @ResponseBody
//返回的数据不经过配置的InternalResourceViewResolve
//即非jsp/html文件。直接以数据形式返回
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String handle01(){
        return "Hello, Spring Boot 2!";
    }
}

测试

运行main,http://localhost:8080/hello

简化配置

application.properties中进行配置

server.port=8888

简化部署

springboot中web模块依赖spring-boot-starter-tomcat,因此无需配置tomcat并打包部署。

<build>
    <plugins>
        <!--maven打包防报错-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
            <configuration>
                <skipTests>true</skipTests>
            </configuration>
        </plugin>
        <!--能够将Spring Boot应用打包为可执行的jar-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

spring-boot-maven-plugin将项目打包为jar包,放置到目标服务器即可执行。

mvn
    clear 执行清理操作
    package 执行打包操作 -> jar

在上传的服务器中安装对应的java版本。对文件放置目录下
#执行java可执行文件,运行对应springboot程序
java -jar xxx.jar

springboot对spring的优化

依赖管理

springboot是模块化组件,只要引入starter(或第三方starter),所有常规需要的依赖我们都自动引入。常见[starter参照][https://docs.spring.io/spring-boot/docs/current/reference/html/using-spring-boot.html#using-boot-starter]

如何做到依赖管理与版本仲裁那?以spring-boot-starter-web为例

依赖管理

start-web中放置了 spring core/mvc/json/tomcat依赖。
<dependency>
    <!--内部依赖spring核心(IOC/AOP)-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.5.2</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <!--json依赖-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.5.2</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <!--内置tomcat-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.5.2</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <!--springmvc依赖-->
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.3.8</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.8</version>
    <scope>compile</scope>
</dependency>

版本仲裁

<!--每一个springboot项目都会有一个父项目-->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.2</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<!--内部定义了项目的一些初始信息
    包括jdk版本/字符编码/资源路径/插件信息与其他语言支持等-->

夫项目中的父项目有一个dependencies用作
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.3.4.RELEASE</version>
</parent>

<!--定义了常见组件的版本号用作仲裁,因此开发者引入依赖可以不写版本-->
<properties>
    <activemq.version>5.16.2</activemq.version>
    <antlr2.version>2.7.7</antlr2.version>
    <appengine-sdk.version>1.9.89</appengine-sdk.version>
    <artemis.version>2.17.0</artemis.version>
    <aspectj.version>1.9.6</aspectj.version>
    <!--等等-->
</properties>

<!--但版本仲裁中与本地系统冲突时,例如默认导入mysql 8版本,而本地使用mysql5.7则需要

修改版本号

两种方式修改 1)依赖时写死版本 2)<properties>标签-->
<mysql.version>5.1.42</mysql.version>

自动配置

如何注入组件到容器

  • 基于配置
  • 导入原生配置文件
基于配置

@Configuration

  • Full模式与Lite模式
@Configuration(proxyBeanMethods = true) full模式
@Configuration(proxyBeanMethods = false) lite模式

Lite总结:运行时不用生成CGLIB子类,不能保证单实例
         提高运行性能,降低启动时间,可以作为普通类使用
         但是不能声明@Bean之间的依赖

@Configuration(proxyBeanMethods = false) 
public class MyConfig {
    /**
     * Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        //user组件依赖了Pet组件,每次依赖的Pet对象不同
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}    

@Component、@Controller、@Service、@Repository

@ComponentScan、@Import

//指定扫描包
@ComponentScan(basePackages = "com.example.scanTest")
@Import({User.class, DBHelper.class})//指定注入类
@Configuration(proxyBeanMethods = false)
public class MyConfig {
}

Conditonal 条件装配:满足Conditional指定的条件,则进行组件注入

image-20210701111559618

导入原生配置文件

@ImportResource

@ImportResource("classpath:beans.xml")
public class MyConfig {

}

注入组件与配置文件绑定

解释一下几个与绑定相关注解的含义:

  • @ConfigurationProperties注解主要用来把配置文件(properties/yaml/yml)转化为bean来使用的
  • 而@EnableConfigurationProperties注解的作用是使@ConfigurationProperties注解生效。
//application.yaml
person:
  name: wk
  age: 13

@Compoent + @ConfigurationProperties(prefix = "person")

/**想要使用配置文件绑定,首先要注入到IOC中。
*/
@Data
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private int age;
}

@ConfigurationPrconfioperties + @EnableConfigurationProperties

EnableConfigurationProperties使ConfigurationPrconfioperties 生效

@Data
@ConfigurationProperties(prefix = "person")
public class Person {
    private String name;
    private int age;
}
@Configuration
@EnableConfigurationProperties(Person.class)
//@ComponentScan(basePackages = "com.example.scanTest")
public class MyConfig implements WebMvcConfigurer {
}

自动配置原理

自动配置原理与IOC启动/生命周期等因素有关,相对复杂,此处暂时不介绍关于IOC相关知识。

让我们简约的从主程序开始,主程序启动完成便返回自动配置好的IOC容器

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        ApplicationContext IOC = SpringApplication.run(DemoApplication.class, args);
    }
}

注意注解@SpringBootApplication

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

可以简单的理解为
**@SpringBootApplication = 
@SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan**

    @SpringBootConfiguration源码,发现就是一个@Configure
    @ComponentScan则是添加了排除包扫描规则。

重点关注@EnableAutoConfiguration,官方描述如下

    Enable auto-configuration of the Spring Application Context, attempting to guess and configure beans that you are likely to need. Auto-configuration classes are usually applied based on your classpath and what beans you have defined.
    尝试去获得默认classpath下所注册的类(@Compoent等),并猜(默认配置)可能需要的类。
    可使用配置spring.autoconfigure.exclude搞排除规则。

此处有第一个疑问?默认classpath(主程序类)是如何配置的?来看源码

@AutoConfiguratConfionPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

/*注意@AutoConfiguratConfionPackage,其中@Import(AutoConfigurationPackages.Register.class)
熟悉spring启动原理的同学应该知道容器启动清理后,先对类进行解析成元数据,然后使用Register注册到对应的BeanDefinition
AutoConfiguratConfionPackage便是根据包名,进行注册。*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

        @Override
        public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
            //通过元数据对获得报名,作为basePacket
            register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
        }
    }

第二个问题:可能使用的类是何时注入与绑定的?@Import(AutoConfigurationImportSelector.class)

/*AutoConfigurationImportSelector
其主要目的是通过用户 or 默认的方式去选择需要注入的类。并排除掉重复以及配置过滤的类。*/
//通过元数据获得需要自动配置的类。
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
        if (!isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        }
        AnnotationAttributes attributes = getAttributes(annotationMetadata);

        //获得所有需要注入的类。
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

        //排除规则 + 去重
        configurations = removeDuplicates(configurations);
        Set<String> exclusions = getExclusions(annotationMetadata, attributes);
        checkExcludedClasses(configurations, exclusions);
        configurations.removeAll(exclusions);
        configurations = getConfigurationClassFilter().filter(configurations);
        fireAutoConfigurationImportEvents(configurations, exclusions);
        return new AutoConfigurationEntry(configurations, exclusions);
    }


//来看看如何获得所有需要注入的类。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
                getBeanClassLoader());
        return configurations;
    }

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();

    //获得自动类名list
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        //查询缓存
        Map<String, List<String>> result = cache.get(classLoader);
        if (result != null) {
            return result;
        }

        result = new HashMap<>();
        try {
            //注意FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
            //便是将该路径下的元素扫描获得自动注册类全类名
            Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);

            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                UrlResource resource = new UrlResource(url);
                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                for (Map.Entry<?, ?> entry : properties.entrySet()) {
                    String factoryTypeName = ((String) entry.getKey()).trim();
                    String[] factoryImplementationNames =
                            StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                    for (String factoryImplementationName : factoryImplementationNames) {
                        result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                                .add(factoryImplementationName.trim());
                    }
                }
            }

            // Replace all lists with unmodifiable lists containing unique elements
            result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                    .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));

            //放入缓存,加速查询
            cache.put(classLoader, result);
        }
        catch (IOException ex) {
            throw new IllegalArgumentException("Unable to load factories from location [" +
                    FACTORIES_RESOURCE_LOCATION + "]", ex);
        }
        return result;
    }

按需配置

//虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
//按照条件装配规则(@Conditional),最终会按需配置。

//以WebMvcAutoConfiguration为例。注意XXXAutoConfiguration便是一个配置文件的实体类,



//以lite模式启动,则无CGLIB代理,注意依赖问题
@Configuration(proxyBeanMethods = false)
//以servlet模式启动。(springboot开始支持响应式编程)
@ConditionalOnWebApplication(type = Type.SERVLET)

//按需注入
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
//由于采用lite模式启动,同时MVC需要依赖DispatchServlet/Validation等,因此先组测
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
        ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
    @Bean
    //用于支持Rest风格的。在用户配置HiddenHttpMethodFilter.class后便不注入。
    @ConditionalOnMissingBean(HiddenHttpMethodFilter.cla***文件中spring.mvc.hiddenmethod.filter=disable则不可用Rest风格
    @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
    public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter();
    }


    @SuppressWarnings("deprecation")
    @Configuration(proxyBeanMethods = false)
    //导入nableWebMvcConfiguration.class 用于配置MVC的部分项
    @Import(EnableWebMvcConfiguration.class)
    //EnableConfigurationProperties前面已经分析过了是使@ConfigurationProperties配置类生效。
    //此处导入WebMvcProperties/ResourceProperties/WebProperties三个配置
    @EnableConfigurationProperties({ WebMvcProperties.class,
            org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
    @Order
    public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware {}
}

同样的简要的回顾分析一下配置类WebMvcProperties

体验一下自定义配置

//代表与配置文件中spring.mvc开头的绑定。
@ConfigurationProperties(prefix = "spring.mvc")
public class WebMvcProperties {
    public static class Servlet {
        //servlet的匹配路径
        private String path = "/";

        private int loadOnStartup = -1;
    }

    public static class View {
        //视图的前缀与后缀
        private String prefix;

        private String suffix;
    }
}

那我们可以这样进行配置

spring:
  mvc:
    servlet:
      path: hello/
      loadOnStartUp: 1
    view:
      prefix: classpath:/hello
      suffix: .html