【小家Spring】Spring注解驱动开发---Servlet 3.0整合Spring MVC(不使用web.xml部署描述符,使用ServletContainerInitializer)(中)

简介: 【小家Spring】Spring注解驱动开发---Servlet 3.0整合Spring MVC(不使用web.xml部署描述符,使用ServletContainerInitializer)(中)

整合Spring MVC



整合Spring MVC是重中之重。前面已经说过ServletContainerInitializer了,相信大家能够想到Spring是怎么做的了吧?直接参照Spring官方文档先看看:


image.png

链接如下:https://docs.spring.io/spring/docs/5.1.5.RELEASE/spring-framework-reference/web.html#mvc-servlet


我们看看Spring-web包jar包内:


image.png


很显然的发现,Spring MVC也是通过这种方式和Servlet容器进行整合的。web容器在启动的时候,就会自动去加载org.springframework.web.SpringServletContainerInitializer这个类。源码如下(它关心的是WebApplicationInitializer的子类们):

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
  @Override
  public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
      throws ServletException {
    List<WebApplicationInitializer> initializers = new LinkedList<>();
    if (webAppInitializerClasses != null) {
      for (Class<?> waiClass : webAppInitializerClasses) {
        // Be defensive: Some servlet containers provide us with invalid classes,
        // no matter what @HandlesTypes says...
        if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
            WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
          try {
            initializers.add((WebApplicationInitializer)
                ReflectionUtils.accessibleConstructor(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);
    for (WebApplicationInitializer initializer : initializers) {
      initializer.onStartup(servletContext);
    }
  }
}



步骤分析:


  1. spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;
  2. 为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类

image.png

image.png


1)、AbstractContextLoaderInitializer:创建根容器;createRootApplicationContext();

2)、AbstractDispatcherServletInitializer:

创建一个web的ioc容器;createServletApplicationContext();

创建了DispatcherServlet;createDispatcherServlet();

将创建的DispatcherServlet添加到ServletContext中;

-----------抽象方法:getServletMappings();

3)、AbstractAnnotationConfigDispatcherServletInitializer:注解方式配置的DispatcherServlet初始化器(本文重点)

创建根容器:createRootApplicationContext()

------------getRootConfigClasses();传入一个配置类(用户自定义)

创建web的ioc容器: createServletApplicationContext();

------------获取配置类;getServletConfigClasses();

说明:虽然父类只有两个abstract抽象方法要求子类必须实现。但是父类的设计都是可以扩展的,若你想定制化自己的需求,都是可以通过复写父类的protected的方式扩展的

比如:你想定制化自己的DispatcherServlet(父类默认值是单纯的new一下),那么你就可以通过复写createDispatcherServlet()去定制~


Spring容器推荐使用父子容器的概念:

image.png从上面分析得知,我们要使用注解驱动的话。只需要我们自己实现AbstractAnnotationConfigDispatcherServletInitializer这个抽象类就行了,这样web容器启动的时候就能处理我们实现的这个类的内容。示例如下(采用父子容器):


父子容器的配置类:

// 备注:此处@ControllerAdvice、RestControllerAdvice 这个注解不要忘了,属于Controller层处理全局异常的,应该交给web去扫描
@ComponentScan(value = "com.fsx", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})
})
@Configuration //最好标注上,本人亲测若不标准,可能扫描不生效
public class RootConfig {
}
// 此处记得排除掉@Controller和@ControllerAdvice、@RestControllerAdvice
@ComponentScan(value = "com.fsx", useDefaultFilters = false,
        includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, ControllerAdvice.class, RestControllerAdvice.class})}
)
@Configuration //最好标注上,本人亲测若不标准,可能扫描不生效
public class AppConfig {
}


@RestControllerAdvice是Spring4.3后提供的注解。@ControllerAdvice是Spring3.2提供的

它俩的区别就像@Controller和@RestController的区别。(也就是说@RestControllerAdvice``可以省略@ResponseBody`不用写了~~~)


useDefaultFilters默认值为true,表示默认情况下@Component、@Repository、@Service、@Controller都会扫描

useDefaultFilters=false加上includeFilters我们就可以只扫描指定的组件了,比如Spring MVC的web子容器只扫描Controller组件

excludeFilters的时候,就不需要去设置useDefaultFilters=false,这样子我们直接排除掉即可哟~


特别注意:useDefaultFilters的正确使用,不要造成重复扫描。否则很有可能造成事务不生效,并且你还非常不好定位这个错误。


然后我们自己来实现AbstractAnnotationConfigDispatcherServletInitializer一个初始化实体类:

/**
 * 自己实现 基于注解驱动的ServletInitializer来初始化DispatcherServlet
 */
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 根容器的配置类;(Spring的配置文件)   父容器;
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }
    /**
     * web容器的配置类(SpringMVC配置文件)  子容器;
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[]{AppConfig.class};
    }
    //获取DispatcherServlet的映射信息
    // 注意: /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
    //  /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
    // 若你想定制化父类的一些默认行为  这里都是可以复写父类的protected方法的~~~~
    // Spring MVC也推荐你这么干~
    @Override
    protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
        DispatcherServlet dispatcherServlet = (DispatcherServlet) super.createDispatcherServlet(servletAppContext);
        // dispatcherServlet.setDetectAllHandlerAdapters(false); 
        return dispatcherServlet;
    }
}


我们写个测试类试试:


@Controller
public class HelloController {
    @Autowired
    HelloService helloService;
    @ResponseBody
    @RequestMapping("/hello")
    public String hello() {
        System.out.println(helloService); //com.fsx.service.HelloServiceImpl@512663b0
        return "hello...";
    }
}


这样我们就可以正常访问controller的请求了。大功告成~


web容器中的Spring的应用 一启动就会 加载感兴趣的WebApplicationInitializer接口的下的所有组件,并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)。


特别注意的是:


按照上面的配置,我偶然的发现了,RootConfig仍然还是去扫描了我的controller,导致我的controller被扫描了两次,怎么回事呢???


找了好久,终于找到原因了,并不是@ComponentScan或者excludeFilters的问题,而是因为咱们在执行RootConfig的时候,虽然不去扫描Controller注解了,但是它会扫描AppConfig.java这个配置类,从而间接的又去扫描了@Controller了,因此最正确的做法应该如下:

@ComponentScan(value = "com.fsx", excludeFilters = {
        @Filter(type = FilterType.ANNOTATION, classes = {Controller.class}),
        //排除掉web容器的配置文件,否则会重复扫描
        @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {AppConfig.class})
})
@Configuration
public class RootConfig {
}


这样子,我们的Controller就只会被扫描一次了,容器也就非常的干净了,强烈建议这么干。


当然,如果你还是当初xml的方式来做的,分别是两个配置文件下,只要不自己import之类,就不会出现此种问题~


容器完全隔离后的好处是非常明显的,比如我们的web组件,就放在AppConig里,其它的放在Root里,不要什么都往RootConfig里面塞,比如如下:

//web子容器里注册一个Child
public class AppConfig {
    @Bean
    public Child child() {
        return new Child();
    }
}
//父容器里注册一个Parent
public class RootConfig {
    @Bean
    public Parent parent() {
        return new Parent();
    }
}


然后我们会发现在Controller层注入这两个Bean是正常的:


image.png

但是在Service层注入,启动的时候就会报错了


image.png


报错如下:

image.png这里面说个结论:

1、父子容器的关系就行内部类的关系一样。子容器能得到父容器的Bean,但是父容器得不到子容器的Bean

2、父子容器中,属性值都不是互通的。@Value注入的时候需要注意一下子~

定制Spring MVC


之前我们使用xml文件的时候,我们可以配置Spring MVC等相关选项。

比如视图解析器、视图映射、静态资源映射、拦截器。。。


首先:在配置文件里加上注解@EnableWebMvc:开启SpringMVC定制配置功能;

其次: 实现WebMvcConfigurer接口。通过这个接口我们可以发现,里面有很多方法,但大多数情况下我们并不需要配置这么多项,因此Spring MVC也考虑到了这一点,提供给我们一个WebMvcConfigurerAdapter来extends就行,Adapter都是空实现~,这样我们需要配置什么,复写对应方法就行

image.png


从上可以看出,如果你的项目是构建在Spring5.0(基于java8)以上的,直接实现接口即可。不用再继承此Adapter了~


@EnableWebMvc
@Configuration //一定要说明这个文件是个配置文件
public class WebMvcConfig implements WebMvcConfigurer {
    //视图解析器
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默认所有的页面都从 /WEB-INF/ xxx .jsp
        //registry.jsp();
        registry.jsp("/WEB-INF/views/", ".jsp");
    }
    // 开启静态资源的请求转发到默认servlet上,不配置页面报错404,(默认servlet不是DispatcherServlet!理解的)
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
    //自定义添加拦截器=========这个比较常用
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
    }
}


这样我们就可以通过此配置文件,个性化定制我们的Spring MVC了。


Spring MVC中WebMvcConfigurerAdapter、WebMvcConfigurationSupport与WebMvcConfigurer


最后需要多一句嘴,我们还能看到还有一个类:WebMvcConfigurationSupport。小伙伴们查看很多文章,但此处我只推荐一个老铁的文章,说到了点上:WebMvcConfigurationSupport与WebMvcConfigurer的关系


结论可以摆在此处:最佳实践还是继承WebMvcConfigurerAdapter(或直接实现接口WebMvcConfigurer),只不过要多加一个@EnableWebMvc注解而已。备注:若是SpringBoot环境,请不要加@EnableWebMvc注解,因为springboot已经实例化了WebMvcConfigurationSupport,如果添加了该注解,默认的WebMvcConfigurationSupport配置类是不会生效的


HandlerInterceptor与WebRequestInterceptor的异同


WebRequestInterceptor间接实现了HandlerInterceptor,只是他们之间使用WebRequestHandlerInterceptorAdapter适配器类联系。


这两个Spring MVC的拦截器接口比较就比较简单了。直接给结论吧:


两个接口都可用于Contrller层请求拦截,接口中定义的方法作用也是一样的。


1.WebRequestInterceptor的入参WebRequest是包装了HttpServletRequest 和


2.HttpServletResponse的,通过WebRequest获取Request中的信息更简便直接


3.WebRequestInterceptor的preHandle是没有返回值的,说明该方法中的逻辑并不影响后续的方法执行,所以这个接口实现就是为了获取Request中的信息,没有阻止请求的作用


4.HandlerInterceptor的功能更强大也更基础,可以在preHandle方法中就直接拒绝请求进入controller方法。


最佳实践:HandlerInterceptor能够实现所有WebRequestInterceptor做的事,更偏底层些。因此建议使用HandlerInterceptor


最后需要注意一点的是,关于自定义视图解析的自定义配置。此处还有一种方法是直接向容器里面注册Bean即可,如下:

    //自定义一个视图解析器
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("WEB-INF/views/");
        resolver.setSuffix(".html");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }


网络异常,图片无法展示
|


关于其中缘由原理。接下来相关文章讲解到Spring MVC深层原理剖析的时候,会精讲这一块,有兴趣的小伙伴可以持续关注

由于Spring Boot环境下并不建议启用@EnableWebMvc,所以使用起来请自行举一反三

相关文章
|
16天前
|
前端开发 安全 JavaScript
2025年,Web3开发学习路线全指南
本文提供了一条针对Dapp应用开发的学习路线,涵盖了Web3领域的重要技术栈,如区块链基础、以太坊技术、Solidity编程、智能合约开发及安全、web3.js和ethers.js库的使用、Truffle框架等。文章首先分析了国内区块链企业的技术需求,随后详细介绍了每个技术点的学习资源和方法,旨在帮助初学者系统地掌握Dapp开发所需的知识和技能。
2025年,Web3开发学习路线全指南
|
22天前
|
存储 前端开发 JavaScript
如何在项目中高效地进行 Web 组件化开发
高效地进行 Web 组件化开发需要从多个方面入手,通过明确目标、合理规划、规范开发、加强测试等一系列措施,实现组件的高效管理和利用,从而提高项目的整体开发效率和质量,为用户提供更好的体验。
27 7
|
22天前
|
前端开发 Java Spring
Spring MVC核心:深入理解@RequestMapping注解
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的核心,它将HTTP请求映射到控制器的处理方法上。本文将深入探讨`@RequestMapping`注解的各个方面,包括其注解的使用方法、如何与Spring MVC的其他组件协同工作,以及在实际开发中的应用案例。
36 4
|
22天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
67 2
|
22天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
22天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
57 0
|
2月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
159 3
|
1月前
|
设计模式 前端开发 数据库
Python Web开发:Django框架下的全栈开发实战
【10月更文挑战第27天】本文介绍了Django框架在Python Web开发中的应用,涵盖了Django与Flask等框架的比较、项目结构、模型、视图、模板和URL配置等内容,并展示了实际代码示例,帮助读者快速掌握Django全栈开发的核心技术。
172 45
|
27天前
|
开发框架 搜索推荐 数据可视化
Django框架适合开发哪种类型的Web应用程序?
Django 框架凭借其强大的功能、稳定性和可扩展性,几乎可以适应各种类型的 Web 应用程序开发需求。无论是简单的网站还是复杂的企业级系统,Django 都能提供可靠的支持,帮助开发者快速构建高质量的应用。同时,其活跃的社区和丰富的资源也为开发者在项目实施过程中提供了有力的保障。
|
26天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
35 2
下一篇
DataWorks