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/resources,classpath:/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中。
- 在pom.xml中修改打包方式
- 启动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()则额外返回响应头,状态码等信息。
- param1:请求URL,支持查询串和
- 单元测试POST请求:使用
restTemplate.postForObject()发送POST请求:- param2:请求体对象,不需要时注入null,建议使用
LinkedMultiValueMap()结构。 - 其余参数同
getForObject()。 - return:返回响应数据,若使用
postForEntity()则额外返回响应头,状态码等信息。
- param2:请求体对象,不需要时注入null,建议使用
- 单元测试重定向请求:使用
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中的信息:- 文件中的
key是EnableAutoConfiguration的类全名。 - 文件中的
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以设置屏蔽词为a,b和c。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));
}
}