答: Spring
框架是轻量级Java
框架,他完全可以基于配置+一些POJO
(简单的Java对象,不是JavaBean
, EntityBean
或者 SessionBean
,无需担任任何的角色)即可完成一套web
服务器开发。尽管是轻量级,但是还是需要配置大量的xml
,而Spring boot
则将繁琐的配置全部封装起来,使得我们可以开箱即用。
答: 嗯,大概有下面这几个点吧:
Spring
应用容器。Spring boot
开发应用的时间比Spring
要少很多,生产力明显提高了。Spring
组件,例如Spring JDBC
、Spring ORM
等。Spring boot
内嵌了web
容器,例如tomcat
服务器,也可以改为jetty
。Spring boot
内部提供的CLI
命令行工具,例如java
或者Groovy
。xml
配置文件。Spring Boot
遵循“固执己见的默认配置”,以减少开发工作(默认配置可以修改)
。Spring boot
内部也提供了许多的插件,例如Maven
或Gradle
,使得我们的可以快速的开发或者测试Spring
应用。答: 原本我们开发web应用需要手动引入mvc
、jackson
等各种依赖,有了Starters
之后,我们只需引入一个Starters
依赖即可完成所有Spring web
应用的配置,非常方便。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
SpringBoot应用启动器基本的一共有44种,具体如下:
1)spring-boot-starter 这是Spring Boot的核心启动器,包含了自动配置、日志和YAML。
2)spring-boot-starter-actuator 帮助监控和管理应用。
3)spring-boot-starter-amqp 通过spring-rabbit来支持AMQP协议(Advanced Message Queuing Protocol)。
4)spring-boot-starter-aop 支持面向方面的编程即AOP,包括spring-aop和AspectJ。
5)spring-boot-starter-artemis 通过Apache Artemis支持JMS的API(Java Message Service API)。
6)spring-boot-starter-batch 支持Spring Batch,包括HSQLDB数据库。
7)spring-boot-starter-cache 支持Spring的Cache抽象。
8)spring-boot-starter-cloud-connectors 支持Spring Cloud Connectors,简化了在像Cloud Foundry或Heroku这样的云平台上连接服务。
9)spring-boot-starter-data-elasticsearch 支持ElasticSearch搜索和分析引擎,包括spring-data-elasticsearch。
10)spring-boot-starter-data-gemfire 支持GemFire分布式数据存储,包括spring-data-gemfire。
11)spring-boot-starter-data-jpa 支持JPA(Java Persistence API),包括spring-data-jpa、spring-orm、hibernate。
12)spring-boot-starter-data-MongoDB 支持MongoDB数据,包括spring-data-mongodb。
13)spring-boot-starter-data-rest 通过spring-data-rest-webmvc,支持通过REST暴露Spring Data数据仓库。
14)spring-boot-starter-data-solr 支持Apache Solr搜索平台,包括spring-data-solr。
15)spring-boot-starter-freemarker 支持FreeMarker模板引擎。
16)spring-boot-starter-groovy-templates 支持Groovy模板引擎。
17)spring-boot-starter-hateoas 通过spring-hateoas支持基于HATEOAS的RESTful Web服务。
18)spring-boot-starter-hornetq 通过HornetQ支持JMS。
19)spring-boot-starter-integration 支持通用的spring-integration模块。
20)spring-boot-starter-jdbc 支持JDBC数据库。
21)spring-boot-starter-jersey 支持Jersey RESTful Web服务框架。
22)spring-boot-starter-jta-atomikos 通过Atomikos支持JTA分布式事务处理。
23)spring-boot-starter-jta-bitronix 通过Bitronix支持JTA分布式事务处理。
24)spring-boot-starter-mail 支持javax.mail模块。
25)spring-boot-starter-mobile 支持spring-mobile。
26)spring-boot-starter-mustache 支持Mustache模板引擎。
27)spring-boot-starter-Redis 支持Redis键值存储数据库,包括spring-redis。
28)spring-boot-starter-security 支持spring-security。
29)spring-boot-starter-social-facebook 支持spring-social-facebook
30)spring-boot-starter-social-linkedin 支持pring-social-linkedin
31)spring-boot-starter-social-twitter 支持pring-social-twitter
32)spring-boot-starter-test 支持常规的测试依赖,包括JUnit、Hamcrest、Mockito以及spring-test模块。
33)spring-boot-starter-thymeleaf 支持Thymeleaf模板引擎,包括与Spring的集成。
34)spring-boot-starter-velocity 支持Velocity模板引擎。
35)spring-boot-starter-web S支持全栈式Web开发,包括Tomcat和spring-webmvc。
36)spring-boot-starter-websocket 支持WebSocket开发。
37)spring-boot-starter-ws 支持Spring Web Services。
Spring Boot应用启动器面向生产环境的还有2种,具体如下:
38)spring-boot-starter-actuator 增加了面向产品上线相关的功能,比如测量和监控。
39)spring-boot-starter-remote-shell 增加了远程ssh shell的支持。
最后,Spring Boot应用启动器还有一些替换技术的启动器,具体如下:
40)spring-boot-starter-jetty 引入了Jetty HTTP引擎(用于替换Tomcat)。
41)spring-boot-starter-log4j 支持Log4J日志框架。
42)spring-boot-starter-logging 引入了Spring Boot默认的日志框架Logback。
43)spring-boot-starter-tomcat 引入了Spring Boot默认的HTTP引擎Tomcat。
44)spring-boot-starter-undertow 引入了Undertow HTTP引擎(用于替换Tomcat)。
答: 有3个:
Tomcat
(这个是默认的容器)Jetty
Undertow
正是因为Spring boot
内嵌了web
容器,使得我们启动web程序就像启动普通Java程序的一样方便,由于Spring boot
默认使用的web
容器是Tomcat
,如果我们想修改可以使用下面这种方式排除tomcat
容器,再引入其他容器
<!--从Web启动器依赖中排除Tomcat-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加Jetty依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
答: 这个注解我们完全可以通过源码就知道它大概做的实际,如下所示,可以看到SpringBootApplication
主要是由SpringBootConfiguration
、EnableAutoConfiguration
、ComponentScan
构成。这三个注解分别是意思是:
SpringBootConfiguration
内部有一个Configuration
注解,所以它的意思是允许在上下文开启额外bean
或者其他配置类。EnableAutoConfiguration
启用Spring boot
自动配置机制。ComponentScan
自动扫描当前类的包下的所有被@Component (@Service,@Controller)
注解的类。@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
.....
}
答: 我们不妨基于源码找到答案,首先我们编写一个Spring Boot
程序,启动代码如下所示:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
我们开启debug
,查看堆栈执行到调用run:1292, SpringApplication
,从方法不难看出就是拿着DemoApplication.class
的信息完成容器加载,我们不妨步进看看
public static ConfigurableApplicationContext run(Class> primarySource, String... args) {
return run(new Class>[] { primarySource }, args);
}
可以看到它要做的就是创建SpringApplication
实体,并执行run
方法。
public static ConfigurableApplicationContext run(Class>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
我们不妨先看看创建实体这一步做了是什么,如下源码可以看到它大抵时加载启动类信息primarySources
,然后初始化web容器类型,初始化各种启动类和监听类
public SpringApplication(ResourceLoader resourceLoader, Class>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
//项目启动类 SpringbootDemoApplication.class 设置为属性存储起来
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//设置应用类型是 SERVLET 应用(Spring 5 之前的传统 MVC 应用)还是 REACTIVE 应用(Spring 5 开始出现的 WebFlux 交互式应用)
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
// 设置初始化器(Initializer),最后会调用这些初始化器
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器(Listener)
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 初始化 mainApplicationClass 属性:通过堆栈调用信息获取 main()方法启动的主程序启动类
this.mainApplicationClass = deduceMainApplicationClass();
}
核心流程来了run:327, SpringApplication
,笔者会对Spring boot
的核心流程进行描述,见下文注释
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 1. 获取并启动监听
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//2. 项目环境environment 预配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//打印banner,此时控制台会输出一个banner
Banner printedBanner = printBanner(environment);
//3. 创建Sring容器
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
//4. 容器刷新前置处理
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
//5.刷新容器,这里会进行IOC相关操作
refreshContext(context);
//6. 容器刷新后置操作
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
// 7. 发出执行结束的事件通知
listeners.started(context, timeTakenToStartup);
//8. 调用runner,完成启动后的特定命令
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
//9. 返回上下文
return context;
}
总结一下大概有9个步骤:
了解了整体步骤之后,我们再来具体了解相关步骤。
创建监听并准备监听,实际上做的就是从META-INF/spring.factories
中获取关于监听器的类然后返回一个监听器列表,见下图
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class>[] types = new Class>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
this.applicationStartup);
}
接下来就是获取各种配置并加载的逻辑了,这个配置加载包含系统配置以及我们配置文件的配置,见下放代码和图片
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
完成加载后的设置逻辑
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
Boolean ignore = environment.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
Boolean.class, Boolean.TRUE);
//将配置设置为系统属性
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
}
}
然后打印banner
,这就不多说了,之后后控制台会输出一个logo
Banner printedBanner = printBanner(environment);
创建应用上下文,同样的也是从getFromSpringFactories
方法中的META-INF/spring.factories
中获取对应类
@Override
public ConfigurableApplicationContext create(WebApplicationType webApplicationType) {
try {
return getFromSpringFactories(webApplicationType, ApplicationContextFactory::create,
AnnotationConfigApplicationContext::new);
}
catch (Exception ex) {
throw new IllegalStateException("Unable create a default ApplicationContext instance, "
+ "you may need a custom ApplicationContextFactory", ex);
}
}
然后就到了容器刷新的前置步骤,主要做的是资源预处理,见下文注释
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置环境变量,包括各种变量
context.setEnvironment(environment);
//设置上下的bean生成器和资源加载器
postProcessApplicationContext(context);
//执行容器初始化工作
applyInitializers(context);
//触发所有监听contextPrepared方法
listeners.contextPrepared(context);
bootstrapContext.close(context);
//记录启动日志
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 注册启动参数bean以及一些BeanFactoryPostProcessor处理类
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof AbstractAutowireCapableBeanFactory) {
((AbstractAutowireCapableBeanFactory) beanFactory).setAllowCircularReferences(this.allowCircularReferences);
if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
}
if (this.lazyInitialization) {
context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
}
context.addBeanFactoryPostProcessor(new PropertySourceOrderingBeanFactoryPostProcessor(context));
// 加载资源,为了后续IOC扫描提供信息
Set
容器刷新,这里就是实现IOC
的步骤了
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
shutdownHook.registerApplicationContext(context);
}
refresh(context);
}
执行相应Runner
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
最后返回上下文
return context;
篇幅原因可以参考笔者这篇文章,比较细致
Spring Boot自动装配原理以及实践
答: 大概有以下几个注解吧:
@Autowired
:自动注入Spring容器
管理的类。@Component
:使用这个注解意味着这个类被Spring容器
管理。@Service
:使用这个注解意为这服务层的bean
。@Repository
:Dao
层的bean
。@Controller
:控制器的bean
。RestController
:即ResponseBody
+Controller
的集合,即这是个控制器的bean
,返回值直接放到响应体
中。答: 和HTTP
请求是一致的,有4个注解:
@GetMapping
:get请求。@PostMapping
: post请求。@PutMapping
:put请求。@DeleteMapping
: delete请求。答: 有3个吧,分别是:
@Pathvairable
:即获取请求地址后面跟着的参数。
@RequestParam
:将查询参数绑定到Spring MVC
的参数上。
示例如下所示,可以看到http
请求的路径参数/hello/{name}
的name,如果我们需要获取则需要使用PathVariable
获取。而查询参数(即问号后面的键值对的值,需要通过RequestParam
获取)
@RestController
public class TestController {
@GetMapping("/hello/{name}")
public String hello(@PathVariable("name") String name, @RequestParam("sno") String sno) {
return "hello! sno:" + sno + " name:" + name;
}
}
请求示例
C:\Users\xxxx>curl 127.0.0.1:8080/hello/jack?sno=18
# 输出结果
hello! sno:18 name:jack
@RequestBody
:将请求类型为 Content-Type
为application/json
的参数通过HttpMessageConverter
或者自定义的HttpMessageConverter
转换为Java
对象上。这个注解就比较常用了。
@PostMapping("/hi")
public String hi(@RequestBody User params) throws JSONException {
return "hello! " + params.getName();
}
使用示例如下所示
答: properties
和yml
两种格式。
答: 相比于properties
,yml格式的文件结构更加清晰,更易于阅读和理解。但是@PropertySource
这个注解无法读取yml
格式的配置数据。
为了演示这种读取方式,我们可以在resource目录下建立一个 application.yml
文件
配置文件内容为
myConfig: 这个是helloworld配置的具体内容哦
后续如果我们希望获取到myConfig
这个值的话,只需按照下面这种方式引入即可,注意value
注解获取配置值的方式,Spring
并不推荐使用。
@Value("${myConfig}")
private String value;
这种方式相较于上面那种更加强大,可以与bean
绑定,例如我们yml
的配置文件内容如下(注意配置名称必须全小写,否则会报一些奇怪的错误)
myobj:
name: out-side-config
email: [email protected]
那么我们就可以编写一个类,代码如下所示,使用ConfigurationProperties
引入前缀为myobj
的配置内容即可,该配置就会将myobj
前缀下的所有配置和我们的类绑定
/**
* 注意 yml配置文件不能有大写字母
*/
@ConfigurationProperties(prefix = "myobj")
public class MyObj {
private String name;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "MyObj{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}
有时候我们希望指定配置文件和类进行绑定,那么我们就可以使用PropertySource
注解,例如我们在resource
目录下有个student.properties
文件,内容为
name:xiaoming
no:18
我们只需使用PropertySource执行路径以及配置文件名,再配合value即可完成属性绑定。
@Component
@PropertySource("classpath:student.properties")
public class Student {
@Value("${name}")
private String name;
@Value("${no}")
private String no;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", no='" + no + '\'' +
'}';
}
}
答: 以下图为例,读取顺序优先取外层config
目录下的yml
文件,然后是resource
目录下的config
的yml
文件,最后才是resource
目录下的yml
配置文件。
如下图,以笔者为例,笔者在在不同路径下都配置了yml文件,最外层内容为
myConfig: 这个是helloworld配置的具体内容哦
myobj:
name: out-side-config
email: [email protected]
然后我们通过测试单元查看结果,读取的配置取的是最外层
@SpringBootTest
class DemoApplicationTests {
@Resource
private MyObj myObj;
@Test
void contextLoads() {
//输出结果 MyObj{name='out-side-config', email='[email protected]'}
System.out.println(myObj);
}
}
答: 常见的是:MapStruct
、ModelMapper
、Dozer
、Orika
、JMapper
这几种吧。
最常用的还是MapStruct
,它的工作原理也很简单,我们声明一个转换接口后,它会在编译期为了我们生成转换实现类的字节码文件。
对此我们不妨距离一下它的使用方式,首先引入版本号、依赖、插件
版本号
<properties>
<java.version>1.8</java.version>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
</properties>
依赖
<!--mapstruct依赖-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
插件
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
例如我们现在有个Doctor
希望转为DoctorDTO
类,代码如下所示
public class Doctor {
private Integer id;
private String name;
private String srcAddr;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSrcAddr() {
return srcAddr;
}
public void setSrcAddr(String srcAddr) {
this.srcAddr = srcAddr;
}
@Override
public String toString() {
return "Doctor{" +
"id=" + id +
", name='" + name + '\'' +
", srcAddr='" + srcAddr + '\'' +
'}';
}
}
DoctorDTO
类,可以看出地址的字段名为dstAddr
,和上面的srcAddr
有区别
public class DoctorDTO {
private Integer id;
private String name;
private String dstAddr;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDstAddr() {
return dstAddr;
}
public void setDstAddr(String dstAddr) {
this.dstAddr = dstAddr;
}
@Override
public String toString() {
return "DoctorDTO{" +
"id=" + id +
", name='" + name + '\'' +
", dstAddr='" + dstAddr + '\'' +
'}';
}
}
所以我们编写一个接口,如下所示,对于字段名不一样的,我们使用Mapping
手动配置映射关系
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface DoctorMapper {
DoctorMapper INSTANCE = Mappers.getMapper(DoctorMapper.class);
/**
* 会在编译期生成
* @param doctor
* @return
*/
@Mapping(source = "srcAddr", target = "dstAddr")
DoctorDTO toDTO(Doctor doctor);
}
测试代码,可以看到bean转换完成
@Test
public void testToDTO() {
Integer doctorId = 15;
String doctorName = "xiaoming";
Doctor doctor = new Doctor();
doctor.setId(doctorId);
doctor.setName(doctorName);
doctor.setSrcAddr("中国北京");
DoctorDTO doctorDTO = DoctorMapper.INSTANCE.toDTO(doctor);
// 输出结果 DoctorDTO{id=15, name='xiaoming', dstAddr='中国北京'}
System.out.println(doctorDTO);
assertEquals(doctorId, doctorDTO.getId());
assertEquals(doctorName, doctorDTO.getName());
}
通过源码我们可以看到这个接口的实现类会在编译器生成
答: 很简单,引入下面这个依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
然后键入下面的地址即可查看对应端点的信息
http://localhost:8080/actuator
具体可以参考下面这篇文章
集成Spring Boot Actuator很简单,难的是运用场景!
答: 有两种校验框架,一个是Hibernate Validator
,还有一个是JSR(Java Specification Requests)
校验,后者比较常用,无需引入特殊的依赖。就例如我们现在有个Person类,希望名字不为空,性别是是数字最大值为2,而email必须为邮箱格式,那么我们就可以基于JSR
的注解进行说明。
public class Person {
@NotNull(message = "姓名不可为空")
@Size(max = 10, message = "姓名长度不可超过10位")
private String name;
@Max(value = 2, message = "性别最大值只能为2")
private int sex;
@Email(message = "邮箱格式不正确")
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
'}';
}
}
当他作为controller
的requestBody
的参数时,用法如下所示
@PostMapping("/test/hello")
public void hello(@Valid Person person) {
logger.info("hello {}", person.getName());
}
假如我们想校验路径参数时,我们只需在Controller
上方加一个注解@Validated
,然后对于路径参数加入校验注解Valid
+校验规则注解
即可即可。
@GetMapping("/test/hello2/{id}")
public void hello2(@Valid @PathVariable("id") @Max(value = 5,message = "最大值为5") Integer id) {
logger.info("hello {}", id);
}
补充一下常见的一些校验注解:
1. @NotEmpty 被注释的字符串的不能为 null 也不能为空
2. @NotBlank 被注释的字符串非 null,并且必须包含一个非空白字符
3. @Null 被注释的元素必须为 null
4. @NotNull 被注释的元素必须不为 null
5. @AssertTrue 被注释的元素必须为 true
6. @AssertFalse 被注释的元素必须为 false
7. @Pattern(regex=,flag=)被注释的元素必须符合指定的正则表达式
8. @Email 被注释的元素必须是 Email 格式。
9. @Min(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
10. @Max(value)被注释的元素必须是一个数字,其值必须小于等于指定的最大值
11. @DecimalMin(value)被注释的元素必须是一个数字,其值必须大于等于指定的最小值
12. @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
13. @Size(max=, min=)被注释的元素的大小必须在指定的范围内
14. @Digits(integer, fraction)被注释的元素必须是一个数字,其值必须在可接受的范围内
15. @Past被注释的元素必须是一个过去的日期
16. @Future 被注释的元素必须是一个将来的日期
答:通过@ControllerAdvice
将控制器声明为增强器,然后通过ExceptionHandler
对自己自己的异常进行处理。
例如我们想处理所有控制器的BindException,代码如下所示
/**
* 统一异常处理、数据预处理等
*/
@ControllerAdvice
public class ControllerExceptionHandler {
private static final Logger LOG = LoggerFactory.getLogger(ControllerExceptionHandler.class);
/**
* 校验异常统一处理
* @param e
* @return
*/
@ExceptionHandler(value = BindException.class)
@ResponseBody
public CommonResp validExceptionHandler(BindException e) {
CommonResp commonResp = new CommonResp();
LOG.warn("参数校验失败:{}", e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
commonResp.setSuccess(false);
commonResp.setMessage(e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
return commonResp;
}
}
答: 操作步骤很简单,首先在启动类中添加@EnableScheduling
注解,然后编写一个定时任务bean
,然后在定时任务的方法上添加@Scheduled
注解
@Component
@EnableAsync
//@EnableAsync 和 @Async 使定时任务并行执行
public class AsyncScheduledTasks {
private static final Logger log = LoggerFactory.getLogger(AsyncScheduledTasks.class);
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
private List<Integer> index = Arrays.asList(6, 6, 2, 3);
int i = 0;
@Scheduled(fixedRate = 5000)
@Async
public void reportCurrentTimeWithFixedRate() {
log.info("Current Thread : {}", Thread.currentThread().getName());
if (i == 0) {
log.info("Start time is {}", dateFormat.format(new Date()));
}
if (i < 4) {
try {
TimeUnit.SECONDS.sleep(index.get(i));
log.info("Fixed Rate Task : The time is now {}", dateFormat.format(new Date()));
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}
剖析面试最常见问题之 Spring Boot
@RequestParam注解使用
如何在 Spring/Spring Boot 中做参数校验?你需要了解的都在这里!
对象转换工具 MapStruct 介绍
SpringBoot项目是如何启动的?
spring boot的常用注解有哪些?
SpringBoot 的 44 种应用启动器,你都知道吗?