在本文中,您将会了解到关于小伙子,别学SpringBoot了!的新资讯,同时我们还将为您解释直接学springboot的相关在本文中,我们将带你探索小伙子,别学SpringBoot了!的奥秘,分析直接
在本文中,您将会了解到关于小伙子,别学SpringBoot了!的新资讯,同时我们还将为您解释直接学springboot的相关在本文中,我们将带你探索小伙子,别学SpringBoot了!的奥秘,分析直接学springboot的特点,并给出一些关于spring boot 初试,springboot入门,springboot helloworld例子、SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?、Springboot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序,支持异常邮件通知、SpringBoot--应用SpringBoot完成基础项目搭建--引入SpringBoot依赖包实现简单的WEB项目的实用技巧。
本文目录一览:- 小伙子,别学SpringBoot了!(直接学springboot)
- spring boot 初试,springboot入门,springboot helloworld例子
- SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
- Springboot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序,支持异常邮件通知
- SpringBoot--应用SpringBoot完成基础项目搭建--引入SpringBoot依赖包实现简单的WEB项目
小伙子,别学SpringBoot了!(直接学springboot)

这些人也是皮:做 Java 开发,真敢小觑 Spring Boot?现在出去面试,无论大小公司 or 项目,都要跟你扯一扯 Spring Boot、微服务,如果啃不下来,很可能直接说拜拜!像快手、美团、网易这些一线大厂都在用它来做敏捷开发,你敢说不适合学?


技术更新迭代是相当快,如果一直都停留在表面会用阶段,迟早会被淘汰。为了能让大家更好更快地掌握 Springboot,在升职加薪、跳槽大厂的时候更有底气,推荐一个限时 0 元 开放的福利——《 SpringBoot 技术整合秘籍》。
课程由开课吧资深讲师,廖雪峰团队实力专家历时3个月打磨而成,从工作常见业务场景出发,讲解易懂,内容系统全面,让你在实际案例中轻松获取体系化技术,非常适合 SpringBoot 想要提升的朋友。
1. SpringBoot中使用jsp
2. SpringBoot中使用MyBatis1
3. SpringBoot中使用MyBatis2
4. SpringBoot对事务的支持
5. SpringBoot对日志的控制
6. SSDM需求分析
7. SSDM代码实现1
8. SSDM代码实现2
9. Redis高并发下的问题
10. 双重检测锁代码
11. 测试与总结
12. 双重检测的线程安全问题1
13. 双重检测的线程安全问题2
14. Dubbo与SpringBoot整合1
15. Dubbo与SpringBoot整合2
16. Dubbo与SpringBoot整合3
学完,你可以
-
梳理 SpringBoot 技术体系,查漏补缺 -
实战场景式学习,快速进阶 SpringBoot 开发 -
从需求分析到代码实现,真实生产环境下的项目设计 -
学完即所得,应用到工作中 -
助力面试中表现出色,取得更高水平的薪资
适合谁学?
-
拥有 0~1 年工作经验,对 SpringBoot 微服务开发进阶感到迷茫,正在 冲击大厂岗位 -
传统开发,正在向微服务转型; -
缺乏高并发、微服务开发场景和调优思路。
本文分享自微信公众号 - Java中文社群(javacn666)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
spring boot 初试,springboot入门,springboot helloworld例子
因为项目中使用了spring boot ,之前没接触过,所以写个helloworld玩玩看,当做springboot的一个入门例子。搜索 spring boot。得到官方地址:http://projects.spring.io/spring-boot/
本文脉络:
1.一句话介绍 spring boot是干啥的。
2.为啥要用spring boot.
3.用一个helloworld 打开springboot。
- springboot是干啥的,可以吃吗。
一句话:做过Javaweb开发的程序员,肯定都知道spring框架。springboot就是一个能够快速搭起spring项目的神器。
- 为啥要用spring boot
在没有springboot之前,假如我们需要使用spring来搭建一个项目,比如说搭建一个spring mvc的项目。我们需要干啥:
假如说我们是使用Maven 那么步骤是这样的:
1.新建一个maven 项目,package 方式为war.
2.加入相关依赖,什么spring-bean,spring-context,spring-webmvc等等,在web.xml中配置spring listener,配置spring.xml之类的。
3.在2完成之后,依赖都配置好了,编译通过了,就可以运行项目了。
假如我们没有使用maven,就是单纯的建个web项目,然后到处copy jar包到webapp lib 下面,将会更麻烦,各种容易漏jar包。
所以,上面的方式,对于新手而言,可能一个小时也跑不起一个项目。而有了springboot之后可能只需要十几分钟。
- 一个demo打开springboot。
1.新建maven项目,package方式为war. (Maven的介绍,安装与使用见 这里)
使用maven的时候,maven会从仓库中下载依赖的jar包,国外仓库可能会很慢,所以最好有个私服。一般公司都会有私服,私服搭建看这里。
2.在pom文件spring boot需要的依赖,配置文件如下:
<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</groupId>com.exampleartifactId>springbootdemoversion>0.0.1-SNAPSHOTpackaging>warbuild /> parent> >org.springframework.boot>spring-boot-starter-parent>1.3.4.RELEASEdependenciesdependency> >spring-boot-starter-web> project>
看这个pom文件,dependencies 节点下面只配置了一个 spring-boot-starter-web 依赖,加了一个parent配置,实在是太简洁了。
3.编写测试action,这个action跟你用其他方式创建spring项目里边的action个没啥区别,@controller 注解action类 ,@Requestmapping 映射请求地址。
唯一不同的就是多了一个Main方法和EnableAutoConfiguration注解,这个main方法使用来运行这个demo的。
package com.example; import org.springframework.boot.SpringApplication; org.springframework.boot.autoconfigure.EnableAutoConfiguration; org.springframework.stereotype.Controller; org.springframework.web.bind.annotation.RequestMapping; org.springframework.web.bind.annotation.ResponseBody; @Controller @EnableAutoConfiguration public class HelloController { @RequestMapping("/hello") @ResponseBody String home() { return "Hello,spring boot!"; } static void main(String[] args) throws Exception { SpringApplication.run(HelloController.,args); //运行之后在浏览器中访问:http://localhost:8080/hello } }
4.启动项目,只需要运行上面代码的main方法,运行成功,控制台输出如下:
. ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__,| / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.3.4.RELEASE) 2016-05-10 15:21:46.299 INFO 6668 --- [ main] com.example.HelloController : Starting HelloController on 0KE99GYM83Y5KGX with PID 6668 (D:\newspace\springbootdemo\target\classes started by Administrator in D:\newspace\springbootdemo) 2016-05-10 15:21:46.301 INFO 6668 --- [ main] com.example.HelloController : No active profile set,falling back to default profiles: default 2016-05-10 15:21:46.337 INFO 6668 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@18b8315: startup date [Tue May 10 15:21:46 CST 2016]; root of context hierarchy 2016-05-10 15:21:47.385 INFO 6668 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http) 2016-05-10 15:21:47.399 INFO 6668 --- [ main] o.apache.catalina.core.StandardService : Starting service Tomcat 2016-05-10 15:21:47.400 INFO 6668 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet Engine: Apache Tomcat/8.0.33 2016-05-10 15:21:47.482 INFO 6668 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2016-05-10 15:21:47.482 INFO 6668 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1148 ms 2016-05-10 15:21:47.714 INFO 6668 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet: 'dispatcherServlet' to [/] 2016-05-10 15:21:47.718 INFO 6668 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'characterEncodingFilter' to: [/*] 2016-05-10 15:21:47.718 INFO 6668 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'hiddenHttpMethodFilter' to: [/*] 2016-05-10 15:21:47.718 INFO 6668 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'httpPutFormContentFilter' to: [/*] 2016-05-10 15:21:47.718 INFO 6668 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: 'requestContextFilter' to: [/*] 2016-05-10 15:21:47.939 INFO 6668 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@18b8315: startup date [Tue May 10 15:21:46 CST 2016]; root of context hierarchy 2016-05-10 15:21:47.991 INFO 6668 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/hello]}" onto java.lang.String com.example.HelloController.home() 2016-05-10 15:21:47.993 INFO 6668 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String,java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest) 2016-05-10 15:21:47.994 INFO 6668 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) 2016-05-10 15:21:48.014 INFO 6668 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2016-05-10 15:21:48.014 INFO 6668 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2016-05-10 15:21:48.048 INFO 6668 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**/favicon.ico] onto handler of type [ org.springframework.web.servlet.resource.ResourceHttpRequestHandler] 2016-05-10 15:21:48.128 INFO 6668 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2016-05-10 15:21:48.187 INFO 6668 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2016-05-10 15:21:48.190 INFO 6668 --- [ main] com.example.HelloController : Started HelloController in 2.166 seconds (JVM running for 2.401) 2016-05-10 15:22:03.171 INFO 6668 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet' 2016-05-10 15:22:03.171 INFO 6668 --- [nio-8080-exec-1] o.s.web.servlet.dispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started 2016-05-10 15:22:03.181 INFO 6668 --- [nio-8080-exec-1] o.s.web.servlet.dispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 10 ms
这是啥,这是应用启动了啊。我们之前在没有使用springboot的时候,就是一般的spring项目启动应用需要干啥。之前是deploy到tomcat里边,然后启动tomcat。
这里我啥都没干,连web.xml都没写,直接运行这个Main方法就可以了。
5.在浏览器中访问,如下:
可以看到,这种方式创建一个spring 的web项目比较方便。如果是传统的spring项目,需要添加各种依赖,然后发布到tomcat运行看效果。
这里只需要很少的依赖引用配置,运行一个方法便可以直接在浏览器访问。正如springboot官网上说的 Just run。
[本例代码地址](http://git.oschina.net/dimixu/springbootdemo)
更多内容,欢迎关注公众号
推荐阅读:
[spring-data-rest的魔力 10分钟实现增删改查](https://www.cnblogs.com/demingblog/p/10599134.html)
[探索Java9 模块系统和反应流](https://www.cnblogs.com/demingblog/p/9104103.html)
[Java8系列- 如何用Java8 Stream API找到心仪的女朋友]
[Java8系列- 何用Java8 Stream API进行数据抽取与收集
[Lua脚本在redis分布式锁场景的运用](https://www.cnblogs.com/demingblog/p/9542124.html)
[redis单点、redis主从、redis集群cluster配置搭建与使用](https://www.cnblogs.com/demingblog/p/10295236.html)
[Netty开发redis客户端,Netty发送redis命令,netty解析redis消息](https://www.cnblogs.com/demingblog/p/9989699.html)
[spring如何启动的?这里结合spring源码描述了启动过程](https://www.cnblogs.com/demingblog/p/7443714.html)
[SpringMVC是怎么工作的,SpringMVC的工作原理](https://www.cnblogs.com/demingblog/p/9925268.html)
[spring 异常处理。结合spring源码分析400异常处理流程及解决方法](https://www.cnblogs.com/demingblog/p/9218271.html)
[Mybatis Mapper接口是如何找到实现类的-源码分析](https://www.cnblogs.com/demingblog/p/9544774.html)
[使用Netty实现HTTP服务器](https://www.cnblogs.com/demingblog/p/9970772.html)
[Netty实现心跳机制](https://www.cnblogs.com/demingblog/p/9957143.html)
[Netty系列](https://www.cnblogs.com/demingblog/p/9912099.html)
SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?
上一篇我们讲了SpringBoot中Tomcat的启动过程,本篇我们接着讲在SpringBoot中如何向Tomcat中添加Servlet、Filter、Listener
自定义Servlet、Filter、Listener
Spring容器中声明ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean
@Bean
public ServletRegistrationBean customServlet() {
return new ServletRegistrationBean(new CustomServlet(), "/custom");
}
private static class CustomServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("receive by custom servlet");
}
}
先自定义一个Servlet,重写service实现自己的业务逻辑,然后通过@Bean注解往Spring容器中注入一个ServletRegistrationBean类型的bean实例,并且实例化一个自定义的Servlet作为参数,这样就将自定义的Servlet加入Tomcat中了。
@ServletComponentScan注解和@WebServlet、@WebFilter以及@WebListener注解配合使用
@ServletComponentScan注解启用ImportServletComponentScanRegistrar类,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器所解析。ServletComponentScanRegistrar内部会解析@ServletComponentScan注解,然后会在Spring容器中注册ServletComponentRegisteringPostProcessor,是个BeanFactoryPostProcessor,会去解析扫描出来的类是不是有@WebServlet、@WebListener、@WebFilter这3种注解,有的话把这3种类型的类转换成ServletRegistrationBean、FilterRegistrationBean或者ServletListenerRegistrationBean,然后让Spring容器去解析:
@SpringBootApplication
@ServletComponentScan
public class EmbeddedServletApplication {
...
}
@WebServlet(urlPatterns = "/simple")
public class SimpleServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("receive by SimpleServlet");
}
}
在Spring容器中声明Servlet、Filter或者Listener
@Bean(name = "dispatcherServlet")
public DispatcherServlet myDispatcherServlet() {
return new DispatcherServlet();
}
我们发现往Tomcat中添加Servlet、Filter或者Listener还是挺容易的,大家还记得以前SpringMVC是怎么配置DispatcherServlet的吗?在web.xml中:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
和我们SpringBoot中配置Servlet相比是不是复杂很多,虽然SpringBoot中自定义Servlet很简单,但是其底层却不简单,下面我们来分析一下其原理
ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean
我们来看看这几个特殊的类:
ServletRegistrationBean
public class ServletRegistrationBean extends RegistrationBean {
//存放目标Servlet实例
private Servlet servlet;
//存放Servlet的urlMapping
private Set<String> urlMappings;
private boolean alwaysMapUrl;
private int loadOnStartup;
private MultipartConfigElement multipartConfig;
public ServletRegistrationBean(Servlet servlet, String... urlMappings) {
this(servlet, true, urlMappings);
}
public ServletRegistrationBean(Servlet servlet, boolean alwaysMapUrl, String... urlMappings) {
this.urlMappings = new LinkedHashSet();
this.alwaysMapUrl = true;
this.loadOnStartup = -1;
Assert.notNull(servlet, "Servlet must not be null");
Assert.notNull(urlMappings, "UrlMappings must not be null");
this.servlet = servlet;
this.alwaysMapUrl = alwaysMapUrl;
this.urlMappings.addAll(Arrays.asList(urlMappings));
}
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.servlet, "Servlet must not be null");
String name = this.getServletName();
if (!this.isEnabled()) {
logger.info("Servlet " + name + " was not registered (disabled)");
} else {
logger.info("Mapping servlet: ''" + name + "'' to " + this.urlMappings);
Dynamic added = servletContext.addServlet(name, this.servlet);
if (added == null) {
logger.info("Servlet " + name + " was not registered (possibly already registered?)");
} else {
this.configure(added);
}
}
}
//略
}
在我们例子中我们通过return new ServletRegistrationBean(new CustomServlet(), "/custom");就知道,ServletRegistrationBean里会存放目标Servlet实例和urlMapping,并且继承RegistrationBean这个类
FilterRegistrationBean
public class FilterRegistrationBean extends AbstractFilterRegistrationBean {
//存放目标Filter对象
private Filter filter;
public FilterRegistrationBean() {
super(new ServletRegistrationBean[0]);
}
public FilterRegistrationBean(Filter filter, ServletRegistrationBean... servletRegistrationBeans) {
super(servletRegistrationBeans);
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
public Filter getFilter() {
return this.filter;
}
public void setFilter(Filter filter) {
Assert.notNull(filter, "Filter must not be null");
this.filter = filter;
}
}
abstract class AbstractFilterRegistrationBean extends RegistrationBean {
private static final EnumSet<DispatcherType> ASYNC_DISPATCHER_TYPES;
private static final EnumSet<DispatcherType> NON_ASYNC_DISPATCHER_TYPES;
private static final String[] DEFAULT_URL_MAPPINGS;
private Set<ServletRegistrationBean> servletRegistrationBeans = new LinkedHashSet();
private Set<String> servletNames = new LinkedHashSet();
private Set<String> urlPatterns = new LinkedHashSet();
//重写onStartup方法
public void onStartup(ServletContext servletContext) throws ServletException {
Filter filter = this.getFilter();
Assert.notNull(filter, "Filter must not be null");
String name = this.getOrDeduceName(filter);
if (!this.isEnabled()) {
this.logger.info("Filter " + name + " was not registered (disabled)");
} else {
Dynamic added = servletContext.addFilter(name, filter);
if (added == null) {
this.logger.info("Filter " + name + " was not registered (possibly already registered?)");
} else {
this.configure(added);
}
}
}
//略...
}
我们看到FilterRegistrationBean 中也保存了目标Filter对象,并且继承了RegistrationBean
ServletListenerRegistrationBean
public class ServletListenerRegistrationBean<T extends EventListener> extends RegistrationBean {
//存放了目标listener
private T listener;
public ServletListenerRegistrationBean() {
}
public ServletListenerRegistrationBean(T listener) {
Assert.notNull(listener, "Listener must not be null");
Assert.isTrue(isSupportedType(listener), "Listener is not of a supported type");
this.listener = listener;
}
public void setListener(T listener) {
Assert.notNull(listener, "Listener must not be null");
Assert.isTrue(isSupportedType(listener), "Listener is not of a supported type");
this.listener = listener;
}
public void onStartup(ServletContext servletContext) throws ServletException {
if (!this.isEnabled()) {
logger.info("Listener " + this.listener + " was not registered (disabled)");
} else {
try {
servletContext.addListener(this.listener);
} catch (RuntimeException var3) {
throw new IllegalStateException("Failed to add listener ''" + this.listener + "'' to servlet context", var3);
}
}
}
//略...
}
ServletListenerRegistrationBean也是一样,那我们来看看RegistrationBean这个类
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {
...
}
public interface ServletContextInitializer {
void onStartup(ServletContext var1) throws ServletException;
}
我们发现RegistrationBean 实现了ServletContextInitializer这个接口,并且有一个onStartup方法,ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean都实现了onStartup方法。
ServletContextInitializer
是 Servlet 容器初始化的时候,提供的初始化接口。所以,Servlet 容器初始化会获取并触发所有的FilterRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例中
onStartup方法
那到底是何时触发这些类的onStartup方法呢?
当Tomcat容器启动时,会执行callInitializers
,然后获取所有的ServletContextInitializer,循环执行
onStartup
方法触发回调方法。那FilterRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean实例是何时加入到
Initializers集合的呢?这要回顾一下我们上一篇文章Tomcat的启动过程
Servlet容器的启动
大家可以看看我上一篇文章,我这里简单的复制一下代码
EmbeddedWebApplicationContext
1 @Override
2 protected void onRefresh() {
3 super.onRefresh();
4 try {
5 //核心方法:会获取嵌入式的Servlet容器工厂,并通过工厂来获取Servlet容器
6 createEmbeddedServletContainer();
7 }
8 catch (Throwable ex) {
9 throw new ApplicationContextException("Unable to start embedded container", ex);
10 }
11 }
12
13 private void createEmbeddedServletContainer() {
14 EmbeddedServletContainer localContainer = this.embeddedServletContainer;
15 ServletContext localServletContext = getServletContext();
16 if (localContainer == null && localServletContext == null) {
17 //先获取嵌入式Servlet容器工厂
18 EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
19 //根据容器工厂来获取对应的嵌入式Servlet容器
20 this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
21 }
22 else if (localServletContext != null) {
23 try {
24 getSelfInitializer().onStartup(localServletContext);
25 }
26 catch (ServletException ex) {
27 throw new ApplicationContextException("Cannot initialize servlet context",ex);
28 }
29 }
30 initPropertySources();
31 }
关键代码在第20行,先通过getSelfInitializer()获取到所有的Initializer,传入Servlet容器中,那核心就在getSelfInitializer()方法:
1 private ServletContextInitializer getSelfInitializer() {
2 //只是创建了一个ServletContextInitializer实例返回
3 //所以Servlet容器启动的时候,会调用这个对象的onStartup方法
4 return new ServletContextInitializer() {
5 public void onStartup(ServletContext servletContext) throws ServletException {
6 EmbeddedWebApplicationContext.this.selfInitialize(servletContext);
7 }
8 };
9 }
我们看到只是创建了一个ServletContextInitializer实例返回,所以Servlet容器启动的时候,会调用这个对象的onStartup方法,那我们来分析其onStartup中的逻辑,也就是selfInitialize方法,并将Servlet上下文对象传进去了
selfInitialize
1 private void selfInitialize(ServletContext servletContext) throws ServletException {
2 prepareWebApplicationContext(servletContext);
3 ConfigurableListableBeanFactory beanFactory = getBeanFactory();
4 ExistingWebApplicationScopes existingScopes = new ExistingWebApplicationScopes(beanFactory);
5 WebApplicationContextUtils.registerWebApplicationScopes(beanFactory,getServletContext());
6 existingScopes.restore();
7 WebApplicationContextUtils.registerEnvironmentBeans(beanFactory,getServletContext());
8 //这里便是获取所有的 ServletContextInitializer 实现类,会获取所有的注册组件
9 for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
10 //执行所有ServletContextInitializer的onStartup方法
11 beans.onStartup(servletContext);
12 }
13 }
关键代码在第9和第11行,先获取所有的ServletContextInitializer 实现类,然后遍历执行所有ServletContextInitializer的onStartup方法
获取所有的ServletContextInitializer
我们来看看getServletContextInitializerBeans方法
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
return new ServletContextInitializerBeans(getBeanFactory());
}
ServletContextInitializerBeans对象是对ServletContextInitializer
的一种包装:
1 public class ServletContextInitializerBeans extends AbstractCollection<ServletContextInitializer> {
2 private final MultiValueMap<Class<?>, ServletContextInitializer> initializers = new LinkedMultiValueMap();
3 //存放所有的ServletContextInitializer
4 private List<ServletContextInitializer> sortedList;
5
6 public ServletContextInitializerBeans(ListableBeanFactory beanFactory) {
7 //执行addServletContextInitializerBeans
8 this.addServletContextInitializerBeans(beanFactory);
9 //执行addAdaptableBeans
10 this.addAdaptableBeans(beanFactory);
11 List<ServletContextInitializer> sortedInitializers = new ArrayList();
12 Iterator var3 = this.initializers.entrySet().iterator();
13
14 while(var3.hasNext()) {
15 Entry<?, List<ServletContextInitializer>> entry = (Entry)var3.next();
16 AnnotationAwareOrderComparator.sort((List)entry.getValue());
17 sortedInitializers.addAll((Collection)entry.getValue());
18 }
19 this.sortedList = Collections.unmodifiableList(sortedInitializers);
20 }
21
22 private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
23 Iterator var2 = this.getOrderedBeansOfType(beanFactory, ServletContextInitializer.class).iterator();
24
25 while(var2.hasNext()) {
26 Entry<String, ServletContextInitializer> initializerBean = (Entry)var2.next();
27 this.addServletContextInitializerBean((String)initializerBean.getKey(), (ServletContextInitializer)initializerBean.getValue(), beanFactory);
28 }
29
30 }
31
32 private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
33 if (initializer instanceof ServletRegistrationBean) {
34 Servlet source = ((ServletRegistrationBean)initializer).getServlet();
35 this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
36 } else if (initializer instanceof FilterRegistrationBean) {
37 Filter source = ((FilterRegistrationBean)initializer).getFilter();
38 this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
39 } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
40 String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();
41 this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
42 } else if (initializer instanceof ServletListenerRegistrationBean) {
43 EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();
44 this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
45 } else {
46 this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);
47 }
48
49 }
50
51 private void addServletContextInitializerBean(Class<?> type, String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
52 this.initializers.add(type, initializer);
53 if (source != null) {
54 this.seen.add(source);
55 }
56
57 if (logger.isDebugEnabled()) {
58 String resourceDescription = this.getResourceDescription(beanName, beanFactory);
59 int order = this.getOrder(initializer);
60 logger.debug("Added existing " + type.getSimpleName() + " initializer bean ''" + beanName + "''; order=" + order + ", resource=" + resourceDescription);
61 }
62
63 }
64
65 private void addAdaptableBeans(ListableBeanFactory beanFactory) {
66 MultipartConfigElement multipartConfig = this.getMultipartConfig(beanFactory);
67 this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));
68 this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter(null));
69 Iterator var3 = ServletListenerRegistrationBean.getSupportedTypes().iterator();
70
71 while(var3.hasNext()) {
72 Class<?> listenerType = (Class)var3.next();
73 this.addAsRegistrationBean(beanFactory, EventListener.class, listenerType, new ServletContextInitializerBeans.ServletListenerRegistrationBeanAdapter(null));
74 }
75
76 }
77
78 public Iterator<ServletContextInitializer> iterator() {
79 //返回所有的ServletContextInitializer
80 return this.sortedList.iterator();
81 }
82
83 //略...
84 }
我们看到ServletContextInitializerBeans 中有一个存放所有ServletContextInitializer的集合sortedList,就是在其构造方法中获取所有的ServletContextInitializer,并放入sortedList集合中,那我们来看看其构造方法的逻辑,看到第8行先调用
addServletContextInitializerBeans方法:
1 private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {
2 //从Spring容器中获取所有ServletContextInitializer.class 类型的Bean
3 for (Entry<String, ServletContextInitializer> initializerBean : getOrderedBeansOfType(beanFactory, ServletContextInitializer.class)) {
4 //添加到具体的集合中
5 addServletContextInitializerBean(initializerBean.getKey(),initializerBean.getValue(), beanFactory);
6 }
7 }
我们看到先从Spring容器中获取所有ServletContextInitializer.class 类型的Bean,这里我们自定义的ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean就被获取到了,然后调用addServletContextInitializerBean方法:
1 private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {
2 //判断ServletRegistrationBean类型
3 if (initializer instanceof ServletRegistrationBean) {
4 Servlet source = ((ServletRegistrationBean)initializer).getServlet();
5 //将ServletRegistrationBean加入到集合中
6 this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);
7 //判断FilterRegistrationBean类型
8 } else if (initializer instanceof FilterRegistrationBean) {
9 Filter source = ((FilterRegistrationBean)initializer).getFilter();
10 //将ServletRegistrationBean加入到集合中
11 this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
12 } else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {
13 String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();
14 this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);
15 } else if (initializer instanceof ServletListenerRegistrationBean) {
16 EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();
17 this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);
18 } else {
19 this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);
20 }
21
22 }
23
24 private void addServletContextInitializerBean(Class<?> type, String beanName,
25 ServletContextInitializer initializer, ListableBeanFactory beanFactory, Object source) {
26 //加入到initializers中
27 this.initializers.add(type, initializer);
28 }
很明显,判断从Spring容器中获取的ServletContextInitializer类型,如ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean,并加入到initializers集合中去,我们再来看构造器中的另外一个方法addAdaptableBeans(beanFactory):
1 private void addAdaptableBeans(ListableBeanFactory beanFactory) {
2 //从beanFactory获取所有Servlet.class和Filter.class类型的Bean,并封装成RegistrationBean对象,加入到集合中
3 this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));
4 this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter(null));
5 }
6
7 private <T, B extends T> void addAsRegistrationBean(ListableBeanFactory beanFactory, Class<T> type, Class<B> beanType, ServletContextInitializerBeans.RegistrationBeanAdapter<T> adapter) {
8 //从Spring容器中获取所有的Servlet.class和Filter.class类型的Bean
9 List<Entry<String, B>> beans = this.getOrderedBeansOfType(beanFactory, beanType, this.seen);
10 Iterator var6 = beans.iterator();
11
12 while(var6.hasNext()) {
13 Entry<String, B> bean = (Entry)var6.next();
14 if (this.seen.add(bean.getValue())) {
15 int order = this.getOrder(bean.getValue());
16 String beanName = (String)bean.getKey();
17 //创建Servlet.class和Filter.class包装成RegistrationBean对象
18 RegistrationBean registration = adapter.createRegistrationBean(beanName, bean.getValue(), beans.size());
19 registration.setName(beanName);
20 registration.setOrder(order);
21 this.initializers.add(type, registration);
22 if (logger.isDebugEnabled()) {
23 logger.debug("Created " + type.getSimpleName() + " initializer for bean ''" + beanName + "''; order=" + order + ", resource=" + this.getResourceDescription(beanName, beanFactory));
24 }
25 }
26 }
27
28 }
我们看到先从beanFactory获取所有Servlet.class和Filter.class类型的Bean,然后通过ServletRegistrationBeanAdapter和FilterRegistrationBeanAdapter两个适配器将Servlet.class和Filter.class封装成RegistrationBean
private static class ServletRegistrationBeanAdapter implements ServletContextInitializerBeans.RegistrationBeanAdapter<Servlet> {
private final MultipartConfigElement multipartConfig;
ServletRegistrationBeanAdapter(MultipartConfigElement multipartConfig) {
this.multipartConfig = multipartConfig;
}
public RegistrationBean createRegistrationBean(String name, Servlet source, int totalNumberOfSourceBeans) {
String url = totalNumberOfSourceBeans == 1 ? "/" : "/" + name + "/";
if (name.equals("dispatcherServlet")) {
url = "/";
}
//还是将Servlet.class实例封装成ServletRegistrationBean对象
//这和我们自己创建ServletRegistrationBean对象是一模一样的
ServletRegistrationBean bean = new ServletRegistrationBean(source, new String[]{url});
bean.setMultipartConfig(this.multipartConfig);
return bean;
}
}
private static class FilterRegistrationBeanAdapter implements ServletContextInitializerBeans.RegistrationBeanAdapter<Filter> {
private FilterRegistrationBeanAdapter() {
}
public RegistrationBean createRegistrationBean(String name, Filter source, int totalNumberOfSourceBeans) {
//Filter.class实例封装成FilterRegistrationBean对象
return new FilterRegistrationBean(source, new ServletRegistrationBean[0]);
}
}
代码中注释很清楚了还是将Servlet.class实例封装成ServletRegistrationBean对象,将Filter.class实例封装成FilterRegistrationBean对象,这和我们自己定义ServletRegistrationBean对象是一模一样的,现在所有的ServletRegistrationBean、FilterRegistrationBean
Servlet.class、Filter.class都添加到List<ServletContextInitializer> sortedList这个集合中去了,接着就是遍历这个集合,执行其onStartup方法了
ServletContextInitializer的onStartup方法
ServletRegistrationBean
public class ServletRegistrationBean extends RegistrationBean {
private static final Log logger = LogFactory.getLog(ServletRegistrationBean.class);
private static final String[] DEFAULT_MAPPINGS = new String[]{"/*"};
private Servlet servlet;
public void onStartup(ServletContext servletContext) throws ServletException {
Assert.notNull(this.servlet, "Servlet must not be null");
String name = this.getServletName();
//调用ServletContext的addServlet
Dynamic added = servletContext.addServlet(name, this.servlet);
}
//略...
}
private javax.servlet.ServletRegistration.Dynamic addServlet(String servletName, String servletClass, Servlet servlet, Map<String, String> initParams) throws IllegalStateException {
if (servletName != null && !servletName.equals("")) {
if (!this.context.getState().equals(LifecycleState.STARTING_PREP)) {
throw new IllegalStateException(sm.getString("applicationContext.addServlet.ise", new Object[]{this.getContextPath()}));
} else {
Wrapper wrapper = (Wrapper)this.context.findChild(servletName);
if (wrapper == null) {
wrapper = this.context.createWrapper();
wrapper.setName(servletName);
this.context.addChild(wrapper);
} else if (wrapper.getName() != null && wrapper.getServletClass() != null) {
if (!wrapper.isOverridable()) {
return null;
}
wrapper.setOverridable(false);
}
if (servlet == null) {
wrapper.setServletClass(servletClass);
} else {
wrapper.setServletClass(servlet.getClass().getName());
wrapper.setServlet(servlet);
}
if (initParams != null) {
Iterator i$ = initParams.entrySet().iterator();
while(i$.hasNext()) {
Entry<String, String> initParam = (Entry)i$.next();
wrapper.addInitParameter((String)initParam.getKey(), (String)initParam.getValue());
}
}
return this.context.dynamicServletAdded(wrapper);
}
} else {
throw new IllegalArgumentException(sm.getString("applicationContext.invalidServletName", new Object[]{servletName}));
}
}
看到没,ServletRegistrationBean 中的 onStartup先获取Servlet的name,然后调用ServletContext的addServlet将Servlet加入到Tomcat中,这样我们就能发请求给这个Servlet了。
AbstractFilterRegistrationBean
public void onStartup(ServletContext servletContext) throws ServletException {
Filter filter = this.getFilter();
Assert.notNull(filter, "Filter must not be null");
String name = this.getOrDeduceName(filter);
//调用ServletContext的addFilter
Dynamic added = servletContext.addFilter(name, filter);
}
AbstractFilterRegistrationBean也是同样的原理,先获取目标Filter,然后调用ServletContext的addFilter将Filter加入到Tomcat中,这样Filter就能拦截我们请求了。
DispatcherServletAutoConfiguration

DispatcherServletConfiguration
1 @Configuration
2 @ConditionalOnWebApplication
3 // 先看下ClassPath下是否有DispatcherServlet.class字节码
4 // 我们引入了spring-boot-starter-web,同时引入了tomcat和SpringMvc,肯定会存在DispatcherServlet.class字节码
5 @ConditionalOnClass({DispatcherServlet.class})
6 // 这个配置类的执行要在EmbeddedServletContainerAutoConfiguration配置类生效之后执行
7 // 毕竟要等Tomcat启动后才能往其中注入DispatcherServlet
8 @AutoConfigureAfter({EmbeddedServletContainerAutoConfiguration.class})
9 protected static class DispatcherServletConfiguration {
10 public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
11 public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
12 @Autowired
13 private ServerProperties server;
14
15 @Autowired
16 private WebMvcProperties webMvcProperties;
17
18 @Autowired(required = false)
19 private MultipartConfigElement multipartConfig;
20
21 // Spring容器注册DispatcherServlet
22 @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
23 public DispatcherServlet dispatcherServlet() {
24 // 直接构造DispatcherServlet,并设置WebMvcProperties中的一些配置
25 DispatcherServlet dispatcherServlet = new DispatcherServlet();
26 dispatcherServlet.setDispatchOptionsRequest(this.webMvcProperties.isDispatchOptionsRequest());
27 dispatcherServlet.setDispatchTraceRequest(this.webMvcProperties.isDispatchTraceRequest());
28 dispatcherServlet.setThrowExceptionIfNoHandlerFound(this.webMvcProperties.isThrowExceptionIfNoHandlerFound());
29 return dispatcherServlet;
30 }
31
32 @Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
33 public ServletRegistrationBean dispatcherServletRegistration() {
34 // 直接使用DispatcherServlet和server配置中的servletPath路径构造ServletRegistrationBean
35 // ServletRegistrationBean实现了ServletContextInitializer接口,在onStartup方法中对应的Servlet注册到Servlet容器中
36 // 所以这里DispatcherServlet会被注册到Servlet容器中,对应的urlMapping为server.servletPath配置
37 ServletRegistrationBean registration = new ServletRegistrationBean(dispatcherServlet(), this.server.getServletMapping());
38 registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
39 if (this.multipartConfig != null) {
40 registration.setMultipartConfig(this.multipartConfig);
41 }
42 return registration;
43 }
44
45 @Bean // 构造文件上传相关的bean
46 @ConditionalOnBean(MultipartResolver.class)
47 @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
48 public MultipartResolver multipartResolver(MultipartResolver resolver) {
49 return resolver;
50 }
51
52 }
先看下ClassPath下是否有DispatcherServlet.class字节码, 我们引入了spring-boot-starter-web,同时引入了tomcat和SpringMvc,肯定会存在DispatcherServlet.class字节码,如果没有导入spring-boot-starter-web,则这个配置类将不会生效
然后往Spring容器中注册DispatcherServlet实例,接着又加入ServletRegistrationBean实例,并把DispatcherServlet实例作为参数,上面我们已经学过了ServletRegistrationBean的逻辑,在Tomcat启动的时候,会获取所有的ServletRegistrationBean,并执行其中的onstartup方法,将DispatcherServlet注册到Servlet容器中,这样就类似原来的web.xml中配置的dispatcherServlet。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
所以只要导入了spring-boot-starter-web这个starter,SpringBoot就有了Tomcat容器,并且往Tomcat容器中注册了DispatcherServlet对象,这样就能接收到我们的请求了
原文出处:https://www.cnblogs.com/java-chen-hao/p/11842611.html
Springboot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序,支持异常邮件通知
1. Spring Boot Admin 是什么
Spring Boot Admin 是由 codecentric 组织开发的开源项目,使用 Spring Boot Admin 可以管理和监控你的 Spring Boot 项目。它分为客户端和服务端两部分,客户端添加到你的 Spring Boot 应用增加暴漏相关信息的 HTTP 接口,然后注册到 Spring Boot Admin 服务端,这一步骤可以直接向服务端注册,也可以通过 Eureka 或者 Consul 进行注册。而 Spring Boot Admin Server 通过 Vue.js 程序监控信息进行可视化呈现。并且支持多种事件通知操作。
2. Spring Boot Admin 服务端
Spring Boot Admin 服务端是基于 Spring Boot 项目的,如何创建一个 Spring Boot 项目这里不提,你可以参考之前文章或者从 https://start.spring.io/ 直接获得一个 Spring Boot 项目。
2.1. 添加依赖
只需要添加 web 依赖和 Spring-boot-admin-starter-server 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
2.2. 启动配置
为了和下面的客户端端口不冲突,先修改端口号为 9090。
server:
port: 9090
添加 @EnableAdminServer
注解启用 Spring Boot Admin Server 功能。
@EnableAdminServer
@SpringBootApplication
public class SpringbootAdminServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAdminServerApplication.class,args);
}
}
服务端已经配置完成,启动项目进行访问就可以看到 Spring Boot Admin Server 的页面了。
3. Spring Boot Admin 客户端
创建 Spring Boot 项目依旧不提,这里只需要添加 Spring Boot Admin 客户端需要的依赖,在项目启动时就会增加相关的获取信息的 API 接口。然后在 Spring Boot 配置文件中配置 Spring Boot Admin 服务端,就可以进行监控了。
3.1 客户端依赖
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- Lombok 工具,与 admin client 无关 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
3.2 客户端配置
客户端配置主要为了让客户端可以成功向服务端注册,所以需要配置客户端所在应用相关信息以及 Spring Boot Admin Server 服务端的 url。
server:
port: 8080
spring:
application:
# 应用名称
name: sjfx-api-search
jmx:
enabled: true
boot:
admin:
client:
# 服务端 url
url: http://127.0.0.1:9090
instance:
# 客户端实例 url
service-url: http://127.0.0.1:8080
prefer-ip: true
# 客户端实例名称
name: sjfx-api-search
management:
endpoints:
web:
exposure:
# 暴漏的接口 - 所有接口
include: "*"
配置中的 include: "*"
公开了所有的端口,对于生产环境,应该自信的选择要公开的接口。
Spring Boot Admin 可以获取应用中的定时任务,所以在代码中增加一个定时任务计划,每 20 秒输出一次当前时间,日志级别为 INFO
,用于下面的定时任务和日志监控测试。
@Slf4j
@SpringBootApplication
@EnableScheduling
public class SpringbootAdminClientApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootAdminClientApplication.class,args);
}
@Scheduled(cron = "0/20 * * * * ?")
public void run20s() {
log.info("定时任务:{}",LocalDateTime.Now());
}
}
3.3. 客户端运行
启动客户端会暴漏相关的运行状态接口,并且自动向配置的服务端发送注册信息。
下面是客户端的启动日志:
2019-12-21 22:45:32.878 INFO 13204 --- [ main] n.c.b.SpringbootAdminClientApplication : Starting SpringbootAdminClientApplication on DESKTOP-8SCFV4M with PID 13204 (D:\IdeaProjectMy\springboot-git\springboot-admin\springboot-admin-client\target\classes started by 83981 in D:\IdeaProjectMy\springboot-git\springboot-admin)
2019-12-21 22:45:32.881 INFO 13204 --- [ main] n.c.b.SpringbootAdminClientApplication : No active profile set,falling back to default profiles: default
2019-12-21 22:45:33.627 INFO 13204 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2019-12-21 22:45:33.634 INFO 13204 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2019-12-21 22:45:33.634 INFO 13204 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.29]
2019-12-21 22:45:33.706 INFO 13204 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2019-12-21 22:45:33.706 INFO 13204 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 797 ms
2019-12-21 22:45:33.850 INFO 13204 --- [ main] o.s.b.a.e.web.ServletEndpointRegistrar : Registered '/actuator/jolokia' to jolokia-actuator-endpoint
2019-12-21 22:45:33.954 INFO 13204 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor'
2019-12-21 22:45:34.089 INFO 13204 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService
2019-12-21 22:45:34.117 INFO 13204 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler'
2019-12-21 22:45:34.120 INFO 13204 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 15 endpoint(s) beneath base path '/actuator'
2019-12-21 22:45:34.162 INFO 13204 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-12-21 22:45:34.163 INFO 13204 --- [ main] n.c.b.SpringbootAdminClientApplication : Started SpringbootAdminClientApplication in 1.563 seconds (JVM running for 2.131)
2019-12-21 22:45:34.271 INFO 13204 --- [gistrationTask1] d.c.b.a.c.r.ApplicationRegistrator : Application registered itself as 6bcf19a6bf8c
2019-12-21 22:45:34.293 INFO 13204 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring dispatcherServlet 'dispatcherServlet'
2019-12-21 22:45:34.294 INFO 13204 --- [nio-8080-exec-1] o.s.web.servlet.dispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-12-21 22:45:34.300 INFO 13204 --- [nio-8080-exec-1] o.s.web.servlet.dispatcherServlet : Completed initialization in 6 ms
从启动日志里的 Exposing 15 endpoint(s) beneath base path '/actuator'
这段,可以看到暴漏了 15 个 /actuator
的 API 接口,可以直接访问查看响应结果。
从日志 Application registered itself as 6bcf19a6bf8c
可以看到客户端已经注册成功了。再看服务端可以看到注册上来的一个应用实例。
4. Spring Boot Admin 功能
点击监控页面上的在线的应用实例,可以跳转到应用实例详细的监控管理页面,也就是 Vue.js 实现的 web 展示。
Spring Boot Admin Server 可以监控的功能很多,使用起来没有难度,下面描述下可以监测的部分内容:
- 应用运行状态,如时间、垃圾回收次数,线程数量,内存使用走势。
- 应用性能监测,通过选择 JVM 或者 Tomcat 参数,查看当前数值。
- 应用环境监测,查看系统环境变量,应用配置参数,自动配置参数。
- 应用 bean 管理,查看 Spring Bean ,并且可以查看是否单例。
- 应用计划任务,查看应用的计划任务列表。
- 应用日志管理,动态更改日志级别,查看日志。
- 应用 JVM 管理,查看当前线程运行情况,dump 内存堆栈信息。
- 应用映射管理,查看应用接口调用方法、返回类型、处理类等信息。
上面提到的日志管理,可以动态的更改日志级别,以及查看日志。默认配置下是只可以动态更改日志级别的,如果要在线查看日志,就需要手动配置日志路径了。
客户端上可以像下面这样配置日志路径以及日志高亮。
# 配置文件:application.yml
logging:
file:
name: boot.log
pattern:
# 日志高亮
file: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx'
下面是在 Spring Boot Admin 监测页面上查看的客户端应用日志。
5. Spring Boot Admin 进阶
5.1. 邮件通知
Spring Boot Admin Server 支持常见的通知方式,比如邮件通知、电报通知、PagerDuty 通知等,下面将会演示常见的通知方式 - 邮件通知,最后也会演示如何通过监听时间进下设置自定义通知方式。
Spring Boot Admin Server 的邮件通知通过 Thymeleaf 模板发送 HTML 格式的电子邮件。因此,想要使用邮件通知首先要引入 Thymeleaf 依赖以及 spring-boot-starter-mail
依赖,并配置邮件发送者信息和接受者信息。
1. 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- Thymeleaf 模版,用于发送模版邮件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2. 配置邮件
主要设置发送者信息和接收者信息。
spring:
boot:
admin:
notify:
mail:
# 逗号分隔的邮件收件人列表
to: xxxx@126.com
# 开启邮箱通知
enabled: true
# 不需要发送通知的状态:从状态A:到状态B
ignore-changes: {"UNKNowN:UP"}
# 逗号分隔的抄送收件人列表
cc: xxxx@126.com
# 发件人
from: Spring Boot Admin<xxxx@126.com>
# 邮件发送者信息
mail:
host: smtp.126.com
port: 25
username: xxxx@126.com
default-encoding: utf-8
password: xxxx
如果想了解更多关于 Spring Boot 邮件发送信息,可以参考 Spring Boot 系列文章第十三篇。
配置好邮件通知之后,重启服务端和客户端,等客户端注册到服务端之后直接终止客户端的运行,稍等片刻就可以在配置的通知接收邮箱里收到客户端实例下线通知了。
邮件通知使用的模板存放在 server 依赖的 classpath:/meta-inf/spring-boot-admin-server/mail/status-changed.html 路径,如果想要自定义模板内容。可以拷贝这个文件放到自己的 templates 目录下,修改成自己想要的效果,然后在配置中指定自定义模板路径。
spring:
boot:
admin:
notify:
mail:
# 自定义邮件模版
template: classpath:/templates/notify.html
5.2 自定义通知
自定义通知只需要自己实现 Spring Boot Admin Server 提供的监听通知类即可,下面会演示如何在实例状态改变时输出实例相关信息。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import de.codecentric.boot.admin.server.domain.entities.Instance;
import de.codecentric.boot.admin.server.domain.entities.InstanceRepository;
import de.codecentric.boot.admin.server.domain.events.InstanceEvent;
import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent;
import de.codecentric.boot.admin.server.notify.AbstractEventNotifier;
import de.codecentric.boot.admin.server.notify.LoggingNotifier;
import reactor.core.publisher.Mono;
@Component
public class CustomNotifier extends AbstractEventNotifier {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingNotifier.class);
public CustomNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono<Void> doNotify(InstanceEvent event,Instance instance) {
return Mono.fromrunnable(() -> {
if (event instanceof InstanceStatusChangedEvent) {
LOGGER.info("Instance {} ({}) is {}",instance.getRegistration().getName(),event.getInstance(),((InstanceStatusChangedEvent)event).getStatusInfo().getStatus());
} else {
LOGGER.info("Instance {} ({}) {}",event.getType());
}
});
}
}
5.2. 访问限制
上面提到过,因为客户端增加了暴漏运行信息的相关接口,所以在生产环境中使用存在风险,而服务端没有访问限制,谁的可以访问也是不合理的。
下面将会为客户端和服务端分别增加访问限制,客户端主要是限制敏感接口的访问权限,服务端则是全局的访问限制。这些访问限制都通过 spring 安全框架 security 来实现,所以首先要为客户端和服务端都增加 maven 依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
1. 服务端
在引入安全框架依赖之后,需要配置访问控制,比如静态资源不需要限制,登录登出页面指定等。
import java.util.UUID;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.csrf.CookieCsrftokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import de.codecentric.boot.admin.server.config.AdminServerProperties;
import io.netty.handler.codec.http.HttpMethod;
@Configuration(proxyBeanMethods = false)
public class SecuritySecureConfig extends WebSecurityConfigurerAdapter {
private final AdminServerProperties adminServer;
public SecuritySecureConfig(AdminServerProperties adminServer) {
this.adminServer = adminServer;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter("redirectTo");
successHandler.setDefaultTargetUrl(this.adminServer.path("/"));
http.authorizeRequests()
.antMatchers(this.adminServer.path("/assets/**")).permitAll()
.antMatchers(this.adminServer.path("/login")).permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage(this.adminServer.path("/login")).successHandler(successHandler).and()
.logout().logoutUrl(this.adminServer.path("/logout")).and()
.httpBasic().and()
.csrf()
.csrftokenRepository(CookieCsrftokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(
new AntPathRequestMatcher(this.adminServer.path("/instances"),HttpMethod.POST.toString()),new AntPathRequestMatcher(this.adminServer.path("/instances/*"),HttpMethod.DELETE.toString()),new AntPathRequestMatcher(this.adminServer.path("/actuator/**"))
)
.and()
.rememberMe().key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600);
// @formatter:on
}
// 代码配置用户名和密码的方式
// required to provide UserDetailsService for "remember functionality"
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().withUser("user").password("{noop}password").roles("USER");
// }
}
在 application.yml 配置文件中配置用户名和密码。
spring:
security:
user:
name: user
password: 123
重启服务端,再次访问就需要用户名和密码进行登录了。
2. 客户端
客户端在引入安全框架之后,也需要配置访问权限,主要是配置哪些路径可以访问,哪些路径访问需要登录限制,默认所有接口都需要登录限制。
同样的,客户端应用也需要在配置中配置客户端应用对于敏感接口的登录用户和密码,同时需要配置 Spring Boot Admin Server 的访问用户和密码,还要把自身的用户和密码注册时告诉服务端,不然服务端不能获取到监测数据。
spring:
security:
user:
# 客户端敏感接口用户和密码
name: client
password: 123
application:
# 应用名称
name: sjfx-api-search
jmx:
enabled: true
boot:
admin:
client:
# 服务端 url
url: http://127.0.0.1:9090
instance:
# 客户端实例 url
service-url: http://127.0.0.1:8080
prefer-ip: true
# 客户端实例名称
name: sjfx-api-search
Metadata:
# 客户端自身的用户和密码告诉服务端
user.name: client
user.password: 123
# 服务端用户名密码
username: user
password: 123
客户端敏感接口访问测试。
到这里,客户端的敏感接口访问需要登录,服务端的管理页面也需要登录,客户端和服务端的访问控制已经完成了。
文中代码已经上传到:github.com/niumoo/springboot/tree/master/springboot-admin
参考资料:
https://github.com/codecentric/spring-boot-admin
https://codecentric.github.io/spring-boot-admin/current/
<完>
个人网站:https://www.codingme.net
如果你喜欢这篇文章,可以关注公众号,一起成长。
关注公众号回复资源可以没有套路的获取全网最火的的 Java 核心知识整理&面试核心资料。
SpringBoot--应用SpringBoot完成基础项目搭建--引入SpringBoot依赖包实现简单的WEB项目
新建项目
1.新建project
2.maven项目,jdk1.8,quickstart
3.包名
4.等待maven下载依赖完成
标注项目文件夹类型
1. java
2. source
![图片上
运行
1.怎么运行
打开src/main/java/com/miaoshaproject/App.java
,右键运行
2.运行成功
引入WEB项目要用的SpringBoot依赖包
spring官网文档
https://spring.io/guides/gs/r...
修改pom.xml文件,重新刷新maven,加载jar包
- 蓝色为要引入的jar包配置
- 红色为重新刷新maven,加载jar包
运行WEB项目
修改App.java文件
com.miaoshaproject.App
package com.miaoshaproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Hello world!
*
*/
@EnableAutoConfiguration
public class App
{
public static void main( String[] args ) {
System.out.println( "Hello World!" );
SpringApplication.run(App.class,args);
}
}
http://localhost:8080/
运行时,返回点内容
修改App.java文件
com.miaoshaproject.App
package com.miaoshaproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Hello world!
*
*/
@EnableAutoConfiguration
@RestController
public class App
{
@RequestMapping("/")
public String home(){
return "hello world";
}
public static void main( String[] args ) {
System.out.println( "Hello World!" );
SpringApplication.run(App.class,args);
}
}
http://localhost:8080/
端口占用情况和解决办法
今天关于小伙子,别学SpringBoot了!和直接学springboot的讲解已经结束,谢谢您的阅读,如果想了解更多关于spring boot 初试,springboot入门,springboot helloworld例子、SpringBoot 源码解析 (七)----- Spring Boot的核心能力 - SpringBoot如何实现SpringMvc的?、Springboot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序,支持异常邮件通知、SpringBoot--应用SpringBoot完成基础项目搭建--引入SpringBoot依赖包实现简单的WEB项目的相关知识,请在本站搜索。
本文标签: