spring配置services接口异常_SpringBoot 零配置启动Web容器如何做到的?

一:传统spring mvc构建个web项目 早期,我们用spring mvc构建web项目时,都会经过这配置的3部曲
配置xml功能概述

web.xml

listener,servlet等配置信息

spring-mvc.xml

配置@Controller注解扫描,请求参数绑定的注解驱动,静态资源,JSON转换器,文件上传等等配置

applicationContext.xml

配置spring容器初始化,譬如包扫描,数据源,事务,AOP等等


简单分析下web.xml 内容
      org.springframework.web.context.ContextLoaderListener        contextConfigLocation    /WEB-INF/applicationContext.xml        app    org.springframework.web.servlet.DispatcherServlet          contextConfigLocation      /WEB-INF/spring-mvc.xml        1        app    /app/*  
以下内容基于 springframework 4.3.11.RELEASE 版本进行分析 我们来一起分析下 web.xml adf1450814c3c7907bc2084a903726ed.png

1)配置 ContextLoaderListener

ContextLoaderListener作为一个Listener会首先启动,该监听器会去加载applicationContext.xml,用于创建WebApplicationContext,并做为spring根容器 “Root WebApplicationContext” ,用来加载除了controller等web组件外的所有bean。

那么web容器启动时,是如何自动去装配Spring applicationContext.xml呢?

这是因为ContextLoaderListener实现了ServletContextListener这个接口,并实现了 contextInitialized,contextDestroyed 这两个方法,由于tomcat遵从servlet规范, 在web容器启动时,会自动执行 ContextLoaderListener 实现的contextInitialized 方法,那么spring在该方法调用initWebApplicationContext方法进行初始化 WebApplicationContext,解析applicationContext.xml文件,进行bean的装配。xml文件解析出来的applicationContext就是XmlWebApplicationContext 这个对象。

其调用链路图:

bd63818200035220a9eadd3df8e85154.png

2)配置DispatcherServlet DispatcherServlet将作为整个web请求的路由转发器。 DispatcherServlet继承了FrameworkServlet,FrameworkServlet继承了HttpServletBean,而HttpServletBean有个关键设计就是继承了HttpServlet并且重写了其init方法,也就是遵从了servlet规范,那么tomcat启动时,调用其重写的init 方法,进行一系列的初始化操作。init方法中有个非常关键的方法,就是initServletBean,该方法会创建个XmlWebApplicationContext容器,并且把ContextLoaderListener创建的容器作为其父容器。

其调用链路图:

505070f5dfde63296d607f05b89d3b9c.png

对于springmvc构造web项目,web.xml的起到关键作用,也就是ContextLoaderListener,DispatcherServlet这两个关键类,那么我们能通过编程式的方式来构造一个功能等价于 web.xml吗?答案是可以,也就是spring官网对spring mvc编程式的概述。详情可移步到 https://spring.io/projects/spring-framework,进行深入学习。

这里我贴下官网的关于web.xml编程式代码:

public class MyWebApplicationInitializer implements WebApplicationInitializer {    @Override    public void onStartup(ServletContext servletCxt) {        // Load Spring web application configuration        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();        ac.register(AppConfig.class);        ac.refresh();        // Create and register the DispatcherServlet        DispatcherServlet servlet = new DispatcherServlet(ac);        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);        registration.setLoadOnStartup(1);        registration.addMapping("/app/*");    }}

第一步:创建WebApplicationContext容器,当然若是用xml配置,则创建XmlWebApplicationContext对象,若基于AnnotationConfig注解的方式,则创建AnnotationConfigWebApplicationContext。

第二步 :注入注解配置类AppConfig.class,譬如包扫描 @ComponentScan,bean 的实例化等等。 第三步 :创建DispatcherServlet,将AnnotationConfigWebApplicationContext作为父容器注入进去,同时配置加载顺序,以及访问路径扫描映射等等。 最后: 剩下个关键节点问题,就是如何让servlet容器去加载我们编写的MyWebApplicationInitializer,进行一些列初始化?这就得聊到servlet3.0新特性。
二:servlet 3.0规范

详细内容        

https://jcp.org/aboutJava/communityprocess/mrel/jsr315/index.html

这里我摘取部分内容,关键介绍信息在 “8.2.4 Shared libraries / runtimes pluggability” 这一章节

d3dc8213604b06eed2c63d1fc4e8c09f.png

大致意思是这样的:

1、Servlet容器启动时,会进行自动扫描应用里每一个jar包中的类,看看哪个类实现了 ServletContainerInitializer 2、必须在  WEB-INF/services/javax.servlet.ServletContainerInitializer这文件内,定义一个实现 ServletContainerInitializer 全类路径 3、最后,也就是比较有趣的一个注解 @HandlesTypes ,该注解实现的效果是在Servlet 容器回调我们实现 ServletContainerInitializer接口的类时,会返回我们使用了 @HandlesTypes这个注解中定义的接口所有实现类

好了, servlet3.0给我们提供的新特性讲完了,那么springmvc是如何对其进行“精装”呢?回过头来,我们看下我们的MyWebApplicationInitializer,它实现了WebApplicationInitializer ,但这个接口是spring自定义的类,那servlet容器启动时,是如何进行回调到呢?其实,在springframework的spring-web模块中,也遵从servlet3.0规范,在 WEB-INF/services/javax.servlet.ServletContainerInitializer这文件内,定义了

org.springframework.web.SpringServletContainerInitializer

该 ServletContainerInitializer实现了servlet的ServletContainerInitializer接口,实现了onStartup方法,以下就是它详细信息:

@HandlesTypes(WebApplicationInitializer.class)public class SpringServletContainerInitializer implements ServletContainerInitializer {    public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException {    List initializers = new LinkedList();    if (webAppInitializerClasses != null) {      for (Class> waiClass : webAppInitializerClasses) {        if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&            WebApplicationInitializer.class.isAssignableFrom(waiClass)) {          try {            initializers.add((WebApplicationInitializer) waiClass.newInstance());          }          catch (Throwable ex) {            throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);          }        }      }    }    if (initializers.isEmpty()) {      servletContext.log("No Spring WebApplicationInitializer types detected on classpath");      return;    }    servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");    AnnotationAwareOrderComparator.sort(initializers);    // 循环调用所有实现了WebApplicationInitializer接口的实现类    for (WebApplicationInitializer initializer : initializers) {      initializer.onStartup(servletContext);    }  }}

还记得上文说到的servlet3.0规范中的 @HandlesTypes 注解吗?spring-web中的 SpringServletContainerInitializer类定义了该注解,并且指明了类型为WebApplicationInitializer ,也就是说所有实现了WebApplicationInitializer接口的类,都会在 servlet 容器启动时加载到,并在回调SpringServletContainerInitializer类的onStartup方法时,设置到webAppInitializerClasses 这个set 集合参数。所以,spring-web就顺利拿到了我们自定义的MyWebApplicationInitializer ,并且调用我们实现的onStartup。

至此,我们就可以采用编程式的方式进行无配置化去创建web项目。

三:spring-boot是如何无配置化启动web容器 以下内容基于spring-boot 1.5.7.RELEASE版本进行分析

那么从一开始的web.xml ,到现在编程式的方式去实现web.xml功能,以及知道了如何让servlet容器启动时回调处理我们实现的web.xml功能,但是spring-boot是直接使用springframework的spring-web模块吗?

其实不完全是的,spring-boot也遵从了servlet3.0规范,但没有直接使用spring-web中定义的WebApplicationInitializer接口,并去实现它,而是另辟蹊径,在项目启动,创建WebApplicationContext时,会调用到AbstractApplicationContext的 refresh方式中的一个比较重要的方法,那就是onRefresh方式(模板方法),该方法其实是调用EmbeddedWebApplicationContext类的onRefresh 具体实现,然后调用其createEmbeddedServletContainer方法,同时采用匿名方法实现了 ServletContextInitializer 接口,进行创建内嵌servlet 容器。

//创建内嵌的servlet容器private void createEmbeddedServletContainer() {  EmbeddedServletContainer localContainer = this.embeddedServletContainer;  ServletContext localServletContext = getServletContext();  if (localContainer == null && localServletContext == null) {    //根据当前使用容器类型,创建对应的工厂类,譬如tomcat,jetty    EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();    //进行实例化对应的servlet容器,譬如new个tomcat对象,    //getSelfInitializer()是实例化对应的ServletContextInitializer,以便servlet容器启动时,完成回调onStartup方法进行初始化servlet容器    this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());  }  else if (localServletContext != null) {    try {      getSelfInitializer().onStartup(localServletContext);    }    catch (ServletException ex) {      throw new ApplicationContextException("Cannot initialize servlet context", ex);    }  }  initPropertySources();}

 其中getSelfInitializer方法采用匿名内部类实现了ServletContextInitializer接口:

private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {  return new ServletContextInitializer() {    @Override    public void onStartup(ServletContext servletContext) throws ServletException {      selfInitialize(servletContext);    }  };}

getEmbeddedServletContainer方法开始对tomcat容器实例化:

public EmbeddedServletContainer getEmbeddedServletContainer(ServletContextInitializer... initializers) {  Tomcat tomcat = new Tomcat();  File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");  tomcat.setBaseDir(baseDir.getAbsolutePath());  Connector connector = new Connector(this.protocol);  tomcat.getService().addConnector(connector);  customizeConnector(connector);  tomcat.setConnector(connector);  tomcat.getHost().setAutoDeploy(false);  configureEngine(tomcat.getEngine());  for (Connector additionalConnector : this.additionalTomcatConnectors) {    tomcat.getService().addConnector(additionalConnector);  }  prepareContext(tomcat.getHost(), initializers);  return getTomcatEmbeddedServletContainer(tomcat);}

以下就是 spring-boot 启用内嵌 tomcat 容器代码调用链:

2f6ab38147da687b95aceefcde3f0ebc.png

最后,我们简单做个总结下,基于servlet3.0的新特性,以及springframework的 spring-web模块,实现了无配置化启动web容器;而spring boot采用内嵌web容器的方式,匿名方法实现了ServletContextInitializer接口达到了无配置化启动web容器的效果。

以上只是个人一些看法,若有不对的地方,欢迎各位看官多多指教!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot + Vue项目中配置应用监控,可以使用Spring Boot Actuator来提供监控功能,同时结合Prometheus和Grafana等工具来实现监控数据的可视化。具体步骤如下: 1. 在Spring Boot项目中引入Actuator依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> ``` 2. 配置Actuator的端点暴露: 在application.properties或application.yml文件中增加如下配置,以开放所有Actuator端点: ```yaml management: endpoints: web: exposure: include: "*" ``` 3. 启动Spring Boot应用,并访问Actuator的端点: 启动Spring Boot应用后,可以通过访问http://localhost:8080/actuator来查看所有Actuator的端点列表。例如,可以访问http://localhost:8080/actuator/health来查看应用的健康状况。 4. 配置Prometheus的数据采集: 在Spring Boot应用中集成Prometheus,可以使用Spring Boot Actuator提供的spring-boot-starter-actuator和micrometer-registry-prometheus依赖。具体步骤如下: 在pom.xml文件中增加如下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> <version>1.1.1</version> </dependency> ``` 在application.properties或application.yml文件中增加如下配置: ```yaml management: metrics: export: prometheus: enabled: true ``` 5. 配置Grafana的数据展示: 在Spring Boot应用中集成Grafana,可以使用docker-compose等工具来进行配置。具体步骤如下: 在docker-compose.yml文件中增加如下配置: ```yaml version: '3' services: prometheus: image: prom/prometheus:v2.26.0 ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml command: - '--config.file=/etc/prometheus/prometheus.yml' grafana: image: grafana/grafana:8.1.5 ports: - 3000:3000 volumes: - ./grafana.ini:/etc/grafana/grafana.ini - ./provisioning:/etc/grafana/provisioning - grafana-data:/var/lib/grafana volumes: grafana-data: ``` 其中,prometheus.yml文件中需要配置对应的监控数据采集地址。grafana.ini文件中需要配置对应的数据源和仪表盘等信息。provisioning目录下可以放置对应的数据源和仪表盘等信息。 6. 启动Prometheus和Grafana容器: 在命令行中执行如下命令,启动Prometheus和Grafana容器: ```bash docker-compose up -d ``` 7. 在Grafana中导入仪表盘: 在Grafana中可以通过导入仪表盘的方式来展示监控数据。可以在Grafana仪表盘库中搜索Prometheus相关的仪表盘,并进行导入和配置。具体步骤可以参考Grafana的官方文档。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值