SpringBoot框架使用

    技术2022-07-13  76

    —— 本文转自onestar : SpringBoot框架原理分析

    一、起步依赖原理分析

    在搭建SpringBoot环境的时候,在pom.xml中添加了两个依赖,对这两个依赖进行分析,分别是: SpringBoot的起步依赖:spring-boot-starter-parent web的起步依赖:spring-boot-starter-web

    1、spring-boot-starter-parent spring-boot-starter-parent 是Spring Boot的父级依赖,是一个特殊的starter,它用来提供相关的Maven默认依赖。使用它之后,常用的包依赖可以省去version标签。

    咱们可以直接对源码进行分析,来看看源码,就拿上篇博文中搭建 SpringBoot 环境的代码,使用 idea 按住 Ctrl 点击 pom.xml 中的spring-boot-starter-parent

    <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.0.1.RELEASE</version> <relativePath>../../spring-boot-dependencies</relativePath> </parent>

    截取了部分源码,在这里,可以看到SpringBoot的继承关系:

    SpringBoot 继承 spring-boot-starter-parent spring-boot-starter-parent 继承 spring-boot-dependencies

    【1】咱们可以进入 spring-boot-dependencies 看一看,按住 Ctrl 点击 spring-boot-dependencies,截取部分代码:

    <properties> <activemq.version>5.15.3</activemq.version> <antlr2.version>2.7.7</antlr2.version> <appengine-sdk.version>1.9.63</appengine-sdk.version> <artemis.version>2.4.0</artemis.version> <aspectj.version>1.8.13</aspectj.version> <assertj.version>3.9.1</assertj.version> <atomikos.version>4.0.6</atomikos.version> <bitronix.version>2.1.4</bitronix.version> <build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version> <byte-buddy.version>1.7.11</byte-buddy.version> ... ... ... </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> <version>2.0.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> <version>2.0.1.RELEASE</version> </dependency> ... ... ... </dependencies> </dependencyManagement> <build> <pluginManagement> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> </plugin> <plugin> <groupId>org.jooq</groupId> <artifactId>jooq-codegen-maven</artifactId> <version>${jooq.version}</version> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.0.1.RELEASE</version> </plugin> ... ... ... </plugins> </pluginManagement> </build>

    这些配置里面主要是定义一些坐标的版本、依赖管理、插件管理,这里会根据我们在spring-boot-starter-parent定义的版本来提供相应版本的匹配,这就很好的解决了Spring导入版本依赖冲突的问题,所以我们的 SpringBoot 工程继承 spring-boot-starter-parent 后已经具备版本锁定等配置了。

    可以看出起步依赖的作用就是进行依赖的传递。

    【2】在 spring-boot-starter-parent 中,还有一个地方咱们可以看一下,那就是资源引入:

    <resource> <filtering>true</filtering> <directory>${basedir}/src/main/resources</directory> <includes> <include>**/application*.yml</include> <include>**/application*.yaml</include> <include>**/application*.properties</include> </includes> </resource>

    可以看到,${basedir}/src/main/resources 表示资源的加载文件,资源文件包括下面三种格式的,也就是说,咱们在配置SpringBoot资源文件的时候都是以 application*.yml、application*.yaml、application*.properties文件格式

    2、spring-boot-starter-web spring-boot-starter-web 是web功能的起步依赖,导入了web功能的起步依赖后,可以不用导入Spring和SpringMVC的坐标,是因为starter-web 将坐标打包了,同样,可以来看看源码,按住 Ctrl 点击 spring-boot-starter-web,截取部分代码

    <?xml version="1.0" encoding="UTF-8"?> <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starters</artifactId> <version>2.0.1.RELEASE</version> </parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.0.1.RELEASE</version> <name>Spring Boot Web Starter</name> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.1.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</artifactId> <version>2.0.1.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>2.0.1.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.9.Final</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>5.0.5.RELEASE</version> <scope>compile</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version> <scope>compile</scope> </dependency> </dependencies> </project>

    可以看到,spring-boot-starter-web就是将web开发要使用的spring-web、spring-webmvc等坐标进行了“打包”,这样我们的工程只要引入spring-boot-starter-web起步依赖的坐标就可以进行web开发了,同样体现了依赖传递的作用。

    二、自动配置原理解析

    自动配置其实就是将默认的配置自动加载进去,不需要我们去手动进行配置,依然是对源码进行分析,从mySpringBootApplication引导类开始:

    @SpringBootApplication public class mySpringBootApplication { public static void main(String[] args) { //将字节码引导参数传递给run方法 SpringApplication.run(mySpringBootApplication.class); } }

    1、@SpringBootApplication注解

    首先是@SpringBootApplication注解,咱们按住 Ctrl 点击 SpringBootApplication,进入 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 { @AliasFor( annotation = EnableAutoConfiguration.class ) Class<?>[] exclude() default {}; @AliasFor( annotation = EnableAutoConfiguration.class ) String[] excludeName() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackages" ) String[] scanBasePackages() default {}; @AliasFor( annotation = ComponentScan.class, attribute = "basePackageClasses" ) Class<?>[] scanBasePackageClasses() default {}; }

    在这里我们可以看到,有一些其他的注解,咱们挑一些重要的注解来进行分析:

    【1】@SpringBootConfiguration 注解 咱们可以按住 Ctrl 点进去看

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

    可以看到 @SpringBootConfiguration 注解上面有一个@Configuration 注解,既标注该类是 Spring 的一个配置类 ,其实,@SpringBootConfiguration 注解就相当于Configuration注解,用于标注该类是 Spring 的一个配置类,和 Spring 中的@Configuration 注解是一个意思

    【2】@EnableAutoConfiguration 注解 @EnableAutoConfiguration 注解是 SpringBoot自动配置功能开启 ,同样,咱们可以按住 Ctrl 点进去瞧瞧

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

    可以看到,在上面有一个 @Import({AutoConfigurationImportSelector.class}) 注解配置,这是导入了AutoConfigurationImportSelector类,意思是自动配置导入选择器,咱们可以点进AutoConfigurationImportSelector类看看,截取部分源码:

    图中框出来的表示加载某些配置,点进源码看一看:

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

    其中,SpringFactoriesLoader.loadFactoryNames 方法的作用就是从META-INF/spring.factories文件中读取指定类对应的类名称列表,而这个文件就在当前类的包下面,咱们可以找到看一看:

    点进spring.factories文件,里面有关自动配置的配置信息:

    ... ... ... org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\ org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\ ... ... ...

    上面配置文件存在大量的以 Configuration 为结尾的类名称,这些类就是存有自动配置信息的类,而 SpringApplication 在获取这些类名后再加载 ,我们以ServletWebServerFactoryAutoConfiguration为例来分析源码,找到 ServletWebServerFactoryAutoConfiguration 点进去看看:

    @Configuration @AutoConfigureOrder(-2147483648) @ConditionalOnClass({ServletRequest.class}) @ConditionalOnWebApplication( type = Type.SERVLET ) @EnableConfigurationProperties({ServerProperties.class}) @Import({ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, EmbeddedTomcat.class, EmbeddedJetty.class, EmbeddedUndertow.class}) public class ServletWebServerFactoryAutoConfiguration {...}

    在这里,@EnableConfigurationProperties(ServerProperties.class) 代表加载 ServerProperties 服务器配置属性类,咱们可以进入ServerProperties.class 看源码(截取部分源码):

    @ConfigurationProperties( prefix = "server", ignoreUnknownFields = true ) public class ServerProperties { private Integer port; private InetAddress address; ... }

    在这里,prefix = “server” 表示SpringBoot配置文件中的前缀,SpringBoot会将配置文件中以server开始的属性映射到该类的字段中。可以通过这个类完成相应配置加载,而加载的内容在包下面的 spring-configuration-metadata.json 文件中

    点进去可以看到很多默认的配置,咱们以 server.port 配置为例:

    { "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties", "defaultValue": 8080, "name": "server.port", "description": "Server HTTP port.", "type": "java.lang.Integer" }

    这个配置表示 Tomcat 启动时的默认端口,在这里配好之后,通过 ServerProperties 进行加载,然后通过@EnableConfigurationProperties标签引入,最终在 getCandidateConfigurations 类中进行加载。这就是整个默认配置的过程,当然,我们可以通过修改我们自己的 application*.properties 配置文件来覆盖默认配置

    【3】@ComponentScan 注解 @ComponentScan 注解是用于组件扫描,默认扫描当前引导类所在的包下的索引

    三、整合Mybatis 中的 @Repository 与 @Mapper

    1 - @Mapper @Mapper 是 Mybatis 的注解,和 Spring 没有关系,@Repository 是 Spring 的注解,用于声明一个 Bean。 使用 Mybatis 有 XML 文件或者注解的两种使用方式,如果是使用 XML 文件的方式,我们需要在配置文件中指定 XML 的位置,这里只研究注解开发的方式。

    在 Spring 程序中,Mybatis 需要找到对应的 mapper,在编译的时候动态生成代理类,实现数据库查询功能,所以我们需要在接口上添加 @Mapper 注解。

    @Mapper public interface UserDao { ... }

    但是,仅仅使用@Mapper注解,我们会发现,在其他变量中依赖注入,IDEA 会提示错误,但是不影响运行(亲测~)。因为我们没有显式标注这是一个 Bean,IDEA 认为运行的时候会找不到实例注入,所以提示我们错误。如下图,会有红色波浪线。 在这里插入图片描述 尽管这个错误提示并不影响运行,但是看起来很不舒服,所以我们可以在对应的接口上添加 bean 的声明,如下:

    @Repository // 也可以使用@Component,效果都是一样的,只是为了声明为bean @Mapper public interface UserDao { @Insert("insert into user(account, password, user_name) " + "values(#{user.account}, #{user.password}, #{user.name})") int insertUser(@Param("user") User user) throws RuntimeException; }

    2 - @Repository 正如上面说的,@Repository 用于声明 dao 层的 bean,如果我们要真正地使用 @Repository 来进行开发,那是基于代码的开发,简单来说就是手写 JDBC。

    和 @Service、@Controller 一样,我们将 @Repository 添加到对应的实现类上,如下:

    @Repository public class UserDaoImpl implements UserDao{ @Override public int insertUser(){ JdbcTemplate template = new JdbcTemplate(); ... } }

    3 - 其他扫描手段 基于注解的开发也有其他手段帮助 Mybatis 找到 mapper,那就是 @MapperScan 注解,可以在启动类上添加该注解,自动扫描包路径下的所有接口。

    @SpringBootApplication @MapperScan("com.scut.thunderlearn.dao") public class UserEurekaClientApplication { public static void main(String[] args) { SpringApplication.run(UserEurekaClientApplication.class, args); } }

    使用这种方法,接口上不用添加任何注解。

    4 - 总结 @Mapper 一定要有,否则 Mybatis 找不到 mapper。 @Repository 可有可无,可以消去依赖注入的报错信息。 @MapperScan 可以替代 @Mapper。

    四、SpringBoot工程热部署

    我们在开发中反复修改类、页面等资源,每次修改后都是需要重新启动才生效,这样每次启动都很麻烦,浪费了大量的时间,我们可以在修改代码后不重启就能生效,在 pom.xml 中添加如下配置就可以实现这样的功能,我们称之为热部署。热部署分为两个步骤:

    对 idea 进行自动编译设置 在 pom.xml 进行配置 1、配置 idea 【1】文件(file)—>设置(setting)—>编辑器(Compiler),勾选如图,点击应用,确定

    【2】按住快捷键:Shift+Ctrl+Alt+/,选择注册(Registry)

    2、配置 pom.xml

    <!--热部署配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency>

    配置好后只需要启动一次,修改代码后不需要再次启动即可运行

    Processed: 0.025, SQL: 9