springboot2 (1) 基础入门

232 阅读7分钟

1. 概念入门

概念: springboot 是由Pivotal团队提供的全新框架,用于简化项目搭建过程,开发过程和部署过程等,springboot项目独立运行,不依赖外部web容器,且对很多主流开发框架的无配置集成,就像maven整合了很多依赖一样,springboot整合了很多框架。

1.1 手动搭建

流程: 新建maven-jar项目 springboot2-manual

  • 配置pom核心父标签 <parent>
    • <version> 用于指定springboot版本,后续依赖根据该版本自动选择适合的版本,无需手配。
    • <relativePath/> 表示允许在maven仓库中可以查找到它。
  • 配置pom依赖:spring-boot-starter-web/spring-boot-starter-test/lombok
  • 配置pom属性:<java.version>:使用JDK1.8。
  • 配置pom插件:spring-boot-maven-plugin:若不打算用maven插件来运行应用则忽略。
  • 开发启动类 c.y.s.App:一个项目仅有一个启动类可生效:
    • @SpringBootApplication:开启自动装配,扫描等基本功能。
  • 开发动作类 c.y.s.start.StartController:必须位于启动类同级或下级,否则失效:
    • @RestController:组装了 @Controller@ResponseBody
  • psm测试:api/start/test
    • 一个tomcat只跑一个项目,访问路径中勿加项目名,直接访问路由即可。

源码: /springboot2-manual/

  • res: pom.xml
 <!--springboot的核心parent-->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.1.RELEASE</version>
        <!--可以再maven仓库中可以查找到该<parent>-->
        <relativePath/>
    </parent>

    <!--配置JDK编码和版本-->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

  • src: c.y.s.App
package com.yap.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author yap
 */
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

  • src: c.y.s.controller.StartController
package com.yap.app.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yap
 */
@RestController
@RequestMapping("api/start")
public class StartController {

    @RequestMapping("test")
    public String test() {
        return "test()...";
    }
}
  • psm: http://127.0.0.1:8080/api/start/test

1.2 自动搭建

流程: springboot项目可在 官网 自动搭建,或使用IDEA进行自动搭建:

  • New Project/Module - Spring Initializr - Next
  • 填写项目横纵坐标,java版本,打包方式,启动类包名等元信息。
  • 选择依赖,如 Spring Web/Lombok/Spring Boot DevTools 等。
  • 目录结构:
    • src/main/java:存放源代码。
    • src/main/resources/static:存放静态文件,如css,js,image等,浏览器可直接访问。
    • src/main/resources/templates:存放页面文件,如jsp,html等。
    • src/main/resources/application.properties:springboot核心配置文件。
    • .gitignore:分布式版本控制系统git的配置文件,其中规定了哪些文件不提交,可删除。
    • mvnw:maven wrapper,用于在 maven-wrapper.properties 文件中记录你要使用的maven版本,当执行maven命令时发现当前maven版本和记录的期望版本不一致时会下载期望版本后再执行,可删除。
    • .mvn:存放 maven-wrapper.properties 和相关jar包的目录,可删除。
    • mvn.cmd:执行 mvnw 命令的cmd入口,可删除。
    • HELP.md:说明书和向导文件,可删除。
  • 开发动作类 c.y.s.start.StartController:必须位于启动类同级或下级,否则失效:
    • @RestController:组装了 @Controller@ResponseBody
  • psm测试:api/start/test
    • 一个tomcat只跑一个项目,访问路径中勿加项目名,直接访问路由即可。

springboot默认会顺序从 /META-INF/resourcesclasspath:/resources/classpath:/static/classpath:/public/ 中寻找资源,有则直接返回,若都没有404。

源码: /springboot2/

  • src: c.y.controller.StartController
package com.yap.springboot2.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yap
 */
@RestController
@RequestMapping("api/start")
public class StartController {

    @RequestMapping("test")
    public String test() {
        return "test()...";
    }
}
  • psm: http://127.0.0.1:8080/api/start/test

1.3 独立部署

概念: 开发环境使用内置tomcat更简单,生产环境将war包部署到独立的tomcat更方便对tomcat做额外优化:

  • 启动类继承 SpringBootServletInitializer 并重写 configure()
    • builder.sources(入口类.class)
  • 将项目打成war包:
    • 在pom.xml中修改打包方式 <packaging>war</packaging>
    • 添加 provided 作用域的依赖 spring-boot-starter-tomcat
    • 项目右键Terminal:mvn clean package -Dmaven.test.skip=true
    • 重命名war包并拷贝到 %TOMCAT_HOME%\webapps 中。
  • 启动tomcat,并测试:http://127.0.0.1:8080/项目名/路由源码:
  • 启动类修改
@SpringBootApplication
public class Springboot2Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Springboot2Application.class);
    }
}*/
  • pom.xml
        <!--spring-boot-starter-tomcat-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>


<!--如果一个不好使可以多加几个如下-->
    <properties>
        <java.version>1.8</java.version>

        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    
          <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <scope>test</scope>
        </dependency>	
        
        
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <version>2.4.3</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
            
<configuration>        
<mainClass>com.yap.z11springboot2.Z11Springboot2Application</mainClass>
</configuration>

2. RestTemplate

概念: RestTemplate 是spring3开始支持的,用来模拟客户端向服务端发送请求的工具,继承自 InterceptingHttpAccessor 并实现 定义了常见的如GET,POST等请求操作 RestOperations 接口:

  • 开发动作类 c.y.s.restTemplate.RestTemplateController
    • @GetMapping 相当于 @RequestMapping(method = RequestMethod.GET)
    • @PostMapping 相当于 @RequestMapping(method = RequestMethod.POST)
  • 开发测试类:c.y.s.RestTemplateTest
    • 使用 new RestTemplate() 实例化 RestTemplate 属性。
  • 单元测试GET请求:使用 restTemplate.getForObject() 发送GET请求:
    • param1:请求URL,支持查询串和 {} 占位符两种方式传递请求参数。
    • param2:响应数据类型,对应动作方法返回值。
    • param3:可选,当URL使用占位符方式时可使用数组或Map结构进行赋值。
    • return:返回响应数据,若使用 getForEntity() 则额外返回响应头,状态码等信息。
  • 单元测试POST请求:使用 restTemplate.postForObject() 发送POST请求:
    • param2:请求体对象,不需要时注入null,建议使用 LinkedMultiValueMap() 结构。
    • 其余参数同 getForObject()
    • return:返回响应数据,若使用 postForEntity() 则额外返回响应头,状态码等信息。
  • 单元测试重定向请求:使用 restTemplate.postForLocation() 发送POST重定向请求:
    • 参数同 postForObject(),返回值是一个URI对象,没有 getForLocation()

源码: /springboot2/

  • res: classpath:templates/success.html
<h1>success!!!</h1>
  • src: c.y.s.controller.RestTemplateController
package com.yap.springboot2.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author yap
 */
@Controller
@RequestMapping("api/rest-template")
public class RestTemplateController {

    @ResponseBody
    @GetMapping("get-mapping")
    public String getMapping(String name) {
        System.out.println("getMapping()..." + name);
        return "data: " + name;
    }

    @ResponseBody
    @PostMapping("post-mapping")
    public String postMapping(String name) {
        System.out.println("postMapping()..." + name);
        return "data: " + name;
    }

    @RequestMapping("redirect")
    public String redirect(String name) {
        System.out.println("redirect()..." + name);
        // `/` 不能省略
        return "redirect:/success.html";
    }

}

  • tst: c.y.s.RestTemplateTest
package com.yap.springboot2;

import org.junit.jupiter.api.Test;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

/**
 * @author yap
 */
class RestTemplateTest {

    private RestTemplate restTemplate = new RestTemplate();

    @Test
    void getForObject() {
        String baseUrl = "http://127.0.0.1:8080/api/rest-template/get-mapping";

        // with no param...
        System.out.println(restTemplate.getForObject(baseUrl, String.class));

        // with k-v param...
        System.out.println(restTemplate.getForObject(baseUrl + "?name=yap", String.class));

        // with array param...
        System.out.println(restTemplate.getForObject(baseUrl + "?name={name}", String.class, "yap"));

        // with map param...
        Map<String, Object> params = new HashMap<>(1);
        params.put("name", "yap");
        System.out.println(restTemplate.getForObject(baseUrl + "?name={name}", String.class, params));
    }

    @Test
    void getForEntity() {
        String baseUrl = "http://127.0.0.1:8080/api/rest-template/get-mapping";

        // with no param...
        ResponseEntity<String> response = restTemplate.getForEntity(baseUrl, String.class);
        System.out.println("body: " + response.getBody());
        System.out.println("statusCode: " + response.getStatusCode());
        System.out.println("statusCodeValue: " + response.getStatusCodeValue());
        System.out.println("headers: " + response.getHeaders());

        // with k-v param...
        System.out.println(restTemplate.getForEntity(baseUrl + "?name=yap", String.class));

        // with array param...
        System.out.println(restTemplate.getForEntity(baseUrl + "?name={name}", String.class, "yap"));

        // with map param...
        Map<String, Object> params = new HashMap<>(1);
        params.put("name", "yap");
        System.out.println(restTemplate.getForEntity(baseUrl + "?name={name}", String.class, params));
    }

    @Test
    void postForObject() {
        String baseUrl = "http://127.0.0.1:8080/api/rest-template/post-mapping";

        // with no param...
        System.out.println(restTemplate.postForObject(baseUrl, null, String.class));

        // with k-v param...
        System.out.println(restTemplate.postForObject(baseUrl + "?name=yap", null, String.class));

        // with array param...
        System.out.println(restTemplate.postForObject(baseUrl + "?name={name}", null, String.class, "yap"));

        // with map param...
        Map<String, Object> params = new HashMap<>(1);
        params.put("name", "yap");
        System.out.println(restTemplate.postForObject(baseUrl + "?name={name}", null, String.class, params));

        // with request-body param...
        MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add("name", "yap");
        System.out.println(restTemplate.postForObject(baseUrl, requestBody, String.class));
    }

    @Test
    void postForEntity() {
        String baseUrl = "http://127.0.0.1:8080/api/rest-template/post-mapping";

        // with no param...
        ResponseEntity<String> response = restTemplate.postForEntity(baseUrl, null, String.class);
        System.out.println("body: " + response.getBody());
        System.out.println("statusCode: " + response.getStatusCode());
        System.out.println("statusCodeValue: " + response.getStatusCodeValue());
        System.out.println("headers: " + response.getHeaders());

        // with k-v param...
        System.out.println(restTemplate.postForEntity(baseUrl + "?name=yap", null, String.class));

        // with array param...
        System.out.println(restTemplate.postForEntity(baseUrl + "?name={name}", null, String.class, "yap"));

        // with map param...
        Map<String, Object> params = new HashMap<>(1);
        params.put("name", "yap");
        System.out.println(restTemplate.postForEntity(baseUrl + "?name={name}", null, String.class, params));

        // with request-body param...
        MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<>();
        requestBody.add("name", "yap");
        System.out.println(restTemplate.postForEntity(baseUrl, requestBody, String.class));
    }

    @Test
    void redirect() {
        String baseUrl = "http://127.0.0.1:8080/api/rest-template/redirect?name=yap";
        System.out.println(restTemplate.postForLocation(baseUrl,null));
    }
}

3. 启动类注解

概念: @SpringBootApplication 是一个组合注解,其中包括:

  • @SpringBootConfiguration:标记该类为配置类,相当于一个XML配置文件。
  • @ComponentScan:用于扫描所在包及子包的 @Component,可用 value 属性修改扫描范围。
  • @EnableAutoConfiguration:用于自动装配,即自动将某些类交给spring管理:
    • @AutoConfigurationPackage:负责自动扫描包中的类。
    • @Import:负责自动导入:
      • Configruation 结尾并标注了 @Configruation 注解的配置类。
      • Selector 结尾并实现了 ImportSelector 接口的选择器类,该类会根据条件选择哪些类被导入。
      • Registrar 结尾并实现了 ImportBeanDefinitionRegistrar 接口的注册器类,该类可以在代码运行时动态注册指定类的实例。
    • springboot支持的自动装配类都在 spring-boot-autoconfigure-xxx.jar 下,且常以 AutoConfiguration 为后缀。
    • exclude 属性可以排除某些项目的自动装配以节省资源,启动类注解继承了该属性,即可以在启动类中设置排除项,如排除Solr自动装配:@SpringBootApplication(exclude = SolrAutoConfiguration.class)。 `。

4. 自定义starter

概念: 每一个starter都是springboot的一个组件,而 @EnableAutoConfiguration 负责自动装配它们:

  • 新建springboot-jar项目 shield-spring-boot-starter,该starter功能为屏蔽敏感词:
    • 官方格式:spring-boot-starter-功能名
    • 个人格式:功能名-spring-boot-starter
    • 添加依赖: spring-boot-configuration-processor/lombok
  • 开发功能类 c.y.s.ShieldOperations:建议以 Operations 为后缀:
    • 属性 String shieldWords 用于接收屏蔽词,逗号分隔,如 a,b,c
    • 方法 String shield(String word) 用于将单词中出现的屏蔽词替换成 *
  • 开发测试类 c.y.s.ShieldOperationsTest 注入 ShieldOperations 属性并测试 shield()
  • 开发配置类 c.y.s.ShieldProperties:建议以 Properties 为后缀:
    • 标记 @ConfigurationProperties("yap.shield"):设置属性前缀。
    • 属性 String shieldWords 与前缀组成 yap.shield.shieldWords 用于配置屏蔽词内容。
    • 属性 Boolean enabled 与前缀组成 yap.shield.enabled 用于配置是否开启屏蔽词功能。
  • 开发自动装配类 c.y.s.ShieldAutoConfiguration:仿照 RedisAutoConfiguration
    • @Configuration:标识该类为配置类。
    • @ConditionalOnClass(ShieldOperations.class):当拥有指定功能类时生效。
    • @EnableConfigurationProperties(ShieldProperties.class):启用指定配置类。
    • @Autowired:建议使用构造器方式注入 shieldProperties 实例。
  • 开发自动装配方法:仿照 RedisAutoConfiguration
    • @Bean:对应功能类的 <bean>,方法名对应 id,返回值对应 class
    • @ConditionalOnMissingBean:仅在当前容器中不存在此 <bean> 时才实例化该 <bean>
      • 若指定了 name 表示spring容器会通过byName的方式实例化该 <bean>
      • 若没指定 name 表示spring容器会通过byClass的方式实例化该 <bean>
    • @ConditionalOnProperty:仅在某个指定属性满足某些指定条件时才实例化该 <bean>
      • name="yap.shield.enabled:设置指定的属性。
      • havingValue="true":指定属性的值为 true 时满足条件。
      • matchIfMissing=true:指定属性缺省时视为满足条件。
    • 在方法中读取配置类中的屏蔽词,并注入到功能类的 shieldWords 属性中。
  • 开发 classpath:META-INF/spring.factories 文件:
    • 启动类注解自动导入了 AutoConfigurationImportSelector.class
    • 跟踪 getCandidateConfigurations() - loadFactoryNames() - loadSpringFactories()
    • 发现该选择器会读取每个starter组件中的 classpath:META-INF/spring.factories 中的信息:
      • 文件中的 keyEnableAutoConfiguration 的类全名。
      • 文件中的 value 是自定义自动装配类的类全名。
  • 将springboot项目install到maven仓库中:在 spring-boot-maven-plugin 插件的 <configuration> 中添加 <skip>true</skip> 以跳过jar包中多余的 BOOT-INF 目录。
  • 使用starter组件:新建项目 springboot2
  • 新项目配置pom依赖:shield-spring-boot-starter
  • 新项目主配:
    • yap.shield.shieldWords=a,b,c 以设置屏蔽词为 abc
    • yap.shield.enabled=true 以开启屏蔽词功能。
  • 新项目开发测试类 c.j.s.shield.ShieldTest:注入 ShieldOperations 属性并测试 shield()

源码: /shield-spring-boot-starter/

  • res: pom.xml
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<configuration>
		<skip>true</skip>
	</configuration>
</plugin>
  • res: classpath:META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.yap.shieldspringbootstarter.ShieldAutoConfiguration
  • src: c.y.s.ShieldOperations
package com.yap.shieldspringbootstarter;

import lombok.Data;

/**
 * @author yap
 */
@Data
public class ShieldOperations {

    /**
     * a,b,c
     **/
    private String shieldWords;

    /**
     * "a blue apple" => "* *lue *pple"
     */
    public String shield(String word) {
        if (shieldWords != null) {
            for (String e : shieldWords.split(",")) {
                word = word.replace(e, "*");
            }
        }
        return word;
    }
}
  • src: c.y.s.ShieldProperties
package com.yap.shieldspringbootstarter;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author yap
 */
@Data
@ConfigurationProperties("yap.shield")
public class ShieldProperties {

    /**
     * yap.shield.shieldWords="a,b,c"
     */
    private String shieldWords;

    /**
     * yap.shield.enable=true
     * enable shield if true
     */
    private Boolean enabled;
}
  • src: c.y.s.ShieldAutoConfiguration
package com.yap.shieldspringbootstarter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author yap
 */
@Configuration
@ConditionalOnClass(ShieldOperations.class)
@EnableConfigurationProperties(ShieldProperties.class)
public class ShieldAutoConfiguration {

    private ShieldProperties shieldProperties;

    @Autowired
    public ShieldAutoConfiguration(ShieldProperties shieldProperties) {
        this.shieldProperties = shieldProperties;
    }

    /***
     * yap.shield.enabled=true时:返回设置了屏蔽词的ShieldOperations实例
     * 必须写在 shieldOperations() 之上,否则因重复实例化相同 class 值而报错。
     * @return ShieldOperations实例
     */
    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(
            name = "yap.shield.enabled",
            havingValue = "true",
            matchIfMissing = true)
    public ShieldOperations shieldOperationsWithEnabled() {
        ShieldOperations shieldOperations = new ShieldOperations();
        shieldOperations.setShieldWords(shieldProperties.getShieldWords());
        return shieldOperations;
    }

    /***
     * yap.shield.enabled=false时:返回空的ShieldOperations实例
     * @return ShieldOperations实例
     */
    @Bean
    @ConditionalOnMissingBean
    public ShieldOperations shieldOperations() {
        return new ShieldOperations();
    }

}
  • tst: c.y.s.ShieldOperationsTest
package com.yap.shieldspringbootstarter;

import org.junit.jupiter.api.Test;

/**
 * @author yap
 */
class ShieldOperationsTest {

    @Test
    void shield() {
        ShieldOperations shieldOperations = new ShieldOperations();
        String words = "a blue apple!";
        shieldOperations.setShieldWords("a,b,c");
        System.out.println(shieldOperations.shield(words));
    }
}

引入依赖测试starter

源码: /springboot2/

  • res: pom.xml
        <!--shield-spring-boot-starter-->
 <dependency>
     <groupId>com.yap</groupId>
     <artifactId>shield-spring-boot-starter</artifactId>
     <version>0.0.1-SNAPSHOT</version>
 </dependency>
  • src: classpath:application.properties
# 屏蔽词
# 等效于yap.shield.shield-words
yap.shield.shieldWords=a,b,c

# 仅在true或缺省时屏蔽词功能均生效
yap.shield.enabled=true
  • src: c.y.s.ShieldController
package com.yap.springboot2.controller;

import com.yap.shieldspringbootstarter.ShieldOperations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author yap
 */
@RequestMapping("api/shield")
@RestController
public class ShieldController {

    private ShieldOperations shieldOperations;

    @Autowired
    public ShieldController(ShieldOperations shieldOperations) {
        this.shieldOperations = shieldOperations;
    }

    @RequestMapping("test")
    public String test(String word) {
        return shieldOperations.shield(word);
    }
}
  • tst: c.y.s.ShieldTest
package com.yap.springboot2;

import org.junit.jupiter.api.Test;
import org.springframework.web.client.RestTemplate;

/**
 * @author yap
 */
class ShieldTest {

    @Test
    void shield() {
        String baseUrl = "http://127.0.0.1:8080/api/shield/test?word=a-blue-apple";
        System.out.println(new RestTemplate().getForObject(baseUrl, String.class));
    }
}