以下转载和参考自:Spring Boot开发 - 廖雪峰的官方网站
1、配置Spring Boot
SpringBoot包含了Spring MVC和Tomca、日志等,启动SpringBoot就开启了Spring MVC和Tomcat。一个Spring Boot项目的组织结构如下所示,可以看到,Spring Boot中已经不需要专门的webapp目录了:
springboot
├── pom.xml
├── src
│ └── main
│ ├── java
│ ├── xsl
│ ├── Application.java
│ ├── entity
│ │ └── User.java
│ ├── service
│ │ ├── UserMapper.java
│ │ └── UserService.java
│ └── web
│ └── UserController.java
│ └── resources
│ ├── application.yml
│ ├── logback-spring.xml
│ ├── static
│ ├── css
│ └── bootstrap.css
│ └── js
│ ├── bootstrap.js
│ └── jquery.js
│ └── templates
│ ├── hello.html
│ └── signin.html
└── target
其中resources目录下的application.yml为Spring Boot默认配置文件,也可以使用.properties文件,如下所示为其和.properties的对比:
# application.yml
server:
#先从环境变量中查找APP_PORT,查找不到的话再使用8080对port赋值,可以在测试环境中使用默认值,正式环境使用环境变量中设置的值
port: ${APP_PORT:8080}
spring:
application:
#先从环境变量中查找APP_NAME,查找不到的话再使用app_name对port赋值,可以在测试环境中使用默认值,正式环境使用环境变量中设置的值
name: ${APP_NAME:app_name}
datasource:
url: jdbc:mysql://127.0.0.1:3306/my_database?serverTimezone=GMT&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: sa
password: 123456
#使用mysql数据库驱动
driver-class-name: com.mysql.cj.jdbc.Driver
#配置JDBC连接池
hikari:
auto-commit: false
connection-timeout: 3000
validation-timeout: 3000
max-lifetime: 60000
maximum-pool-size: 20
minimum-idle: 1
pebble:
# 默认为".pebble",改为"":
suffix:
# 开发阶段禁用模板缓存:
cache: false
# application.properties
spring.application.name = ${ APP_NAME:unnamed }
spring.datasource.url = jdbc:hsqldb:file:testdb
spring.datasource.username = sa
spring.datasource.password =
spring.datasource.dirver - class - name = org.hsqldb.jdbc.JDBCDriver
spring.datasource.hikari.auto - commit = false
spring.datasource.hikari.connection - timeout = 3000
spring.datasource.hikari.validation - timeout = 3000
spring.datasource.hikari.max - lifetime = 60000
spring.datasource.hikari.maximum - pool - size = 20
spring.datasource.hikari.minimum - idle = 1
io.pebbletemplates.pebble.suffix =
io.pebbletemplates.pebble.cache = false
如上所示,对于地址、用户名、密码这种类型的配置信息,可以使用环境变量,我们可以在启动程序时传入环境变量,如java -D PASSWORD=123456 -jar app.jar。
模板引擎中一些常用的默认属性值:
pebble.prefix=/templates/ 模板前缀,这里模板必须放在`/templates`目录
pebble.suffix=.pebble 模板后缀
pebble.encoding=UTF-8 模板编码
pebble.cache=true 使用模板缓存
pebble.content-type=text/html
pebble.defaultLocale=null
pebble.strictVariables=false
resources下的logback-spring.xml(也可以使用logback.xml)是日志logback配置文件,如下为一个标准的写法,它定义了一个控制台输出和文件输出,其中第三行<include resource=.../>引入了Spring Boot的一个缺省配置,这样我们就可以引用CONSOLE_LOG_PATTERN、FILE_LOG_PATTERN等变量:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
</appender>
<appender name="APP_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>utf8</charset>
</encoder>
<file>app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
<maxIndex>1</maxIndex>
<fileNamePattern>app.log.%i</fileNamePattern>
</rollingPolicy>
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>1MB</MaxFileSize>
</triggeringPolicy>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="APP_LOG" />
</root>
</configuration>
如下为pom.xml中引入的相关依赖,其中的<parent>...</parent>表示从spring-boot-starter-parent继承,这样就可以引入Spring Boot的预置配置。然后我们引入了Spring Boot、模板引擎(视图解析器)pebble、JDBC驱动的依赖。可以看到引入的Spring Boot没有指定版本号,因为引入的<parent>内已经指定了,引入的JDBC驱动也没有指定版本号,因为hsqldb已在spring-boot-starter-jdbc中预置了版本号:
<?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">
<!-- 从spring-boot-starter-parent继承 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Spring-Boot</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<!-- 设置使用的版本 -->
<java.version>11</java.version>
<pebble.version>3.1.2</pebble.version>
<mybatis.version>3.5.4</mybatis.version>
<mybatis-spring.version>2.0.4</mybatis-spring.version>
</properties>
<dependencies>
<!-- 集成Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 集成Spring Boot JDBC-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 集成Pebble View -->
<dependency>
<groupId>io.pebbletemplates</groupId>
<artifactId>pebble-spring-boot-starter</artifactId>
<version>${pebble.version}</version>
</dependency>
<!--MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!--集成MyBatis与Spring的库-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- JDBC驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
Application 为Spring Boot启动类。Spring Boot要求main()方法所在的启动类必须放到根package下,我们项目中的根package为xsl,xsl下还有xsl.entity、xsl.service、xsl.web等子package。Spring Boot启动类使用注解@SpringBootApplication,该注解实际上又包含了:
- @SpringBootConfiguration
- @Configuration
- @EnableAutoConfiguration
- @AutoConfigurationPackage
- @ComponentScan
/*Application.java*/
package xsl;
@SpringBootApplication
@MapperScan("xsl.service")
public class Application { //Spring Boot启动类
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args); //启动Spring Boot
}
@Bean//提供创建WebMvcConfigurer方法,使Spring MVC自动处理静态文件,并且映射路径为/static/**
WebMvcConfigurer createWebMvcConfigurer(@Autowired HandlerInterceptor[] interceptors) {
return new WebMvcConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 映射路径`/static/`到classpath路径:
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
};
}
@Bean //提供给MyBatis创建SqlSessionFactoryBean对象的方法
SqlSessionFactoryBean createSqlSessionFactoryBean(@Autowired DataSource dataSource) {
var sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
}
下面是JavaBean、Service、Controller的代码:
/*User.java*/
package xsl.entity;
public class User {
private Long id;
private String email;
private String password;
private String name;
private long createdAt;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
......
}
/*UserMapper.java*/
package xsl.service;
//User类和数据库users表之间映射的Mapper
public interface UserMapper {
//SELECT查询方法
@Select("SELECT * FROM users WHERE id = #{id}") //getById()方法里对应执行的SQL语句
User getById(@Param("id") long id); //通过主键获得一行记录的方法,@Param指示该参数与SQL语句中"id"参数对应
@Select("SELECT id, name, created_time AS createdAt FROM users LIMIT #{offset}, #{maxResults}") //User类成员名要与查询记录的列名相同,列名和类属性名不同的话使用AS
List<User> getAllLimit(@Param("offset") int offset, @Param("maxResults") int maxResults);
//INSERT插入方法
//如果表的id是自增主键,那么,我们在SQL中不传入id,但希望获取插入后的主键,需要再加一个@Options注解
@Options(useGeneratedKeys = true/*设置自增主键*/, keyProperty = "id"/*主键值使用JavaBean的id属性值*/, keyColumn = "id"/*设置主键列名*/)
@Insert("INSERT INTO users (email, password, name, createdAt) VALUES (#{user.email}, #{user.password}, #{user.name}, #{user.createdAt})")
void insert(@Param("user") User user);
//UPDATE更新方法
@Update("UPDATE users SET name = #{user.name}, createdAt = #{user.createdAt} WHERE id = #{user.id}")
void update(@Param("user") User user);
//DELETE删除方法
@Delete("DELETE FROM users WHERE id = #{id}")
void deleteById(@Param("id") long id);
}
/*UserService.java*/
package xsl.service;
@Component
public class UserService {
final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired
UserMapper userMapper;
......
public User register(String email, String password, String name) {
User user = new User();
user.setName(name);
user.setEmail(email);
user.setPassword(password);
user.setCreatedAt(System.currentTimeMillis());
userMapper.insert(user);
logger.info("user registered: {}", user.getEmail());
return user;
}
......
}
/*UserController.java*/
package xsl.web;
@Controller
public class UserController {
@Autowired
UserService userService;
@GetMapping("/test")
public ModelAndView test(){
return new ModelAndView("test.html");
}
@GetMapping("/index")
public ModelAndView index(HttpSession session) {
User user = (User) session.getAttribute("__user__");
Map<String, Object> model = new HashMap<>();
if (user != null) {
model.put("user", user);
}
return new ModelAndView("index.html", model);
}
@PostMapping("/register")
public ModelAndView doRegister(@RequestParam("name") String name,
@RequestParam("password") String password,
@RequestParam("email") String email) {
User user = userService.register(email, password, name);
return new ModelAndView("redirect:/signin");
}
}
下面为templates下的页面文件及其展示效果:
test.html
<html>
<body>
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
</body>
</html>

index.html、_base.html
{% extends "_base.html" %}
{% block main %}
{% if user == null %}
<h3>Welcome!</h3>
<p><a href="/signin">Sign In</a> or <a href="/register">Register</a>
{% else %}
<h3>Welcome {{ user.name }}!</h3>
{% endif %}
{% endblock %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Spring Mvc Web App</title>
<script src="/static/js/jquery.js"></script>
<link href="/static/css/bootstrap.css" rel="stylesheet">
</head>
<body style="font-size:16px">
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal"><a href="/">Home</a></h5>
<nav class="my-2 mr-md-3">
<a class="p-2 text-dark" target="_blank" href="https://www.liaoxuefeng.com/wiki/1252599548343744">Learn</a>
</nav>
{% if user==null %}
<a href="/signin" class="btn btn-outline-primary">Sign In</a>
{% else %}
<span>Welcome, <a href="/user/profile">{{ user.name }}</a></span>
<a href="/signout" class="btn btn-outline-primary">Sign Out</a>
{% endif %}
</div>
<div class="container" style="max-width: 960px">
<div class="row">
<div class="col-12 col-md">
{% block main %}
{% endblock %}
</div>
</div>
<footer class="pt-4 my-md-5 pt-md-5 border-top">
<div class="row">
<div class="col-12 col-md">
<h5>Copyright©2020 {{ __time__ }}</h5>
<p>
<a target="_blank" href="https://www.liaoxuefeng.com/">Learn</a> -
<a target="_blank" href="https://www.liaoxuefeng.com/">Download</a> -
<a target="_blank" href="https://github.com/michaelliao">Github</a>
</p>
</div>
</div>
</footer>
</div>
</body>
</html>

我们可以在项目中点击运行来直接运行当前的SringBoot项目,发布的时候可以使用maven package生成jar包,将jar包和classes目录拷贝出来,使用java -jar jarName.jar来运行SpringBott,如下所示(实际运行的时候发现 在classes目录下仅保留application.yml配置文件程序也可以正常运行):


2、自动重启和打包
可以添加如下的依赖,然后我们开发的时候在IDEA中修改代码后不用重启服务,Spring Boot应用可以自动重启(热部署:只要classpath路径下发生变化,项目就会自动重启)。不起作用的话可以先设置Settings-Build-Compiler-勾选Build Project automatically,然后快捷键ctrl + shift + alt + /,选择Registry, 勾上 Compiler autoMake allow when app running。默认配置下,针对/static、/public和/templates目录中的文件修改,不会自动重启,因为禁用缓存后(yml配置文件里的pebble:cache为false),这些文件在IDEA里修改的话可以实时更新。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
如下所示,可以添加spring-boot-maven-plugin插件,这样使用Maven打包的时候创建的是包含所有依赖(包括内嵌的Tomcat)的jar(另外还会生成一个以.jar.original结尾的的只包含我们自己类的包),所以将其上传到服务器上后可以直接使用java -jar jar_name.jar命令来运行。
<build>
<finalName>awesome-app</finalName> <!-- 指定jar包名称 -->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version> <!-- 与<parent>...</parent>中版本号一致 -->
</plugin>
</plugins>
</build>

使用spring-boot-maven-plugin插件打包Spring Boot应用的话,因为包含了所有依赖,所以生成的jar包会有几十兆,每次修改代码都上传这个大文件的话就有点麻烦。可以使用使用spring-boot-thin-launcher,如下所示修改pom.xml,这样生成的jar包只有几十K(只包含我们自己代码编译后的class以及一些其它信息),使用java -jar jar_name.jar命令运行这个jar包的话,会先在指定目录搜索看看依赖的jar包是否都存在,如果不存在,先从Maven中央仓库下载到本地,然后,再执行main()方法启动程序。
<build>
<finalName>awesome-app</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot.experimental</groupId>
<artifactId>spring-boot-thin-layout</artifactId>
<version>1.0.27.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
spring-boot-thin-launcher在启动时搜索的依赖库默认目录是用户主目录的.m2,我们也可以指定下载目录,例如,将下载目录指定为当前目录:java -D thin.root=. -jar jar_name.jar,这样当前目录下就会生成一个repository目录来存放依赖包。也可以专门用来下载依赖项而不执行程序:java -D thin.dryrun=true -D thin.root=. -jar jar_name.jar,这称之为“预热”(warm up)。如果服务器不允许从外网下载文件,那么可以在本地预热,然后把repository目录上传到服务器,只要依赖项没有变化,后续改动只需要上传jar包即可。
3、使用Actuator进行监控
前面我们已经介绍了使用JMX对Java应用程序包括JVM进行监控,Spring Boot也内置了一个监控功能,它叫Actuator。Actuator会把它能收集到的所有信息都暴露给JMX,其监控功能更丰富。Actuator 2默认会公开/actuator/health和/actuator/info两个地址(health和info被Actuator称为endpoint端点),比如我们访问http://localhost:8080/actuator/health的话,默认就会返回如下的内容。许多网关需要一个URL来探测后端应用是否存活,这个health端点可以提供给网关使用。
{
"status": "UP"
}
使用Actuator只需添加如下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
想要暴露更多的访问点的话,需要在application.yml中进行配置,如下所示暴露了info、health、beans、env四个端点。一些高危端点可能还需要配置management.endpoints.端点名.enabled=true才能开启,可以通过http://localhost:8080/actuator 来查看所有暴露出来的端点。
management:
endpoints:
web:
exposure:
include: info, health, beans, env
如果要暴露的端点比较多,可以配置management.endpoints.web.exposure.include=* 来开启全部端点,然后配置management.endpoints.web.exposure.exclude来指定不包含的端点。如果是通过JMX方式进行访问,则配置management.endpoints.jmx.exposure.include。
Actuator会暴露服务的内部详细信息,所以为了安全起见,建议添加安全控制的相关依赖spring-boot-starter-security,设置相关安全校验规则后,访问端点的时候就会提示输入用户名和密码,如下所示:

可以配置health端点显示详细的服务健康信息,通过配置 management.endpoint.health.show-details=always,如下所示,开启了详细信息展示后,会展示项目磁盘空间以及使用到的redis、数据库等组件的健康信息(status为down表示组件异常)。

Spring boot中包含了大量的HealthIndicator实现类来为health端点提供健康信息,如DiskSpaceHealthIndicator用来实现检查磁盘,RedisHealthIndicator用来检查redis,可以通过实现HealthIndicator的接口来增加自定义的健康组件信息(将该实现类注册为spring bean),如下所示,增加了MyHealthIndicator后,health端点就会增加MyHealthIndicator的健康信息。

info端点用来展示服务的相关信息,比如项目构建信息、git信息、自定义信息,可以在配置文件中来配置要显示的自定义信息,比如在management中配置了info.app.encoding=UTF-8后,访问info端点展示的JSON内容中"encoding"的值就会为"UTF-8",如下所示。

可以通过继承InfoContributor,然后实现该类中的 contribute 方法来实现自定义的info:

metrics端点可以查看jvm已用内存、tomcat当前线程数、request 次数和时间、http 请求调用情况(比如显示 10 个请求量最大,耗时最长的 URL)、数据库连接池hikaricp等指标信息。通过访问/actuator/metrics 接口可以看到metrics统计的所有指标,通过访问 /actuator/metrics/指标名 就可以获得指标的详细信息,比如访问 /actuator/metrics/jvm.memory.max的话,就会返回jvm最大内存的相关信息。
可以添加自定义指标到metrics端点,自定义指标可以为Gauge、Counter、Timer、Summary四种类型之一。Gauge(计量器)用来计量一些数据,如下所示我们通过Metrics.gauge()方法设置了user.test.gauge指标,并将其设置为3,通过访问/actuator/metrics/user.test.gauge 可以查看到该指标的value值为3。


Counter是计数器类型,如下所示,我们创建了名称为user.counter.total的计数器,然后在processCollectResult()方法中对其加1,假设processCollectResult()方法被调用了3次,那么查看该计数器的话,COUNT value就是3:


Timer是一种计时器,如下所示我们创建了名为user.test.timer的计时器,然后通过计时器的record()方法来调用createOrder()方法,假设hello()方法被执行了3次,那么查看该计时器的话,会看到其被使用的次数,总时长和最大时长:


Summary(摘要)用来统计数据,它不同于计量器(设置一个数的值)和计数器(递增一个数的值),它可以记录多个数值。如下所示创建了一个名为user.test.summary的摘要,然后通过其record()方法记录了多个数值:


/heapdump端点可以生成一个jvm的内存快照,可以通过JDK 自带的 Jvm 监控工具 VisualVM 打开此文件查看内存快照,如下所示:

/threaddump端点可以查看服务的线程的相关情况,如线程ID、线程状态、线程堆栈等:

/loggers端点可以查看服务中配置的所有logger的信息,还可以通过该端点来改变服务使用logger的等级(如将INFO修改为DEBUG)——通过向http://localhost:8080/actuator/loggers/root 发送POST请求,并携带数据 {"configuredLevel":"DEBUG"}。
/beans端点可以返回Spring 容器中所有bean的类型、别名、是否单例、依赖等信息。
/shutdown端口可以用来关闭服务,为了安全起见,一般不会开启这个端口。
4、使用Profiles
前面讲过Spring提供的Profiles功能,Profile表示一个环境的概念,如开发、测试和生产这3个环境,或者按git分支定义master、dev这些环境,Spring Boot对Profiles的支持在于,可以在application.yml中为每个环境进行配置。如下所示,分隔符---下面分别是test环境和production的配置,算上默认的环境default(启动应用的时候不指定任何Profile),这里面一共配置了三个环境:默认使用8080端口,在test环境下的话使用8000端口,在production环境下使用80端口并且启用Pebble的缓存。要以test环境启动程序的话,可以为:java -D spring.profiles.active=test -jar jar_name.jar。
spring:
application:
name: ${APP_NAME:unnamed}
datasource:
......
pebble:
suffix:
cache: false
server:
port: ${APP_PORT:8080}
---
spring:
profiles: test
server:
port: 8000
---
spring:
profiles: production
server:
port: 80
pebble:
cache: true
如下所示,默认情况下in成员会使用Foo类注入,如果使用其它环境的话就使用Bar注入:
public interface MyInter {
...
}
@Component
@Profile("default")
public class Foo implements MyInter{
...
}
@Component
@Profile("!default")
public class Bar implements MyInter{
...
}
@Autowired
private MyInter in;
5、条件注解
在Spring中可以通过@Conditional来使指定的类生效(能够通过Ioc实例化该类),Spring Boot中有更多的Conditional:
@ConditionalOnProperty:如果有指定的配置,条件生效;
@ConditionalOnBean:如果有指定的Bean,条件生效;
@ConditionalOnMissingBean:如果没有指定的Bean,条件生效;
@ConditionalOnClass:如果有指定的Class,条件生效;
@ConditionalOnMissingClass:如果没有指定的Class,条件生效;
@ConditionalOnWebApplication:在Web环境中条件生效;
@ConditionalOnExpression:根据表达式判断条件是否生效。
@Component
@ConditionalOnProperty(name="app.smtp", havingValue="true") //配置文件中存在app.smtp=true,IoC才会实例化MailService,否则抛出异常
public class MailService {
...
}
@Component
@ConditionalOnClass(name = "javax.mail.Transport") //classpath中存在类javax.mail.Transport,IoC才会实例化MailService,否则抛出异常
public class MailService {
...
}
如果需要注入的是一个接口成员的话,IoC会自动搜索接口的实现类来注入该成员,如果有多个接口实现类的话,可以通过条件注解来设置使用哪个类,比如使用@ConditionalOnProperty来设置通过配置文件中配置来指定使用哪个类:
@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "file", matchIfMissing = true)//配置文件存在app.storage且其值为file,或者配置文件中不存在app.storage,该类有效
public class FileUploader implements Uploader {
...
}
@Component
@ConditionalOnProperty(name = "app.storage", havingValue = "s3") //配置文件存在app.storage,且其值为s3时,该类有效
public class S3Uploader implements Uploader {
...
}
@Component
public class UserImageService {
@Autowired
Uploader uploader; //通过配置文件中配置来选择使用哪个接口实现类来注入该成员
}
@Configuration
@ComponentScan
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(App.class);
UserImageService ser = context.getBean(UserImageService.class);
}
}
6、读取配置文件
如下所示的application.yml配置文件中的内容,除了使用@Value来读取一个配置信息到成员中( 如@Value("${storage.local.max-size:102400}") ),还可以将配置文件中的一组信息保存到JavaBean中来读取,通过使用@ConfigurationProperties。如下所示,我们将storage.local中的所有配置信息保存到了StorageConfiguration对象中:
storage:
local:
root-dir: ${STORAGE_LOCAL_ROOT:/var/storage}
max-size: ${STORAGE_LOCAL_MAX_SIZE:102400}
allow-empty: false
allow-types: jpg, png, gif
@Configuration //相当于@Component,也可以不写该注解,然后在@SpringBootApplication入口类中使用@Import(StorageConfiguration.class)来手动加载
@ConfigurationProperties("storage.local") //读取配置文件(默认为application.yml或application.properties)中storage.local的信息到本类
public class StorageConfiguration {
private String rootDir;
private int maxSize;
private boolean allowEmpty;
private List<String> allowTypes;
String getRootDir(){...}
...
}
@Component
public class StorageService {
@Autowired
StorageConfiguration storageConfig;
@PostConstruct
public void init() {
storageConfig.getRootDir();
storageConfig.getMaxSize();
...
}
}
7、禁用自动配置
Spring Boot大量使用自动配置和默认配置,极大地减少了代码,通常只需要加上几个注解,并对配置文件配置以下即可。例如,配置JDBC,默认情况下,只需要如下所示配置一个spring.datasource,Spring Boot就会自动创建出DataSource、JdbcTemplate、DataSourceTransactionManager,非常方便。
spring:
datasource:
url: jdbc:hsqldb:file:testdb
username: sa
password:
dirver-class-name: org.hsqldb.jdbc.JDBCDriver
有时候,我们需要要禁用某些自动配置,比如系统有主从两个数据库,而Spring Boot的自动配置只能配一个,这时候就需要关闭数据库的自动配置。关于怎样使SpringBoot支持两个数据库,具体可以参考:禁用自动配置 - 廖雪峰的官方网站。
8、添加Filter
在SpringBoot中想要使用Filter的话,通过继承FilterRegistrationBean来实现,如下所示的AuthFilterRegistrationBean实现了过滤所有所有URL的Filter:
@Component
public class AuthFilterRegistrationBean extends FilterRegistrationBean<Filter> {
@Autowired
UserService userService;
@Override
public Filter getFilter() { //返回要注册的Filter
setOrder(10); //设置Filter的顺序
return new AuthFilter();
}
class AuthFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException{
userService.func(); //内部类可以直接使用外部类的成员
...
}
}
}
上面对所有请求都会过滤,也可以设置仅对指定路径有效:
@Component
public class ApiFilterRegistrationBean extends FilterRegistrationBean<Filter> {
@PostConstruct
public void init() {
setOrder(20);
setFilter(new ApiFilter()); //设置要注册的Filter
setUrlPatterns(List.of("/api/*")); //设置要过滤的路径
}
class ApiFilter implements Filter {
...
}
}
9、全局异常处理
@ExceptionHandler注解用来处理控制器中的异常,如下所示,当MyController中的方法抛出异常的时候,handleRuntimeException()方法会被调用:
@RestController
public class MyController {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
// 自定义异常处理逻辑
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}
}
如果希望能处理多种类型的异常,可以在@ExceptionHandler中传入多个异常类,比如@ExceptionHandler({IllegalArgumentException.class, NullPointerException.class})
也可以将所有控制器的异常放到一个类中统一进行处理,通过@ControllerAdvice,如下所示的GlobalExceptionHandler就是一个全局的异常处理器。Spring遵循“就近原则”来选择异常处理方法,如果有多个异常处理方法可用,Spring会选择与抛出的异常最匹配的那个,比如一个方法抛出了NumberFormatException,而存在多个处理不同异常类型的方法,Spring会选择专门处理NumberFormatException的方法。
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGenericException(Exception ex) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("发生错误: " + ex.getMessage());
}
}
@RestControllerAdvice注解则表示专门用于处理RESTful控制器的异常,默认将处理结果写入响应体,无需@ResponseBody。
@ResponseStatus 注解可以为异常处理方法指定一个特定的 HTTP 状态码。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS) // 429状态码
public String handleRateLimitException(RuntimeException e) {
return e.getMessage();
}
}
处理异常的方法不仅可以接收异常对象,还可以接收其它参数,如 HttpServletRequest或WebRequest,比如handleRuntimeException(RuntimeException ex, WebRequest request)。
异常处理方法返回值可以为String、ModelAndView、ResponseEntity,返回值类型应与控制器中其他方法的返回值类型一致,比如控制器方法返回ModelAndView对象,异常处理方法也应该返回ModelAndView。
【转】&spm=1001.2101.3001.5002&articleId=126345186&d=1&t=3&u=e6c208746e3c442fafa39fec2b216762)
6481

被折叠的 条评论
为什么被折叠?



