在 Servlet 3.0 标准之前,配置Spring MVC 要在 web.xml 中配置 前端控制器 本来记住几个类名还不太难,可是要记住这么多XML节点,就有点费劲了。 自从 Servlet 3.0 问世之后,我们可以通过 Java 的方式配置 Servlet 容器了,摆脱了繁琐的 XML 文件,瞬间起飞~~ 芜湖!!! 所有实现了 Servlet3.0 的 Web 容器,都会在 jar/war 包下面搜索一个文件,这个文件的名字必须叫 javax.servlet.ServletContainerInitializer ,而且必须放在 jar/war 包的类路径下面的 META-INF/services 文件夹里面。这个文件的内容也非常简单,是某个实现了 可以看到, 讲了 Servlet 3.0 之后,我们再来理解基于 Java 配置的 Spring MVC,就会轻车熟路了。因为说到底,Spring MVC的核心:前端控制器 有了 Servlet 3.0 的基础,我们知道了所有实现了 Servlet 3.0 标准的 Web 容器在启动的时候都会去项目类路径下的 META-INF/services 下面查找 主要的实现逻辑如下: 既然要加载的是 先了解下 Spring MVC 父子容器的概念,打开–> Spring 官网<– 可以看到,Spring 推荐我们使用父子容器的方式创建 Spring MVC上下文环境。其中 Servlet 应用上下文 注入所有与 Web 开发相关的功能,如所有 Controller 、视图解析器和处理器映射器。 说了半天,终于开始配置 Spring MVC 了。我们先打开 屁话不多说,这里主要强调了该接口的一个抽象实现,也是本文的重头戏: 可以看到 我们只需要实现 啰嗦一堆,重头戏终于来了!!!我会在方法的注释上贴上对应的 xml 配置方式,方便大家对比差异。 好了,这就是用 Java 配置 Spring MVC的全部过程,是不是很方便?比起 xml 繁琐的配置,我还是更喜欢用 Java 来配置。前言
DispatcherServlet
、Web容器启动监听器ContextLoaderListener
。配置的东西倒是不多,但是由于 XML 文件的特性,即使是简单的配置上述两项也会有一大堆的 XML 节点要写,例如下面这样<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://xmlns.jcp.org/xml/ns/javaee https://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!--配置Spring IOC容器的启动监听器--> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/springApplication.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!--配置Spring MVC的前端控制器--> <servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>SpringMVC</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
Servlet 3.0 (及以上版本)
使用 Java 配置的原理
javax.servlet.ServletContainerInitializer
接口的实现类的全限定类名。
(PS : 在 IDEA 中,蓝色的文件夹就代表项目的类路径。)
现在看一下这个类的实现@HandlesTypes(Color.class) public class MyServletContainerInit implements ServletContainerInitializer { /** * Servlet 容器启动时调用该方法 * @param set 上面的 @HandlersTypes 注解中 classes 方法指明的接口/抽象类的所有子类 * @param servletContext Application 作用域,可以用来注册 Servlet 、Listener、Filter * @throws ServletException */ @Override public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException { // 注册 servlet ServletRegistration.Dynamic userServlet = servletContext.addServlet("userServlet", UserServlet.class); userServlet.addMapping("/users"); // 注册 filter FilterRegistration.Dynamic characterFilter = servletContext.addFilter("characterFilter", MyFilter.class); // 第一个参数是设置过滤器拦截哪种请求 characterFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true, "/*"); // 添加 Listener servletContext.addListener(MyListener.class); } }
onStartup
方法中所做的事情,和 web.xml 文件中做的事情是一样的,但是简化了很多。是不是很方便!而且要配什么,都是通过调用方法的方式进行配置,而不是像 XML 中一样要记住那些标签的名字。
这里要特别的注意一下 @HandlesTypes
注解,这个注解就是后面要说的,Spring MVC 基于 Java 配置的原理。使用 Java 配置 Spring MVC
DispatcherServlet
只是一个普通的 Servlet ,它也需要注入到 Web 容器中才能发挥 Spring MVC 的前端控制器功能。你刀法再好,我不给你刀,你也练不出来。javax.servlet.ServletContainerInitializer
文件,那我们就来看看 Spring MVC 的 jar 包下面有没有这个文件。
果然被我们找到了,只不过这里的实现类,是一个名为 SpringServletContainerInitializer
的类,点进去看看里面写了啥/** * 注意这里,非常重要,这就是 Spring-MVC 留给我们的扩展接口 */ @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... // 如果传入的webAppInitializerClasses中某个成员不是WebApplicationInitializer的子接口 // 并且不是抽象实现类,就要创建实例 if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { // 将所有 WebApplicationInitializer 实现类收集到 list 中 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); // 循环调用所有实现类的 onStartup 方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
@HandlesTypes
来获取所有WebApplicationInitializer
类型的实现类LinkedList
中onStartup
方法。WebApplicationInitializer
接口的实现类,那是不是我们创建一个类来实现该接口就可以对Web 容器进行初始化和添加组件了呢?如果真的这么写了,就和上面的普通 Web 工程一样了,我们无法通过这种形式创建 Spring MVC父子容器。Spring MVC父子容器
而根应用上下文则负责管理所有与业务相关的组件,如 Service 层和 Repositories 数据访问层。
开始配置
WebApplicationInitializer
接口,看看 Spring 为我们提供的注释。
注释太长了,里面写了详细的 xml 配置方式。我主要翻译并截取了重要的部分
真的就跟写论文一样······。老外做程序员太吃香了,这些文档看完还有不会的道理么?AbstractAnnotationConfigDispatcherServletInitializer
。Spring-MVC官方推荐我们实现这个类,来对 Web 容器进行初始化的工作。先来看看这个类。public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer { /** * {@inheritDoc} * <p>This implementation creates an {@link AnnotationConfigWebApplicationContext}, * providing it the annotated classes returned by {@link #getRootConfigClasses()}. * Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}. */ @Override @Nullable protected WebApplicationContext createRootApplicationContext() { Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(configClasses); return context; } else { return null; } } /** * {@inheritDoc} * <p>This implementation creates an {@link AnnotationConfigWebApplicationContext}, * providing it the annotated classes returned by {@link #getServletConfigClasses()}. */ @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); Class<?>[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { context.register(configClasses); } return context; } /** * 模板方法1:通过子类重写该方法,来返回 Root 应用上下文的配置类类型 * @return the configuration for the root application context, or {@code null} * if creation and registration of a root context is not desired */ @Nullable protected abstract Class<?>[] getRootConfigClasses(); /** * 模板方法2:通过子类重写该方法,来返回 Servlet 应用上下文的配置类类型 * {@code null} if all configuration is specified through root config classes. */ @Nullable protected abstract Class<?>[] getServletConfigClasses(); }
AbstractAnnotationConfigDispatcherServletInitializer
中应用了模板方法设计模式,上面两个方法分别创建了一个 WebApplicationContext
实例。顾名思义,createRootApplicationContext
方法负责创建根应用上下文环境,而createServletApplicationContext
负责创建 Servlet 应用上下文实例。这里的 WebApplicationContext
实例都是通过 new
关键字创建的,也就是说不存在单例模式的可能,两个方法都是实打实的创建出两个上下文,地址绝对不相同。
createRootApplicationContext
方法调用了模板方法 getRootConfigClasses
来获取根应用的配置类,这个类里面可以注入一些 Bean ,指定包扫描等操作,也就是相当于一个 springApplication.xml 配置文件。createServletApplicationContext
方法调用了模板方法getServletConfigClasses
来获取 Servlet 容器的配置类,可以在这里配置前端控制器,视图解析器等组件。getRootConfigClasses
方法和 getServletConfigClasses
方法就可以分别配置 根应用上下文和 Servlet 上下文,再实现 getServletMappings
方法即可设置前端控制器的拦截路径。代码实现
AbstractAnnotationConfigDispatcherServletInitializer
的子类,也就是我们的配置类public class WebMvcConfig extends AbstractAnnotationConfigDispatcherServletInitializer { /** * 指明跟容器的配置类的类型 * 对应 XML 配置,这里可能不太对,在 xml 配置中。根应用上下文是在容器启动监听器中创建的 * <context-param> * <param-name>contextConfigLocation</param-name> * <param-value>classpath:spring/springApplication.xml</param-value> * </context-param> */ @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{RootContext.class}; } /** * 指明 Web 容器的配置类的类型 * <servlet> * <servlet-name>SpringMVC</servlet-name> * <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> * <load-on-startup>1</load-on-startup> * </servlet> */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{ServletContext.class}; } /** * 为 DispatcherServlet 添加 URL 映射,例如:/ 或者 /app * 对应 XML 的配置 * <servlet-mapping> * <servlet-name>SpringMVC</servlet-name> * <url-pattern>/</url-pattern> * </servlet-mapping> */ @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
/** * Spring 容器只扫描和 web 无关的组件,所以要排除 Controller */ @ComponentScan(basePackages = "com.mvc.demo", excludeFilters = { @ComponentScan.Filter(classes = Controller.class) } ) public class RootContext { @Bean public DataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername("root"); dataSource.setPassword("123"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306"); return dataSource; } }
/** * Dispatcher 容器,只负责扫描 Controller,要排除其他注解标注的类 * 默认就会扫描 Spring 所有 Component 的衍生注解标注的类,所以要禁用 * 默认规则 */ @ComponentScan(basePackages = "com.mvc.demo", useDefaultFilters = false, includeFilters = @ComponentScan.Filter(classes = Controller.class) ) public class ServletContext { /** * 对应的 xml 配置 *<servlet> <servlet-name>SpringMVC</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> */ @Bean public DispatcherServlet dispatcherServlet() { DispatcherServlet dispatcherServlet = new DispatcherServlet(); return dispatcherServlet; } }
如果使用 SpringBoot ,这些配置都不用写了,更加方便。但是理解了这种 Java 配置形式之后,再去理解 SpringBoot 的自动配置原理会容易许多。苦尽甘来,写过 XML 的配置形式,才能体验 Java 配置的简单。配过 Spring MVC 才能知道 SpringBoot 是多么的方便。如果大家喜欢我的文章,觉得讲的还算通俗易懂,麻烦点个赞支持下!
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算