1.编写一个HelloWorld
1.1 版本问题
SpringBoot2要求jdk至少要是1.8或者以上版本,maven至少3.3以上版本。
1.2 新建一个maven项目
1.3 配置maven的settings.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>E:\self-study\mvn_repository</localRepository>
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
</settings>
1.4 pom.xml文件中引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.gongsl</groupId>
<artifactId>SpringBootProject</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<!--spring-boot-maven-plugin可以为我们创建一个可执行的jar-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1.5 创建启动类
package com.gongsl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
SpringApplication.run(MainApplication.class,args);
}
}
启动类中的run方法是有返回值的,返回值就是IOC容器。
1.6 编写业务代码
package com.gongsl.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: gongsl
* @Date: 2021-01-08 18:19
*/
@RestController
public class HelloController {
@RequestMapping("/hello")
public String home(){
return "Hello SpringBoot2!";
}
}
1.7 运行测试
执行MainApplication
类的main
方法,然后浏览器地址栏输入 http://localhost:8080/hello 进行访问即可。
1.8 修改端口启动
在maven项目的src\main\resources
目录下新增application.properties
配置文件,然后在文件中新增以下内容:
server.port=8081
这时端口就已经被修改成8081了,然后重新运行启动类的main
方法,最后在浏览器地址栏输入 http://localhost:8081/hello 进行访问即可。
1.9 通过cmd窗口启动项目
如果我们在pom.xml文件中加了spring-boot-maven-plugin
这个依赖的话,那么使用maven打成的包就是一个可执行的jar包。我们可以在cmd窗口中进行演示,这里需要用到java -jar
命令,如下所示:
1.10 注意事项
- 如果浏览器地址栏访问url出现问题,首先检查是不是
MainApplication
启动类的位置有问题。该类一定要放在最外侧,要保证能够扫描到所有的controller,因为SpringBoot会自动加载启动类所在包下以及其子包下的所有组件; - 如果我们不想把启动类放在最外侧,或者想要自定义被扫描的包及其下的controller类的话,也是可以实现的,只需要在启动类的
@SpringBootApplication
注解中增加scanBasePackages
参数即可,比如扫描com.gongsl
包下的所有内容,就配置为@SpringBootApplication(scanBasePackages = "com.gongsl")
即可;
如果启动类的包路径是“com.gongsl”的话,那么
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.gongsl")
- 上面
HelloController
类上用的是@RestController
注解,这个注解就是@Controller
和@ResponseBody
的组合注解; - 如果想知道
application.properties
配置文件中可以写哪些东西,那么我们可以参考官方文档。
2.SpringBoot简介
2.1 为什么使用SpringBoot
- SpringBoot能快速创建出生产级别的Spring应用。
2.2 SpringBoot的优点
- 可以创建独立的Spring应用;
- 内嵌了web服务器;
- 自动starter依赖,简化了构建配置;
- 自动配置Spring以及第三方功能;
- 提供生产级别的监控、健康检查及外部化配置;
- 无代码生成、无需编写XML文件等。
SpringBoot是整合Spring技术栈的一站式框架,也是简化Spring技术栈的快速开发脚手架。
2.3 SpringBoot的缺点
- 人称版本帝,迭代快,需要时刻关注变化;
- 封装太深,内部原理复杂,不容易精通。
2.4 SpringBoot的时代背景
2.4.1 微服务
- 微服务是一种架构风格;
- 微服务是一个应用拆分为一组小型服务;
- 每个服务运行在自己的进程内,也就是可独立部署和升级;
- 服务之间使用轻量级HTTP进行交互;
- 服务围绕业务功能拆分;
- 服务可以由全自动部署机制独立部署;
- 去中心化,服务自治。服务可以使用不同的语言、不同的存储技术。
2.4.2 分布式
2.4.2.1 分布式的困难
-
远程调用
-
服务发现
-
负载均衡
-
服务容错
-
配置管理
-
服务监控
-
链路追踪
-
日志管理
-
任务调度
-
…
2.4.2.2 分布式的解决方案
- SpringBoot + SpringCloud
2.4.3 云原生
2.4.3.1 上云的困难
-
服务自愈
-
弹性伸缩
-
服务隔离
-
自动化部署
-
灰度发布
-
流量治理
-
…
2.4.3.2 上云的解决方案
- Cloud Native
2.5 SpringBoot的特点
2.5.1 依赖管理
2.5.1.1 父项目做依赖管理
<!-- 通过spring-boot-starter-parent这个父项目做依赖管理 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.7.RELEASE</version>
</parent>
<!-- spring-boot-starter-parent这个父项目中还有下面这个父项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.7.RELEASE</version>
</parent>
spring-boot-dependencies
这个父项目中几乎声明了所有开发中常用的依赖的版本号,这个就是自动版本仲裁机制。
2.5.1.2 导入starter作为场景启动器
- 我们以后在pom.xml文件中会见到很多
spring-boot-starter-*
这种配置,这个就是场景启动器,后面的*号就代表了某种场景; - 只要引入了某个场景的starter,这个场景的所有常规需要的依赖都会被自动引入;
- 如果我们想知道SpringBoot支持哪些场景的starter,可以到官方文档中进行查阅;
- 像
*-spring-boot-starter
这种starter一般都是第三方为我们提供的简化开发的场景启动器; - 所有场景启动器最底层的依赖就是下面这个。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.3.7.RELEASE</version>
<scope>compile</scope>
</dependency>
2.5.1.3 自动版本仲裁
- 自动版本仲裁功能可以让我们无需关注版本号,因为在
spring-boot-dependencies
这个父项目中,所有涉及的依赖的版本号都已经配置在了properties
标签下了,所以我们引入依赖时默认可以不写版本号; - 不过引入非版本仲裁的jar包时,要写版本号。
2.5.1.4 能够修改默认版本号
我们也可以修改默认版本号。首先要在spring-boot-dependencies
这个父项目中查看需要修改版本号的依赖的标签名是什么,比如mysql版本号对应的标签名就是mysql.version
,假设我们想修改mysql的版本号为5.1.43
,那就直接在项目的pom.xml文件中加入如下内容即可。
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
2.5.2 自动配置
假设就以spring-boot-starter-web
这个依赖为例。
- 只要引入了上面那个web依赖,SpringBoot就会为我们自动引入tomcat依赖,自动给我们配置好tomcat;
- 同样会引入SpringMVC的全套组件,并自动配好SpringMVC的常用功能;
- 还会自动配好Web常见功能,比如字符编码问题等,并帮我们配置好了所有web开发的常见场景;
- SpringBoot也会为我们配置一个默认的包扫描路径,不用再手动进行包扫描的配置;
- SpringBoot为我们提供的各种配置都会赋予一个默认值
- 默认配置最终都是映射到某个类上,如:MultipartProperties;
- 配置文件的值最终会绑定某个类上,这个类会在容器中创建对象。
- SpringBoot是按需加载所有自动配置项,我们引入了哪些场景,这个场景的自动配置才会开启;
- SpringBoot所有的自动配置功能都在
spring-boot-autoconfigure
包里面,比如spring-boot-autoconfigure-2.3.7.RELEASE.jar
包。
3.常用注解的用法
3.1 @Bean注解和@Configuration注解
3.1.1 前置准备
3.1.1.1 引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
这里引入lombok依赖时不用加版本号,因为SpringBoot的自动版本仲裁功能已经为我们设置好了对应的版本号,我们可以在spring-boot-dependencies
这个父项目中根据lombok.version
标签查看到对应的版本号是什么。
3.1.1.2 增加两个实体类
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:27
*/
@Data
@ToString
@AllArgsConstructor
public class User {
private String name;
private Integer age;
private String gender;
}
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:33
*/
@Data
@ToString
@AllArgsConstructor
public class Pet {
private String name;
}
3.1.1.3 新增配置类
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration
public class MyConfig {
@Bean
public User userTest(){
return new User("张三",18,"男");
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}
}
3.1.2 @Bean、@Configuration的用法
- 之前Spring想要将某个组件放到IOC容器中,一般会在一个xml配置文件中进行配置,而SpringBoot不再使用配置文件,而是使用配置类,我们可以新建一个类,比如
MyConfig
类,然后只要我们在这个类上加上@Configuration
注解,那这个类就是配置类,这个类等同于之前我们使用的配置文件; - 以前在配置文件中,我们可以使用
bean
标签来给容器添加组件,现在我们在配置类中的方法上使用@Bean
注解可以完成同样的效果; - 以上面的
userTest
方法为例,该方法使用了@Bean
注解,就表示向容器中添加了一个组件,方法名就是组件id,方法的返回类型就是组件的类型,方法的返回值就是组件在容器中的实例; - 如果我们不希望方法名作为容器中组件的id的话,也可以自定义,比如把上面配置类中
userTest
方法的@Bean
注解改成@Bean("user")
,就把组件id改为"user"了,我们可以在启动类中进行验证,如下所示:
配置类:
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration
public class MyConfig {
@Bean("user")
public User userTest(){
return new User("张三",18,"男");
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}
}
启动类:
package com.gongsl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//获取所有组件id的名称
String[] beanNames = run.getBeanDefinitionNames();
for (String name : beanNames) {
if("user".equals(name) || "petCat".equals(name)){
System.out.print(name+" ");
}
}
}
}
//运行结果:user petCat
- 配置类中使用
@Bean
注解在方法上给容器注入的组件,默认是单实例的。由于是单实例,所以容器中同一个实例无论怎么获取,获取多少次,都是一样的,验证如下:
package com.gongsl;
import com.gongsl.bean.Pet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
Pet pet1 = run.getBean(Pet.class);
Pet pet2 = run.getBean(Pet.class);
System.out.println(pet1==pet2);//运行结果:true
//通过组件id更精确地获取
Pet petCat1 = run.getBean("petCat", Pet.class);
Pet petCat2 = run.getBean("petCat", Pet.class);
System.out.println(petCat1==petCat2);//运行结果:true
}
}
- 配置类本身也是容器中的一个组件。如果不是的话,当我们从容器中获取的时候是会报错的,验证如下:
package com.gongsl;
import com.gongsl.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
}
}
//运行结果:com.gongsl.config.MyConfig$$EnhancerBySpringCGLIB$$33e871dd@55f45b92
3.1.3 proxyBeanMethods属性的用法
- 和SpringBoot1相比,SpringBoot2中的
@Configuration
注解新增了一个proxyBeanMethods
属性(这个属性是Spring5.2版本以后才有的,SpringBoot2中Spring的版本就是5.2),该属性的默认值是true; - 外部无论对配置类中已经使用
@Bean
注解注入到容器中的组件方法调用多少次,获取到的都是已经注入到容器中的单实例对象,之所以出现这种情况,就是因为配置类的@Configuration
注解中proxyBeanMethods
属性的默认值是true的原因; - 当配置类的
@Configuration
注解中proxyBeanMethods
属性为true时,如果我们直接从容器中获取配置类,可以发现,获取到的结果是com.gongsl.config.MyConfig$$EnhancerBySpringCGLIB$$33e871dd@55f45b92
。从结果中的EnhancerBySpringCGLIB
可以发现,MyConfig
类并不是一个普通的配置类,而是一个被SpringCGLIB增强了的代理对象; - 当
proxyBeanMethods
属性为true时,如果我们调用配置类中使用过@Bean
注解的方法,比如petCat()
方法,那么SpringBoot总会检查该方法是否已经被注入到容器中,如果发现容器中有该方法返回的组件,那么就直接从容器中获取,这时候无论我们调用多少次petCat()
方法,返回的Pet对象都是同一个,即之前放入容器中的那一个; - 如果
proxyBeanMethods
属性为false,那么当我们调用petCat()
方法时,SpringBoot就不会再检查该方法是否已经被注入到容器中,自然也就不会再从容器中获取组件,而是直接调用该方法,由于petCat()
方法中的逻辑就是创建一个Pet对象,所以这时每调用一次petCat()
方法就会创建一个新的Pet对象,这样每次调用petCat()
方法返回的Pet对象就都不相同了,代码验证如下:
配置类:
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean("user")
public User userTest(){
return new User("张三",18,"男");
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}
}
启动类:
package com.gongsl;
import com.gongsl.bean.Pet;
import com.gongsl.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);//运行结果:com.gongsl.config.MyConfig@437ebf59
Pet pet1 = bean.petCat();
Pet pet2 = bean.petCat();
System.out.println(pet1==pet2);//运行结果:false
}
}
当配置类中的proxyBeanMethods
属性为false时,我们可以发现,pet1和pet2并不是同一个对象,而且从容器中获取的配置类也不带EnhancerBySpringCGLIB
了,而变成了com.gongsl.config.MyConfig@437ebf59
。如果我们把配置类中的proxyBeanMethods
属性设置成true的话,上面pet1和pet2就会是同一个对象,运行结果也自然就是true了。
- 配置类的
@Configuration
注解中proxyBeanMethods
属性可以用来解决组件依赖问题,这个属性也引申出了两种模式,分别是Full模式和Lite模式。当proxyBeanMethods属性值为true时,就是Full模式,这个是默认模式,属性值为false时就是Lite模式。如果组件之间存在依赖关系的话,我们要使用Full模式来保证单实例,无依赖关系则可以使用Lite模式。Lite模式可以减少SpringBoot的判断,加速容器启动的过程。
可以将Pet类设置为User类的一个属性来验证组件依赖:
User实体类:
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:27
*/
@Data
@ToString
@AllArgsConstructor
public class User {
private String name;
private Integer age;
private String gender;
private Pet pet;
}
配置类:
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean("user")
public User userTest(){
return new User("张三",18,"男",petCat());
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}
}
启动类:
package com.gongsl;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//从容器中获取User对象
User user = run.getBean("user", User.class);
//从容器中获取Pet对象
Pet pet = run.getBean("petCat", Pet.class);
System.out.println(user.getPet()==pet);//运行结果:false
}
}
配置类中的proxyBeanMethods
属性设置为了false,所以启动类中运行结果是false。也就是说,从容器中获取的User对象中的pet和容器中的不是一个,这样就无法保证pet的单实例了,那我们把pet注入到容器中就没有意义了,所以遇到这种组件依赖的情况,proxyBeanMethods
属性要设置为true才行。
3.2 @Import注解
- 该注解的值默认是一个数组,书写格式为
@Import({User.class,Pet.class})
,如果数组中只有一个元素,也可以写成@Import(User.class)
这种形式; - 我们可以使用这个注解给容器中自动创建多个不同类型的组件,每个组件的组件id就是数组中各元素的全类名;
- 我们也可以使用
@Import
注解把jar包中的类给注入到容器中; - 如果想要使用
@Import
注解把某个类注入到容器中,那么这个类一定要有无参的构造方法才行。
我们可以把User类、Pet类以及spring-webmvc这个jar包中的ModelAndView类都注入到容器中,演示代码如下:
User类:
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:27
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
private String gender;
}
Pet类:
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:33
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Pet {
private String name;
}
配置类:
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.web.servlet.ModelAndView;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Import({User.class,Pet.class, ModelAndView.class})
@Configuration
public class MyConfig {
/*@Bean("user")
public User userTest(){
return new User("张三",18,"男");
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}*/
}
启动类:
package com.gongsl;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.servlet.ModelAndView;
import java.util.Arrays;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//根据类型获取容器中的组件id
String[] userArr = run.getBeanNamesForType(User.class);
System.out.println(Arrays.toString(userArr));//运行结果:[com.gongsl.bean.User]
String[] petArr = run.getBeanNamesForType(Pet.class);
System.out.println(Arrays.toString(petArr));//运行结果:[com.gongsl.bean.Pet]
String[] servletArr = run.getBeanNamesForType(ModelAndView.class);
//运行结果:[org.springframework.web.servlet.ModelAndView]
System.out.println(Arrays.toString(servletArr));
}
}
通过启动类中的运行结果可以发现,这三个对象都已经注入到容器中了,而且组件id就是全类名。
3.3 @Conditional的子注解
@Conditional
注解包含了很多的子注解,比如:
@ConditionalOnJava
@ConditionalOnProperty
@ConditionalOnResource
@ConditionalOnExpression
@ConditionalOnSingleCandidate
@ConditionalOnBean
@ConditionalOnMissingBean
@ConditionalOnClass
@ConditionalOnMissingClass
@ConditionalOnWebApplication
@ConditionalOnNotWebApplication
@Conditional
及其子注解都是条件注解,即当我们满足这个注解指定的条件的时候,我们才给容器中注入相关的组件,或者干相应的事;@Conditional
注解及其子注解一般用在配置类中,即包含@Configuration
注解的类。
以@ConditionalOnBean
注解为例验证条件注解的用法:
配置类:
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration
public class MyConfig {
@Bean
public User user1(){
return new User("张三",18,"男");
}
@ConditionalOnBean(name = "user12")
@Bean
public User user2(){
return new User("李四",20,"女");
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}
}
启动类:
package com.gongsl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.ArrayList;
import java.util.List;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
List<String> list = new ArrayList<String>();
//获取容器中的所有组件id
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
if("user1".equals(name) || "user2".equals(name) || "petCat".equals(name)){
list.add(name);
}
}
System.out.println(list);//运行结果:[user1, petCat]
}
}
- 配置类中
user2
方法上的@ConditionalOnBean(name = "user12")
注解表示,只有当容器中有名叫“user12”的组件id时,user2
方法上的@Bean
注解才会生效,即才会把user2注入到容器中; - 如果把配置类中的
@ConditionalOnBean(name = "user12")
改成@ConditionalOnBean(name = "user1")
的话,启动类中的运行结果就是:[user1, user2, petCat]; - 类似
@ConditionalOnBean
这种条件注解也可以放到类上面(一般都是指配置类),表示只有当条件注解中的条件满足时,类中的方法上把组件注入到容器中等操作才会生效; - 配置类上的
@Configuration
注解是会把该类作为一个组件注入到容器中的,但是如果类似@ConditionalOnBean
这种条件注解放到配置类上面且条件注解中的条件不满足的话,配置类上的@Configuration
注解就不会生效,配置类自然也不会再被注入到容器中了。
3.4 @ImportResource注解
在之前使用Spring开发项目的时候,我们想要把bean注入到容器中,一般都是使用的配置文件。如果现在使用@Bean
注解了,但是之前使用的配置文件还想保留,也不想把配置文件中的bean标签一个个都转成@Bean
注解的话,就可以使用这个@ImportResource
注解来解决这个问题。
@ImportResource注解的用法案例如下:
在src\main\resources
目录下新增了一个bean.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="haha" class="com.gongsl.bean.User">
<property name="name" value="李四"></property>
<property name="age" value="20"></property>
<property name="gender" value="女"></property>
</bean>
</beans>
配置类:
package com.gongsl.config;
import com.gongsl.bean.Pet;
import com.gongsl.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration
@ImportResource("classpath:bean.xml")
public class MyConfig {
@Bean
public User user(){
return new User("张三",18,"男");
}
@Bean
public Pet petCat(){
return new Pet("汤姆猫");
}
}
启动类:
package com.gongsl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.ArrayList;
import java.util.List;
/**
* 主程序类
* @Author: gongsl
* @Date: 2021-01-08 18:18
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//返回的是IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
List<String> list = new ArrayList<String>();
//获取容器中的所有组件id
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
if("haha".equals(name) || "user".equals(name) || "petCat".equals(name)){
list.add(name);
}
}
System.out.println(list);//运行结果:[user, petCat, haha]
}
}
启动类的运行结果中包含了“haha”这个组件id,说明配置类中的@ImportResource("classpath:bean.xml")
注解生效了,已经把配置文件中bean标签的内容注入到了容器中。
3.5 @ConfigurationProperties注解
-
该注解一般用于做配置绑定,这个注解可以将
properties
文件或者yaml
文件中指定的数据绑定到javaBean中; -
@ConfigurationProperties
可以搭配@Component
、@EnableConfigurationProperties
或@Bean
来使用; -
@ConfigurationProperties
如果不搭配@Bean
的话,一般是放在实体类上的,然后可以使用该注解中的prefix属性来和配置文件中相同前缀的数据进行绑定。但是最好不要使用@ConfigurationProperties(prefix = "user")
这种写法,因为前缀如果是"user"而实体类中正好也有一个name属性的话,那么在进行数据绑定的时候,不管配置文件中针对该属性配置的值是什么,这个name属性获取到的都是我们的电脑用户名; -
由于只有容器中的组件才能使用类似配置绑定等功能,所以需要先把javaBean注入到容器中,比如搭配
@Component
注解进行注入。如果是搭配@EnableConfigurationProperties
注解使用的话,该注解一般用在配置类上。假设我们想对User类进行配置绑定,那么在配置类中使用@EnableConfigurationProperties(User.class)
即可; -
@ConfigurationProperties
如果搭配@Bean
使用的话,这两个注解直接标在配置类的方法上就可以了。假设我们想对jar包中的实体类进行配置绑定的话,由于没法修改jar包中的类,所以是没法在类上标注解的,这时就可以采用搭配@Bean
注解的这种方式进行配置绑定; -
如果需要进行配置绑定的实体类是jar包中的,这时候一般会结合
@EnableConfigurationProperties
注解来进行配置绑定,毕竟jar包中的类我们无法修改,所以没法在类上加@Component
注解,所以结合@Component
注解实现配置绑定的方式就行不通了; -
配置绑定时,会自动检测
src\main\resources
目录下的application.properties
文件或application.yaml
文件,所以需要绑定到javaBean中的内容要写在这俩配置文件的某一个里面才行。当然,写在application.yml
文件中也是可以的,因为它和application.yaml
文件是一样的; -
如果在
src\main\resources
目录下同时存在application.properties
文件和application.yaml
文件,那么在进行配置绑定的时候,application.properties
文件的优先级是要高于application.yaml
文件的,言外之意就是,两个配置文件都针对某个实体类的某个属性配置值了的话,以application.properties
文件中的配置为准。
@ConfigurationProperties
结合@Component
演示配置绑定:
User实体类:
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:27
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Component
@ConfigurationProperties(prefix = "my.user")
public class User {
private String name;
private Integer age;
private String gender;
}
application.properties文件:
my.user.name=Tom
my.user.age=18
如果application.properties中有中文,在配置绑定时可能会乱码,只要勾选IDEA中的如下选项即可解决乱码问题:
如果是放到服务器上,没有办法像本地开发时这种改IDEA的设置的话,也可以把application.properties中的中文直接使用ASCII编码方式显示。比如创建成功
就写成\u521B\u5EFA\u6210\u529F
。
测试类:
package com.gongsl.controller;
import com.gongsl.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: gongsl
* @Date: 2021-01-08 18:19
*/
@RestController
public class HelloController {
@Autowired
User user;
@RequestMapping("/user")
public User home(){
return user;
}
}
执行启动类中的main方法启动项目后浏览器访问:http://localhost:8080/user 会返回如下内容:
{"name":"Tom","age":18,"gender":null}
由返回的内容可知,配置绑定成功了。
@ConfigurationProperties
结合@EnableConfigurationProperties
演示配置绑定:
User实体类:
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:27
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
@ConfigurationProperties(prefix = "my.user")
public class User {
private String name;
private Integer age;
private String gender;
}
配置类:
package com.gongsl.config;
import com.gongsl.bean.User;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration
@EnableConfigurationProperties(User.class)
public class MyConfig {
}
application.properties文件:
my.user.name=Tom
my.user.age=18
测试类:
package com.gongsl.controller;
import com.gongsl.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: gongsl
* @Date: 2021-01-08 18:19
*/
@RestController
public class HelloController {
@Autowired
User user;
@RequestMapping("/user")
public User home(){
return user;
}
}
启动项目进行测试时,测试结果和上面结合@Component
注解演示的测试结果是一样的。与上面结合@Component
注解相比,结合@EnableConfigurationProperties
注解的区别就是去掉了实体类上的@Component
注解,然后在配置类上增加了@EnableConfigurationProperties(User.class)
注解。
@ConfigurationProperties
结合@Bean
演示配置绑定:
User实体类:
package com.gongsl.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:27
*/
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String name;
private Integer age;
private String gender;
}
配置类:
package com.gongsl.config;
import com.gongsl.bean.User;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Author: gongsl
* @Date: 2021-01-11 21:35
*/
@Configuration
public class MyConfig {
@Bean
@ConfigurationProperties(prefix = "my.user")
public User user(){
return new User();
}
}
application.properties文件:
my.user.name=Tom
my.user.age=18
测试类:
package com.gongsl.controller;
import com.gongsl.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author: gongsl
* @Date: 2021-01-08 18:19
*/
@RestController
public class HelloController {
@Autowired
User user;
@RequestMapping("/user")
public User home(){
return user;
}
}
结合@Bean
演示的测试结果和上面两种方式是一样的,这种配置绑定的方式,注解都标到了配置类的方法上,没有标到实体类上,所以即便实体类是jar包中的类,也可以实现配置绑定。
演示application.properties
文件和application.yaml
文件同时存在的场景:
application.properties文件:
my.user.name=Tom
my.user.age=18
application.yaml文件:
my.user:
name: Jerry
age: 20
gender: 男
执行启动类中的main方法启动项目后浏览器访问:http://localhost:8080/user 后返回如下内容:
{"name":"Tom","age":18,"gender":"男"}
通过返回结果发现,虽然name属性和age属性两个配置文件中都有配置值,但是最终使用的是application.properties
文件中的,application.properties
文件中没有配置gender属性的值,所以最终使用的是application.yaml
文件中的值。
3.6 @Value注解
- 该注解中可以使用
${}
这种方式默认从application.properties文件或者application.yaml文件中获取值来给实体类的属性赋值,比如@Value("${my.user.name}")
就是获取文件中my.user.name对应的值。我们还可以在获取不到值的时候设置一个默认值,比如@Value("${my.user.name:Tom}")
这种,就是在获取不到值的时候给实体类中对应属性设置默认值Tom; - 该注解还可以使用SpEL表达式,SpEL表达式是可以进行运算的,比如在实体类的属性上添加
@Value("#{10+2}")
注解,就表示把12
赋值给该属性。我们也可以在SpEL表达式中调用类的方法,下面案例中会有介绍; - 我们也可以使用@Value注解直接给属性设置一个具体值,比如使用
@Value("test")
就是把test
赋值给对应属性。
@Value
注解的用法案例如下:
User类:
package com.gongsl.bean;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.List;
@Data
@Component
public class User {
@Value("${my.user.name}")
private String name;
@Value("#{10+2}")
private Integer age;
@Value("男")
private String gender;
@Value("#{'${my.user.friends}'.split(',')}")
private List<String> friends;
@Value("${my.user.other:默认值}")
private String other;
}
测试类:
package com.gongsl.controller;
import com.gongsl.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
User user;
@RequestMapping("/user")
public User home(){
return user;
}
}
application.properties文件:
my.user.name=Tom
my.user.friends=Jerry,Lucy,Mark
执行启动类中的main方法启动项目后浏览器访问:http://localhost:8080/user 后返回如下内容:
{"name":"Tom","age":12,"gender":"男","friends":["Jerry","Lucy","Mark"],"other":"默认值"}
3.7 @PropertySource注解
我们在进行配置绑定的时候,无论是使用@Value
注解还是使用@ConfigurationProperties
注解,默认情况下都只会从application.properties或者application.yaml这种Spring的主文件中获取数据。如果需要绑定的配置很多,就会导致主配置文件内容过多,不便于我们快速查找数据,这时候就可以使用@PropertySource
注解。我们可以针对某个实体类专门新增一个配置文件,然后使用该注解进行导入,再结合@Value
注解或者@ConfigurationProperties
注解进行配置绑定即可。
@PropertySource
结合@Value
演示配置绑定:
User类:
package com.gongsl.bean;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Data
@Component
@PropertySource(value = {"classpath:test.properties"})
public class User {
@Value("${my.user.name}")
private String name;
@Value("${my.user.age}")
private Integer age;
}
测试类:
package com.gongsl.controller;
import com.gongsl.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
User user;
@RequestMapping("/user")
public User home(){
return user;
}
}
在src\main\resources
目录下新增test.properties文件:
my.user.name=Jerry
my.user.age=18
执行启动类中的main方法启动项目后浏览器访问:http://localhost:8080/user 后返回如下内容:
{"name":"Jerry","age":18}
@PropertySource
结合@ConfigurationProperties
演示配置绑定:
和结合@Value
注解进行配置绑定相比,其他都不变,只需要把User类改成如下内容即可,测试结果也是一样的。
package com.gongsl.bean;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Data
@Component
@PropertySource(value = {"classpath:test.properties"})
@ConfigurationProperties(prefix = "my.user")
public class User {
private String name;
private Integer age;
}
4.Spring Initializr
这是SpringBoot的一个项目初始化向导,在IDEA中我们可以通过这个向导快速创建一个SpringBoot项目,我们可以根据我们的开发场景选择不同的依赖,步骤如下:
- 依次选择File—>New—>Project;
- 选择Spring Initializr,然后选择jdk版本;
- 填写基本信息;
- 选择依赖。我们需要什么场景下的依赖,就在这里进行选择就可以了;
- 填写项目名称、项目位置等信息;
- 最终生成的项目结构如下所示:
5.yaml配置文件的用法
5.1 基本语法
-
使用
key: value
这种表示形式,需要注意的是,冒号后面要有空格; -
大小写敏感;
-
使用缩进表示层级关系;
-
缩进不允许使用tab,只允许空格,但是在IDEA中还是可以使用tab的,IDEA可以为我们自动转换成空格;
-
缩进的空格数不重要,只要相同层级的元素左对齐即可;
-
使用
#
来表示注释; -
表示字符串时,是无需加引号的,如果要加,
''
与""
表示字符串内容会被转义/不转义。比如'test\t测试'
最终结果是test\t测试
,而"test\t测试"
最终结果是test 测试
。
5.2 不同数据类型的表示形式
- 字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
- 对象:键值对的集合。map、hash、set、object
#行内写法:
k: {k1: v1,k2: v2,k3: v3}
#或者:
k:
k1: v1
k2: v2
k3: v3
- 数组:一组按次序排列的值。array、list、queue
#行内写法:
k: [v1,v2,v3]
#或者:
k:
- v1
- v2
- v3
5.3 案例演示
User类:
package com.gongsl.bean;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.Map;
@Data
@Component
@ConfigurationProperties(prefix = "my.user")
public class User {
private String name;
private Integer age;
private String gender;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private Date birth;
private Pet pet;
private List list;
private String[] strings;
private Map map;
private Map<String, Map<String, String>> score;
}
Pet类:
package com.gongsl.bean;
import lombok.Data;
@Data
public class Pet {
private String name;
private Double weight;
}
在src\main\resources
目录下新增application.yaml文件:
my.user:
name: Jerry
age: 20
gender: 男
birth: 2020-12-28 15:36:25
pet:
name: cat
weight: 12.3
list: [abc,123,true,ABC]
strings: [张三,李四,Tom]
map: {a: 王五,b: test}
score:
english:
first: 30
second: 40
third: 50
math: [131,140,148]
chinese: {first: 128,second: 136}
测试类:
package com.gongsl.controller;
import com.gongsl.bean.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
User user;
@RequestMapping("/user")
public User home(){
return user;
}
}
执行启动类中的main方法启动项目后浏览器访问:http://localhost:8080/user 后返回如下内容:
{"name":"Jerry","age":20,"gender":"男","birth":"2020-12-28 15:36:25","pet":{"name":"cat","weight":12.3},"list":["abc",123,true,"ABC"],"strings":["张三","李四","Tom"],"map":{"a":"王五","b":"test"},"score":{"english":{"first":"30","second":"40","third":"50"},"math":{"0":"131","1":"140","2":"148"},"chinese":{"first":"128","second":"136"}}}
把返回的JSON串格式化后如下所示:
{
"name": "Jerry",
"age": 20,
"gender": "男",
"birth": "2020-12-28 15:36:25",
"pet": {
"name": "cat",
"weight": 12.3
},
"list": [
"abc",
123,
true,
"ABC"
],
"strings": [
"张三",
"李四",
"Tom"
],
"map": {
"a": "王五",
"b": "test"
},
"score": {
"english": {
"first": "30",
"second": "40",
"third": "50"
},
"math": {
"0": "131",
"1": "140",
"2": "148"
},
"chinese": {
"first": "128",
"second": "136"
}
}
}
5.4 配置文件的提示功能
自定义的类和配置文件绑定的时候,在application.yaml文件中写对应的配置是没有提示的,如果我们想要有提示,可以通过加入以下依赖来实现:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
加入依赖以后,记得重新启动一下启动类,以便使依赖生效。
由于我们添加的这个依赖只是在代码开发时用于提示的,当项目打包部署后并没有什么实际性的用处,所以我们在项目打包时就不用把这个依赖对应的jar包打包进去了,可以使用exclude
标签把这个依赖排除掉,如下所示:
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
6.Web场景的开发
6.1 静态资源的访问
- 只要静态资源放在类路径下的
/static
、/public
、/resources
、/META-INF/resources
任一文件夹中,就可以使用当前项目根路径/静态资源名
的方式直接进行访问,比如:http://localhost:8080/a.jpg ,如果访问不到,可以使用maven重新编译下再试试; - 在maven项目中上面说的类路径就是指
src\main\resources
目录下,比如src\main\resources\static\a.jpg
; - 如果我们想要改变这些静态资源的默认目录,可以使用
spring.resources.static-locations
配置进行修改,如果是在application.yaml文件中,可以写成下面这样:
spring:
resources:
static-locations: classpath:/test/
如果我们想要自定义多个默认的静态资源目录,可以使用如下写法:
spring:
resources:
static-locations: [classpath:/test1/,classpath:/test2/]
- 假设静态资源放在了默认目录下,但是我们想要给静态资源加一个访问前缀的话可以使用如下写法:
spring:
mvc:
static-path-pattern: /api/**
这时如果想要访问静态资源,就必须在url上加上api
这个前缀了,比如http://localhost:8080/api/a.jpg。
6.2 欢迎页的支持
我们如果将欢迎页index.html
放到项目的静态资源目录下,比如static
目录下,那么当我们在浏览器中进行访问时直接输入IP和端口就可以自动跳转到index.html
页面,案例演示如下:
index.html中的内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试使用</title>
</head>
<body>
<h2>这是一个测试专用页面。</h2>
</body>
</html>
项目结构:
启动项目后测试结果展示:
这里如果不生效,可以使用maven重新编译下后再试试,下面那个自定义favicon也是一样的情况。
6.3 自定义favicon
我们也可以自定义访问项目时浏览器标签栏的图标,只需要将一个favicon.ico
图标放到项目的静态资源目录下即可,比如static
目录下,那么当我们在浏览器中进行访问时标签栏处就可以进行展示了,假设以B站图标为例进行演示:
项目结构:
启动项目后测试结果展示:
启动项目后测试结果展示:
6.4 Web开发常用注解
6.4.1 @PathVariable注解
可以使用该注解获取请求路径中的变量,演示案例如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/test/{id}/owner/{name}")
public Map<String, Object> test(@PathVariable("id") Integer id,
@PathVariable("name") String name){
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",id);
map.put("name",name);
return map;
}
通过浏览器返回结果可知,已经通过@PathVariable
注解获取到了请求路径中的变量啦。
除了以上方式外,还可以通过Map集合的方式获取请求路径中的所有变量,案例演示如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/test/{id}/owner/{name}")
public Map<String, Object> test(@PathVariable Map<String, Object> map){
return map;
}
}
6.4.2 @RequestHeader注解
可以使用该注解获取请求头中的信息,演示案例如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/test/{id}")
public Map<String, Object> home(@PathVariable("id") Integer id,
@RequestHeader("connection") String connection,
@RequestHeader(name = "other", defaultValue = "未找到") String other){
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",id);
map.put("connection",connection);
map.put("other",other);
return map;
}
}
通过浏览器返回结果可知,类似connection
这种请求头中存在的属性就可以获取到对应的值,不存在的返回了我们自己设置的默认值。
我们还可以通过Map集合的方式,获取请求头中所有属性的值,演示案例如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/test/{id}")
public Map<String, Object> home(@PathVariable("id") Integer id,
@RequestHeader Map<String, Object> headers){
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",id);
map.put("headers",headers);
return map;
}
}
6.4.3 @RequestParam注解
可以通过该注解获取请求参数,演示案例如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/test/{id}")
public Map<String, Object> home(@PathVariable("id") Integer id,
@RequestParam("age") Integer age,
@RequestParam("friend") String friend,
@RequestParam Map<String, Object> params){
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",id);
map.put("age",age);
map.put("friend",friend);
map.put("params",params);
return map;
}
}
通过浏览器的返回结果可知,已经获取到了请求参数,而且该注解还支持通过Map集合的方式获取所有的请求参数。
6.4.4 @CookieValue注解
可以通过该注解获取cookie的值。由于我这边测试的时候浏览器中没有cookie,所有我自己手动加了一个,加上后演示案例如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
public class HelloController {
@GetMapping("/test/{id}")
public Map<String, Object> home(@PathVariable("id") Integer id,
@CookieValue("token") String token){
Map<String, Object> map = new HashMap<String, Object>();
map.put("id",id);
map.put("token",token);
return map;
}
}
通过浏览器的返回结果可知,我们已经获取到浏览器中的cookie的值了。
6.4.5 @RequestBody注解
可以使用该注解获取请求体中的内容。这里使用工具发起一个post请求进行演示,演示案例如下:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloController {
@PostMapping("/save")
public String home(@RequestBody String content){
return content;
}
}
通过返回结果可以发现,已经获取到请求体中的内容了。
6.5 统一异常处理
6.5.1 自定义错误页
在类路径下的/static
、/public
、/resources
、/META-INF/resources
任一文件夹中,新建一个error
文件夹,然后在error
文件夹下新增4xx.html或者5xx.html等页面,那么当出现错误的时候,错误状态码是4或者5开头的就会自动跳转到对应页面。比如出现404错误会跳转到4xx.html,出现500错误会跳转到5xx.html,演示如下:
4xx.html文件内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>错误页</title>
</head>
<body>
<h3>错误页!</h3>
</body>
</html>
项目结构如下:
启动项目后,浏览器地址栏随便输入一个错误的url后缀,结果如下:
通过浏览器结果可知,系统已经自动识别到了我们项目类路径下的static/error/4xx.html
文件。
系统在识别错误页的时候,其实是根据错误状态码进行识别的,而且是先精确匹配再模糊匹配,好比出现了404错误,就会首先找404.html
这个文件,如果找不到,才会去找4xx.html
,演示如下:
4xx.html文件内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>错误页</title>
</head>
<body>
<h3>错误页!</h3>
</body>
</html>
404.html文件内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404错误</title>
</head>
<body>
<h3>请求地址有误!</h3>
</body>
</html>
项目结构如下:
启动项目后,浏览器地址栏随便输入一个错误的url后缀,结果如下:
通过浏览器结果可知,当同时存在404.html
和4xx.html
,如果出现了404错误,会优先跳转到404.html
。
如果修改页面内容不生效,可以使用maven重新编译后再试试。
6.5.2 注解方式实现异常处理
这里主要用到@ControllerAdvice
、@ExceptionHandler
、@ResponseStatus
这三个注解。
6.5.2.1 处理指定异常
由于在测试的时候我们只想要返回具体值,而不是跳转页面,所以会用到@RestControllerAdvice
注解,该注解其实就是@ControllerAdvice
注解和@ResponseBody
注解的组合注解,测试如下:
测试类:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloController {
@GetMapping("/test")
public String home(String str){
int i = 10/0;
return str;
}
}
新增一个专门用于处理异常的全局异常处理类:
package com.gongsl.handler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e){
return "算数异常,异常信息为:"+e.getMessage();
}
}
如果我们不需要获取异常中的信息的话,那么上面那个handleArithmeticException
方法直接使用无参的即可。
启动项目后浏览完的测试结果:
我们在测试类中手动创造了一个ArithmeticException异常,由测试结果可知,已经被GlobalExceptionHandler
类中专门用于处理该异常的handleArithmeticException(ArithmeticException e)
方法拦截并处理了。
6.5.2.2 处理未指定的其他异常
我们可以在自定义的GlobalExceptionHandler
类中针对常见的异常分别写一个对应的方法来进行针对性处理,对于不常见的其他异常,我们如果想要统一进行处理的话,只要使用@ExceptionHandler(Exception.class)
注解即可,演示案例如下所示:
测试类:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloController {
@GetMapping("/test")
public String home(@RequestParam("str") String str){
int i = 10/0;
return str;
}
}
这里加一个@RequestParam
注解,但是测试的时候不传参数,以便制造异常场景。
全局异常处理类:
package com.gongsl.handler;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e){
return "算数异常,异常信息为:"+e.getMessage();
}
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
return "其他异常,"+e.getMessage();
}
}
启动项目后浏览器的测试结果:
针对某种异常,如果全局异常处理类中有专门处理该异常的方法,就会直接调用该方法进行处理。如果没有,就会使用标有@ExceptionHandler(Exception.class)
注解的方法来进行统一处理。
6.5.2.3 @ResponseStatus注解的用法
我们可以使用@ResponseStatus
注解来改变请求应答的状态码。
如果测试类和全局异常处理类都和上面6.5.2.2章节中的一样的话,由于异常已经被全局异常处理类处理了,所以请求返回的状态码是200。如果我们想要改变这个状态码的话,就可以使用@ResponseStatus
注解,演示如下:
测试类:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloController {
@GetMapping("/test")
public String home(@RequestParam("str") String str){
int i = 10/0;
return str;
}
}
全局异常处理类:
package com.gongsl.handler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e){
return "算数异常,异常信息为:"+e.getMessage();
}
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public String handleException(Exception e){
return "其他异常,"+e.getMessage();
}
}
这里使用@ResponseStatus(value = HttpStatus.NOT_FOUND)
注解的意思就是把状态码改为404。
启动项目后工具的测试结果:
使用工具进行测试,通过测试结果可知,状态码已经变成404了。
@ResponseStatus
注解还有一个reason
属性,用法演示如下:
测试类:
package com.gongsl.controller;
import org.springframework.web.bind.annotation.*;
@RestController
public class HelloController {
@GetMapping("/test")
public String home(@RequestParam("str") String str){
int i = 10/0;
return str;
}
}
全局异常处理类:
package com.gongsl.handler;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ArithmeticException.class)
public String handleArithmeticException(ArithmeticException e){
return "算数异常,异常信息为:"+e.getMessage();
}
@ExceptionHandler(Exception.class)
@ResponseStatus(value = HttpStatus.GATEWAY_TIMEOUT, reason = "演示使用")
public String handleException(Exception e){
return "其他异常,"+e.getMessage();
}
}
上面的HttpStatus.GATEWAY_TIMEOUT
指的是状态码设置为504的意思。
启动项目后浏览器的测试结果:
需要注意的是,当我们使用了reason
属性后,默认情况下会到默认的静态资源路径下(比如static
路径)的error目录中找错误页。由于我们把状态码设置成了504,所以会在error目录下找有没有504.html文件或者5xx.html文件。没找到就会返回上面那一堆报错。如果我们在error目录下新建了一个504.html文件,内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试</title>
</head>
<body>
<h3>504页面测试!</h3>
</body>
</html>
那么用浏览器测试的时候,就会返回如下内容,这也印证了确实到error目录下找504.html文件或者5xx.html文件了。
如果是使用类似postman等工具进行模拟调用的话,不管项目中有没有504.html或者5xx.html,返回的都是如下信息:
返回信息的应答报文体中message
字段的值就是我们设置的@ResponseStatus
注解中reason
属性的值。