# SpringBoot
springboot
Spring Boot针对我们常用的开发场景提供了一系列自动化配置来减少原本复杂而又几乎很少改动的模板化配置内容。
但是,我们还是需要去了解如何在Spring Boot中修改这些自动化的配置内容,以应对一些特殊的场景需求,
比如:我们在同一台主机上需要启动多个基于Spring Boot的web应用,若我们不为每个应用指定特别的端口号,那么默认的8080端口必将导致冲突。
# RSocket | 替代 REST 的不二选择
# 应用限流
本质都是降低流量保证应用的高可用。
# 常见算法
- 针对于单个应用的限流 RateLimiter够用了,如果是分布式环境可以借助 redis来完成。
对于限流常见有两种算法:
TIP
- 漏桶算法
- 令牌桶算法
漏桶算法比较简单,就是将流量放入桶中,漏桶同时也按照一定的速率流出,如果流量过快的话就会溢出( 漏桶并不会提高流出速率)。溢出的流量则直接丢弃。
漏桶算法虽说简单,但却不能应对实际场景,比如突然暴增的流量。
这时就需要用到 令牌桶算法:
令牌桶会以一个恒定的速率向固定容量大小桶中放入令牌,当有流量来时则取走一个或多个令牌。当桶中没有令牌则将当前请求丢弃或阻塞。
相比之下令牌桶可以应对一定的突发流量.
# RateLimiter实现
对于令牌桶的代码实现,可以直接使用 Guava包中的 RateLimiter。引入jar包
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
2
3
4
5
6
//Java Suppress Warning “RateLimiter is marked unstable”因为有注解@Beta
RateLimiter rateLimiter = RateLimiter.create(1.5);
for (int i = 0; i < 8; i++){
double obtain = rateLimiter.acquire();
log.info("获取令牌成功,消耗=[{}]",obtain);
log.info("返回:[{}]",Thread.currentThread().getName());
}
2
3
4
5
6
7
22:27:11.564 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.0]
22:27:11.564 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:12.234 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.655851]
22:27:12.234 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:12.906 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.659875]
22:27:12.906 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:13.566 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.657954]
22:27:13.566 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:14.232 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.665046]
22:27:14.232 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:14.898 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.666062]
22:27:14.898 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:15.565 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.666073]
22:27:15.565 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
22:27:16.233 [main] INFO com.chlm.mysession.common.RateLimterTest - 获取令牌成功,消耗=[0.665858]
22:27:16.233 [main] INFO com.chlm.mysession.common.RateLimterTest - 返回:[main]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以看到差不多7秒一个请求。被限流了,每秒1.5个请求(1000/1.5)
使用 RateLimiter有几个值得注意的地方:
允许 先消费,后付款,意思就是它可以来一个请求的时候一次性取走几个或者是剩下所有的令牌甚至多取,但是后面的请求就得为上一次请求买单,它需要等待桶中的令牌补齐之后才能继续获取令牌。
# 总结
针对于单个应用的限流 RateLimiter够用了,如果是分布式环境可以借助 redis来完成。具体实现在接下来讨论。
# 内嵌服务器引擎Tomcat,Jetty实现原理
# 概述
1.SpringBoot使用main方法启动的一个重要特性是,不需要打包成war部署到Tomcat这种Servlet容器中,而是只需打包成jar,然后通过java或mvn等命令运行这个jar包,然后应用就可以在指定的端口监听客户端的连接请求了。
2.在SpringBoot内部主要是引用了embedded的Tomcat或Jetty等作为Servlet引擎,由该Servlet引擎负责接收Web请求并交给应用处理和生成响应,从而可以将应用打包成jar,直接在命令行启动,以独立进程的方式运行,无需依赖Tomcat等Servlet容器,但是可以实现跟部署到Tomcat中的war包一样处理Web请求和响应。
# 接口设计
- 在接口设计层面,对Spring容器而言,是通过
拓展ApplicationContext接口来增加Spring容器对Servlet引擎的支持;对Servlet引擎而言,则是定义了WebServer接口来代表Servlet引擎或者说是Web服务器,WebServer接口实现类为具体的Servlet引擎实现。 - Spring容器通过包含WebServer的引用来负责Servlet引擎的启动关闭。同时与普通Servlet容器实现(如Tomcat)一样,将Spring应用通过关联一个ServletContext引用来建立与Servlet引擎的关联,同时将自身保存为
ServletContext引用的attribute。 - 应用自身在启动的时候,会创建和启动Spring容器,在Spring容器中通过该WebServer引用来启动对应的Servlet引擎。并在启动Servlet引擎过程中,创建该应用对应的ServletContext,由该ServletContext来间接建立当前应用与Servlet引擎的关联。
# Spring容器ApplicationContext体系
WebServerApplicationContext接口:定义获取WebServer引用的方法
public interface WebServerApplicationContext extends ApplicationContext {
/**
- Returns the {@link WebServer} that was created by the context or {@code null} if
- the server has not yet been created.
- @return the web server
*/
WebServer getWebServer();
/**
- Returns the namespace of the web server application context or {@code null} if no
- namespace has been set. Used for disambiguation when multiple web servers are
- running in the same application (for example a management context running on a
- different port).
- @return the server namespace
*/
String getServerNamespace();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ServletWebServerApplicationContext:WebServerApplicationContext接口的具体实现类,包含WebServer的引用,类定义如下:
public class ServletWebServerApplicationContext extends GenericWebApplicationContext implements ConfigurableWebServerApplicationContext {
private static final Log logger = LogFactory.getLog(ServletWebServerApplicationContext.class);
/**
- Constant value for the DispatcherServlet bean name. A Servlet bean with this name
- is deemed to be the "main" servlet and is automatically given a mapping of "/" by
- default. To change the default behavior you can use a
- {@link ServletRegistrationBean} or a different bean name.
*/
public static final String DISPATCHER_SERVLET_NAME = "dispatcherServlet";
private volatile WebServer webServer;
private ServletConfig servletConfig;
private String serverNamespace;
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 由继承体系可知,继承了GenericWebApplicationContext,在GenericWebApplicationContext中定义了ServletContext引用。
- 由代码注释可知,Spring容器管理了一个ServletWebServerFactory引用,ServletWebServerFactory为WebServer实现类对象的工厂类,通过ServletWebServerFactory来创建servlet引擎WebServer,其中由ServletWebServerFactory创建的WebServer默认在8080端口监听请求,具体为在基类AbstractConfigurableWebServerFactory中定义。
- 同时注册到Spring容器的Servlet,Filter接口的实现类会自动注册到Servlet引擎,具体为应用对应的ServletContext。 AnnotationConfigServletWebServerApplicationContext:基于注解的Spring容器,继承了ServletWebServerApplicationContext。
- XmlServletWebServerApplicationContext:基于XML的Spring容器,继承了ServletWebServerApplicationContext。
# Servlet引擎体系
WebServer接口:声明Servlet引擎启动,关闭的方法,相应的实现类实现这些方法来定义启动和关闭逻辑,而对Spring容器ServletWebServerApplicationContext而言,只需依赖这个接口即可,不依赖具体实现,这也是遵循了依赖倒置设计原则。
public interface WebServer {
/**
- Starts the web server. Calling this method on an already started server has no
- effect.
- @throws WebServerException if the server cannot be started
*/
void start() throws WebServerException;
/**
- Stops the web server. Calling this method on an already stopped server has no
- effect.
- @throws WebServerException if the server cannot be stopped
*/
void stop() throws WebServerException;
/**
- Return the port this server is listening on.
- @return the port (or -1 if none)
*/
int getPort();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ServletWebServerFactory接口:Servlet引擎WebServer的工厂接口,接口定义如下,声明了getWebServer方法。也是遵循依赖倒置设计原则,即Spring容器ServletWebServerApplicationContext只依赖这个接口,具体为这个接口的getWebServer方法来获取一个Servlet引擎WebServer对象,而该接口的具体实现类,负责实现这个接口。实现类包括:TomcatServletWebServerFactory,JettyServletWebServerFactory,UndertowServletWebServerFactory,具体在embedded包定义。
@FunctionalInterface
public interface ServletWebServerFactory {
/**
- Gets a new fully configured but paused {@link WebServer} instance. Clients should
- not be able to connect to the returned server until {@link WebServer#start()} is
- called (which happens when the {@code ApplicationContext} has been fully
- refreshed).
- @param initializers {@link ServletContextInitializer}s that should be applied as
- the server starts
- @return a fully configured and started {@link WebServer}
- @see WebServer#stop()
*/
WebServer getWebServer(ServletContextInitializer... initializers);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Spring容器和Servlet引擎启动过程
Spring容器是在ApplicationContext的refresh方法定义启动流程的,具体为在AbstractApplicationContext中定义refresh方法的流程模板:关于Servlet引擎的启动,是在onRefresh和finishRefresh方法定义的,由onRefresh方法的注释可知,这个方法是设计用来注册有特殊功能的bean对象到Spring容器内部的BeanFactory的。所以Spring容器的设计拓展性是很好的。
ServletWebServerApplicationContext的refresh方法,onRefresh方法,finishRefresh方法。其中onRefresh方法负责Server引擎和ServletContext的创建;finishOnRefresh方法负责Servlet引擎的启动,即调用WebServer的start方法,然后在指定的端口,如8080,监听客户端的请求。
createWebServer方法实现:创建servlet引擎WebServer和ServletContext。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
startWebServer方法实现:调用Servlet引擎的start方法完成启动。
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
2
3
4
5
6
7
以下为TomcatWebServer的start方法实现:启动应用在Servlet规范中对应的Context,即TomcatEmbeddedContext
@Override
public void start() throws WebServerException {
synchronized (this.monitor) {
if (this.started) {
return;
}
try {
addPreviouslyRemovedConnectors();
Connector connector = this.tomcat.getConnector();
if (connector != null && this.autoStart) {
performDeferredLoadOnStartup();
}
checkThatConnectorsHaveStarted();
this.started = true;
logger.info("Tomcat started on port(s): " + getPortsDescription(true) + " with context path '"
+ getContextPath() + "'");
}
catch (ConnectorStartFailedException ex) {
stopSilently();
throw ex;
}
catch (Exception ex) {
throw new WebServerException("Unable to start embedded Tomcat server", ex);
}
finally {
Context context = findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 花哨调整
# banner
玩过SpringBoot的都知道,SpringBoot启动的时候,默认会在控制台打印SpringBoot字样和当前版本。
网上有很多设置banner的方案。
我一般是直接在resource文件夹下面新增文件:banner.txt
${AnsiColor.BRIGHT_RED}
__ __ ______ ____ ______ __ __ ____
/\ \/\ \ /\__ _\ /\ _`\ /\__ _\ /\ \/\ \ /\ _`\
\ \ \_\ \ \/_/\ \/ \ \ \L\ \ \/_/\ \/ \ \ `\\ \ \ \ \L\_\
\ \ _ \ \ \ \ \ \ , / \ \ \ \ \ , ` \ \ \ \L_L
\ \ \ \ \ \ \ \ \ \ \\ \ \_\ \__ \ \ \`\ \ \ \ \/, \
\ \_\ \_\ \ \_\ \ \_\ \_\ /\_____\ \ \_\ \_\ \ \____/
\/_/\/_/ \/_/ \/_/\/ / \/_____/ \/_/\/_/ \/___/
${AnsiColor.BRIGHT_RED}
Application Version: ${application.version} ${application.formatted-version}
Spring Boot Version: ${spring-boot.version} ${spring-boot.formatted-version}
2
3
4
5
6
7
8
9
10
11
12
转换网站:
# 关闭banner
修改启动类
public class MysessionApplication {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(MysessionApplication.class);
application.setBannerMode(Banner.Mode.OFF);
application.run(args);
// SpringApplication.run(MysessionApplication.class, args);
}
}
2
3
4
5
6
7
8
# Springboot中编写Mock单元测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2
3
4
5
其提供了:
- JUnit 4: 目前最强大的java应用单元测试框架
- Spring Test & Spring Boot Test: Spring Boot 集成测试支持.
- AssertJ: 一个java断言库,提供测试断言支持.
- Hamcrest: 对象匹配断言和约束组件.
- Mockito: 知名 Java mock 模拟框架.
- JSONassert: JSON断言库.
- JsonPath: JSON XPath 操作类库.
测试框架提供一个@SpringBootTest注解来提供SpringBoot单元测试环境支持。你使用的JUnit版本如果是JUnit 4不要忘记在测试类上添加
@RunWith(SpringRunner.class),JUnit 5就不需要了。默认情况下,@SpringBootTest不会启动服务器。
您可以使用其webEnvironment 属性进一步优化测试的运行方式,webEnvironment 相关讲解:
- MOCK(默认):加载Web ApplicationContext并提供模拟Web环境。该选择下不会启动嵌入式服务器。如果类路径上没有Web环境,将创建常规非Web的ApplicationContext。你可以配合@AutoConfigureMockMvc或@AutoConfigureWebTestClient模拟的Web应用程序。
- RANDOM_PORT:加载aWebServerApplicationContext并提供真实的Web环境,启用的是随机web容器端口。
- DEFINED_PORT:加载WebServerApplicationContext并提供真实的Web环境 和RANDOM_PORT不同的是启用你激活的SpringBoot应用端口,通常都声明在application.yml配置文件中。
- NONE:通过SpringApplication加载一个ApplicationContext。但不提供 任何 Web环境(无论是Mock或其他)。
注意事项:如果你的测试带有@Transactional注解时,默认情况下每个测试方法执行完就会回滚事务。但是当你的webEnvironment 设置为RANDOM_PORT或者 DEFINED_PORT,也就是隐式地提供了一个真实的servlet web环境时,是不会回滚的。这一点特别重要,请确保不会在生产发布测试中写入脏数据。
编写http接口,编写测试类
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@Slf4j
public class PersonModelControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private PersonModelService personModelService;
@Test
public void findBy() {
try {
String firstName = "羽";
String expect = "{\"title\":\"htring\"}";
mockMvc.perform(MockMvcRequestBuilders
.get("/person/find")
.param("firstName",firstName))
.andExpect(MockMvcResultMatchers
.content()
.json(""))
.andDo(MockMvcResultHandlers.print());
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
MockMvc 执行一个模拟的post请求然后期望结果是expect Json字符串并且将相应对象打印了出来(下图1标识)。一旦请求不通过将抛出java.lang.AssertionError错误, 会把期望值(Expected)跟实际值打印出来
# 测试启动快
随着项目的代码量越来越大,你会发现测试类的启动速度变得越来越慢,而大多数情况下只是为了测试一下某个实现类的某个方法而已,比如测试一个DAO类的persist方法
@SpringBootTest 注解还
提供了两个参数,好好利用这两个参数就可以让测试类的启动速度变得更快。
- 1、webEnvironment
这个属性决定了测试类要不要启动一个 web 环境,说白了就是要不要启动一个 Tomcat 容器,可选的值为:- MOCK, 启动一个模拟的 Servlet 环境,这是默认值。
- RANDOM_PORT,启动一个 Tomcat 容器,并监听一个随机的端口号
- DEFINED_PORT,启动一个 Tomcat 容器,并监听配置文件中定义的端口(未定义则默认监听8080)
- NONE,不启动 Tomcat 容器
如果你要测试的方法不需要用到 Tomcat 容器,比如:
- 测试一个 DAO 类的增删改查
- 测试一个 Service 类的业务方法
- 测试一个 Util 类的公用方法
- 测试一个配置文件类是否读取到了正确的值
只需要通过指定 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) 即可达到加速的效果。这时测试类启动时就只会初始化 Spring 上下文,不再启动 Tomcat 容器了:
- 2、classes
classes 属性用来指定运行测试类需要装载的 class 集合,如果不指定,那么会默认装载 @SpringBootConfiguration 注解标注的类。
也就是说,如果我们不指定classes属性,那么启动测试类时需要加载的Bean的数量和正常启动一次入口类(即有@SpringBootApplication注解的类)加载的 Bean 数量是一样的。
如果你的项目中有很多个 Bean, 特别是有以下几种时:
- 有 CommandLineRunner 的实现类
- 用 @PostConstruct 注解指定了初始化方法的类
SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes={HelloServiceImpl.class})
public class HelloServiceTests {
@Autowired
private IHelloService helloService;
@Test
public void testHello() {
// ...
}
}
2
3
4
5
6
7
8
9
10
# 高阶用法
# 1、获取Spring IOC容器(Aware
通过ApplicationContextAware实现,在bean实例化后,经过Aware扫描时,发现实现了ApplicationContextAware接口,就会调用setApplicationContext方法注入ApplicationContext对象,这也是非常经典的一种获取上下文的方法。
# 2、动态注册bean信息(BeanFactoryPostProcessor)
通过实现BeanDefinitionRegistryPostProcessor接口完成bean的动态注入,而且图中的动态生成还相比一般的注册更加复杂,往其中添加了类似于@Value一般的属性值,尽管我们后续无任何操作,Spring在进行数据填充的时候还是成功的从Properties文件中获取到了有效数据,这主要依靠的是PropertyPlaceHolderConfigure。
也有更简单的方法实现bean的动态注册,例如
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition("school1", beanDefinition)
其实动态注册bean非常简单,只需要获取到当前的IOC容器,然后调用registerBeanDefinition即可,至于获取当前IOC容器就可以使用Aware、BeanFactoryPostProcessor等方案。
# 3、动态修改bean信息(BeanFactoryPostProcessor)
上面的例子2就很好的说明了修改bean的信息,在postProcessBeanDefinitionRegistry方法中实现了注册操作,在postProcessBeanFactory实现了修改bean信息的操作,再例如下面这个例子。
此外可以通过类似于BeanNameAware的获取到bean的名称信息等
# 4、获取Spring IOC容器所有bean信息(BeanFactoryPostProcessor)
利用BeanFactoryPostProcessor的postProcessBeanFactory方法,获得当前的IOC容器,然后遍历即可。
提问:BeanFactoryPostProcessor 和 BeanPostProcessor的区别在哪里,一般各自有什么用途?
# 5、为兼容不同SpringBoot 版本,以实现选择性加载bean(条件注解)
使用了条件注解功能,获取当前运行的SpringBoot版本进行判断,类似于系统自带的条件注解,如下图
主要实现原理得看OnClassCondition类,是不是感觉和@Import类似呢?
拓展:是否清楚Spring 版本升级导致的WebMvcConfigurerAdapter不兼容问题,那么这个基于当前版本的条件注解功能就能很好的兼容WebMvcConfigurerAdapter问题了。
# 6、自定义工厂bean实例化(工厂Bean、AbstractFactoryBean)
继承了AbstractFactoryBean抽象类,createInstance是由afterPropertiesSet方法或者getObject方法调用,想获取具体的Student对象,则需要&区分工厂bean还是包装bean,具体看下面的图就应该很清楚了。
此外关于afterPropertiesSet方法是InitializingBean类唯一一个方法,一般用来实例化bean之后的自定义修正或者处理初始化后的其他事情。如需对Spring有更深入的理解,下面这个Spring Bean生命周期的流程图就必须清楚。
# 7、Spring MVC 输出所有的URL信息(Spring MVC)
关键思想是获取DispatcherServlet类中的handlerMappings数据,而其数据由主要是从SpringIOC容器中的HandlerMapping类bean,然后分别处理,如图中圈出来的只有RequestMappingHandlerMapping和BeanNameUrlHandlerMapping,这是不完整的,在Spring 3.2以前是DefaultAnnotationHandlerMapping类,但是已经被废弃了,这里就没有补充,大家在使用的时候需要知道自己Spring的版本。
# 8、通过Spring事件机制完成服务启动后的信息整理(类似于Dubbo的服务暴露机制)
使用了Spring 监听机制去监听ContextRefreshedEvent这类Spring IOC容器刷新完成之后的事件触发,本demo并没有做什么事情,但是结合具体业务可以做很多想做的事情,可以看看Dubbo的服务暴露接口继承关系,如下图。
本图来自本人简书文章截图:https://www.jianshu.com/p/507d51bf14ce如下图是dubbo的源码,充分利用了监听机制,监听ContextRefreshedEvent事件,最后调用export方法完成服务暴露操作。
# 9、通过Spring事件机制获取HTTP请求调用详情
在本地测试统计http服务调用统计情况,还是很方便的,依靠的是ServletRequestHandledEvent事件,默认这个事件是开启的,如果未开启该事件,就会导致事件监听无效。
# 10、服务启动后的初始化任务CommandLineRunner
这个就不再介绍了,相信大家也按照上述的原理分析和学习这个工具的使用。整体的思路也是类似的。
# 启动时执行一些额外的任务
启动时执行一些额外的任务
CommandLineRunner和ApplicationRunner在SpringApplication.run()之前,在所有的beans加载完成之后执行,用于执行一些初始化操作(如加载缓存、读取配置文件、创建线程池等)
CommandLineRunner和ApplicationRunner的功能差不多,不同的是run接口的参数,CommandLineRunner#run(String… args)、ApplicationRunner#run(ApplicationArguments args) ApplicationArguments包含更多的信息,其它功能都一样。
当执行多个初始化操作时可以通过@Order(value)来配置执行顺序,value是一个int的值,value的值越小越先执行。
@Order(1)
@Component
public class CacheInitRunner implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("load cache ..." + Arrays.asList(args));
}
}
@Order(2)
@Component
public class LoadResourceRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
System.out.println("create thread loop " + args);
}
}
@SpringBootApplication
public class SpringbootInitExampleApplication {
public static void main(String[] args) {
System.out.println("Application strating...");
SpringApplication.run(SpringbootInitExampleApplication.class, args);
System.out.println("Application finish...");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 整合cache
# 一、Spring Cache介绍
Spring 3.1引入了基于注解的缓存(cache)技术,它本质上是一个对缓存使用的抽象,通过在既有代码中添加少量它定义的各种注解,就能够达到缓存方法的效果。
Spring Cache接口为缓存的组件规范定义,包含缓存的各种操作集合,并提供了各种xxxCache的实现,如RedisCache,EhCacheCache,ConcurrentMapCache等;
项目整合Spring Cache后每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过,如果有就直接从缓存中获取结果,没有就调用方法并把结果放到缓存。
# 二、缓存注解介绍
对于缓存声明,Spring的缓存提供了一组java注解:
- @CacheConfig:设置类级别上共享的一些常见缓存设置。
- @Cacheable:触发缓存写入。
- @CacheEvict:触发缓存清除。
- @Caching 将多种缓存操作分组
- @CachePut:更新缓存(不会影响到方法的运行)。
# @CacheConfig
该注解是可以将缓存分类,它是类级别的注解方式。我们可以这么使用它。
这样的话,UserServiceImpl的所有缓存注解例如@Cacheable的value值就都为user。
@CacheConfig(cacheNames = "user")
@Service
public class UserServiceImpl implements UserService {}
2
3
# @Cacheable
一般用于查询操作,根据key查询缓存.
如果key不存在,查询db,并将结果更新到缓存中。
如果key存在,直接查询缓存中的数据。
//查询数据库后 数据添加到缓存
@Override
@Cacheable(cacheNames = "cacheManager", key = "'USER:'+#id", unless = "#result == null")
public User getUser(Integer id) {
return repository.getUser(id);
}
2
3
4
5
6
# @CachePut
@CachePut标注的方法在执行前不会去检查缓存中是否存在,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
//修改数据后更新缓存
@Override
@CachePut(cacheNames = "cacheManager", key = "'USER:'+#updateUser.id", unless = "#result == null")
public User updateUser(User updateUser) {
return repository.save(updateUser);
}
2
3
4
5
6
# @CacheEvict
根据key删除缓存中的数据。allEntries=true表示删除缓存中的所有数据。
//清除一条缓存,key为要清空的数据
@Override
@CacheEvict(cacheNames = "cacheManager", key = "'USER:'+#id")
public void deleteUser(Integer id) {
repository.deleteById(id);
}
2
3
4
5
6
# 三、Spring Boot+Cache实战
# 1、pom.xml引入jar包
<!-- 引入缓存 starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- 引入 redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2
3
4
5
6
7
8
9
10
# 2、启动类添加@EnableCaching注解
@EnableCaching注解是spring framework中的注解驱动的缓存管理功能,当你在配置类(@Configuration)上使用@EnableCaching注解时,会触发一个post processor,这会扫描每一个spring bean,查看是否已经存在注解对应的缓存。如果找到了,就会自动创建一个代理拦截方法调用,使用缓存的bean执行处理。
启动类部分代码如下:
@SpringBootApplication
@EnableCaching
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2
3
4
5
6
7
# 3、配置数据库和redis连接
application.properties部分配置如下:
#配置数据源信息
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.1.1:3306/test
spring.datasource.username=root
spring.datasource.password=1234
#配置jpa
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
# Redis服务器地址
spring.redis.host=192.168.1.1
# database
spring.redis.database = 1
# Redis服务器连接端口 使用默认端口6379可以省略配置
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=1234
# 连接池最大连接数(如果配置<=0,则没有限制 )
spring.redis.jedis.pool.max-active=8
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4、配置CacheManager
WebConfig.java部分配置如下:
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//缓存配置对象
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.entryTtl(Duration.ofMinutes(30L)) //设置缓存的默认超时时间:30分钟
.disableCachingNullValues() //如果是空值,不缓存
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer())) //设置key序列化器
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((valueSerializer()))); //设置value序列化器
return RedisCacheManager
.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
.cacheDefaults(redisCacheConfiguration).build();
}
2
3
4
5
6
7
8
9
10
11
12
# 5、使用缓存注解
UserServiceImpl.java中使用缓存注解示例如下:
//查询数据库后 数据添加到缓存
@Override
@Cacheable(cacheNames = "cacheManager", key = "'USER:'+#id", unless = "#result == null")
public User getUser(Integer id) {
return repository.getUser(id);
}
//清除一条缓存,key为要清空的数据
@Override
@CacheEvict(cacheNames = "cacheManager", key = "'USER:'+#id")
public void deleteUser(Integer id) {
repository.deleteById(id);
}
//修改数据后更新缓存
@Override
@CachePut(cacheNames = "cacheManager", key = "'USER:'+#updateUser.id", unless = "#result == null")
public User updateUser(User updateUser) {
return repository.save(updateUser);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 6、查看缓存效果
启动服务后,访问两次http://localhost:8090/getUser/2接口,从打印日志可以看到,第一次请求打印了sql说明查询了数据库,耗时960,而第二次直接查询的缓存耗时66,增加缓存后速度提升非常明显。
# Springboot2.0学习10 SpringBootAdmin管理分布式应用
来自Codecentric的Spring Boot Admin 是一个管理和监控SpringBoot应用的工具。Spring Boot应用程序可以使用Spring Boot Admin Client通过进行主动HTTP注册,或在服务端使用Spring Cloud(如Eureka,Consul)工具进行服务发现。