赞
踩
配置webapp(SpringMVC)
选中这个之后出现这个页面(设置web资源的目录,也就是我们webapp所在的位置,它默认的位置并不正确,所有我们要修改一下),修改为 src\main\webapp(没有这个文件夹就新建)
因为我们现在搭建的注解版,所以就不需要web.xml(点击-号把默认的删除)
最终的项目目录:
- <?xml version="1.0" encoding="UTF-8"?>
- <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</modelVersion>
-
- <groupId>com.codedot</groupId>
- <artifactId>SpringAnno</artifactId>
- <version>1.0-SNAPSHOT</version>
- <packaging>war</packaging>
-
- <properties>
- <!-- JDK版本 -->
- <java.version>1.8</java.version>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <!-- Spring 5.1.6 要求JDK必须是1.8以上-->
- <spring.version>5.1.16.RELEASE</spring.version>
- </properties>
-
- <dependencies>
- <!-- 核心容器 之 spring-core -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-core</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <!-- 核心容器 之 spring-beans -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-beans</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <!-- 核心容器 之 spring-context -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <!-- 核心容器 之 spring-context-support -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-context-support</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <!-- 核心容器 之 spring-expression -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-expression</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
- <!-- Spring Web -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-web</artifactId>
- <version>${spring.version}</version>
- </dependency>
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-webmvc</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
- <!-- 否则 Error:(9,7) java: 错误: 无法访问ServletException-->
- <dependency>
- <groupId>javax.servlet</groupId>
- <artifactId>javax.servlet-api</artifactId>
- <version>3.0.1</version>
- <scope>provided</scope>
- </dependency>
-
- <!-- Spring Test -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-test</artifactId>
- <version>${spring.version}</version>
- </dependency>
-
- <!-- Junit -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version><!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上,否则用不了-->
- <scope>test</scope>
- </dependency>
-
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-compiler-plugin</artifactId>
- <version>2.3</version>
- <configuration>
- <source>1.8</source>
- <target>1.8</target>
- <encoding>${project.build.sourceEncoding}</encoding>
- </configuration>
- </plugin>
- <!-- 没有web.xml文件的情况下构建WAR, 否则打包时会报错 -->
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-war-plugin</artifactId>
- <version>2.6</version>
- <configuration>
- <!--如果想在没有web.xml文件的情况下构建WAR,请设置为false。-->
- <failOnMissingWebXml>false</failOnMissingWebXml>
- </configuration>
- </plugin>
- </plugins>
- </build>
-
- </project>

要使用spring的事务,需要引入:
- <!-- Spring 事务支持 -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-tx</artifactId>
- <version>${spring.version}</version>
- </dependency>
要使用spring的JdbcTemplate等功能,需要引入:
- <!-- Spring JDBC -->
- <dependency>
- <groupId>org.springframework</groupId>
- <artifactId>spring-jdbc</artifactId>
- <version>${spring.version}</version>
- </dependency>
继承AbstractAnnotationConfigDispatcherServletInitializer,创建启动器
- import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
-
- /**
- * @Description AbstractAnnotationConfigDispatcherServletInitializer 是在spring-webmvc包下的
- */
- public class SpringInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
-
- //Spring容器:等同于applicationContext.xml
- @Override
- protected Class<?>[] getRootConfigClasses() {
- return new Class[]{SpringConfig.class};
- }
-
- //SpringMVC容器 :等同于springmvc.xml
- @Override
- protected Class<?>[] getServletConfigClasses() {
- return new Class[]{SpringMVCConfig.class};
- }
-
- // SpringMVC 的DispatcherServlet拦截规则
- @Override
- protected String[] getServletMappings() {
- return new String[]{"/"};
- }
- }

这里需要特别强调的是使用的是 / , 而不是 /*。请求时可以通过DispatcherServlet转发到相应的的Controller中的,但是返回的内容,如返回的jsp还会再次被拦截,这样导致404错误,即访问不到jsp。所以如果以后发现总是有404错误的时候,别忘了检查一下 /的配置是否是/*。
Spring 的Servlet拦截器匹配规则,当映射为@RequestMapping("/user/add")时:
① 拦截*.do、*.htm, 例如:/user/add.do
这是最传统的方式,最简单也最实用。不会导致静态文件(jpg,js,css)被拦截。
② 拦截/,例如:/user/add
可以实现现在很流行的REST风格。很多互联网类型的应用很喜欢这种风格的URL。
弊端:会导致静态文件(jpg,js,css)被拦截后不能正常显示(有解决方法)。
③ 拦截/*,这是一个错误的方式,请求可以走到Action中,但转到jsp时再次被拦截,不能访问到jsp。
Spring容器配置
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.FilterType;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RestController;
-
- /**
- * 这个是Spring容器,相当于ApplicationContext.xml,负责扫描相关的service和dao,排除controller的扫描
- * 数据源、事务等均在这里配置
- */
- @ComponentScan(value = {"com.codedot"},
- excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
- @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class})})
- @Configuration
- public class SpringConfig {
-
- }

Spring MVC容器配置
- import org.springframework.context.annotation.ComponentScan;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.context.annotation.FilterType;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RestController;
- import org.springframework.web.servlet.config.annotation.EnableWebMvc;
- import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
- import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
- /**
- * SpringMVC容器,负责扫描controller,声明视图解析规则、静态资源处理规则、拦截器等.
- */
- @EnableWebMvc
- @ComponentScan(value = {"com.codedot.ctrl"},
- includeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value={Controller.class}),
- @ComponentScan.Filter(type= FilterType.ANNOTATION,value={RestController.class})},useDefaultFilters = false)
- @Configuration
- public class SpringMVCConfig implements WebMvcConfigurer{
-
- @Override
- public void configureViewResolvers(ViewResolverRegistry registry) {
- registry.jsp("/WEB-INF/views/", ".jsp");
- }
- }

在Spring5.0后,原来继承WebMvcConfigurerAdapter过期,通常情况下我们会采用下面两种代替方案:
在webapp/WEB-INF/views目录下创建index.jsp
- <%@ page contentType="text/html;charset=UTF-8" language="java" %>
- <html>
- <head>
- <title>首页</title>
- </head>
- <body>
- Hello World!!!
- </body>
- </html>
新建Controller
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.GetMapping;
-
- @Controller
- public class HelloController {
-
- @GetMapping("fromIndex")
- public String helloIndex(){
- return "index";
- }
- }
配置tomcat部署项目,访问http://ip:port/上下文/fromIndex
注意:这里要特别注意上下文的设置。
为什么ServletContainerInitializer取代web.xml实现零配置?
tomcat自动扫描web项目的WEB-INF/classes或WEB-INF/lib/*.jar的META-INF/services/javax.servlet.ServletContainerInitializer文件,调用其onStartup方法,参数webAppInitializerClasses为@HandlesTypes指定接口的所有实现类。因为spring-web.jar,所以开发过程中,只需定义一个类实现org.springframework.web.WebApplicationInitializer接口就可以了。
javax.servlet.ServletContainerInitializer接口
在基于注解的servlet开发中,ServletContainerInitializer接口用于代替web.xml。它只有一个方法:onStartup,可以在其中注册servlet、拦截器(Filter)、监听器(Listener)这三大组件。另外,ServletContainerInitializer还可以使用@HandlesTypes在onStartup方法的参数列表中注入感兴趣的类。servlet容器启动时,会扫描每个jar包的项目根目录下的/META-INF/services/javax.servlet.ServletContainerInitializer文件,执行这个文件中指定的ServletContainerInitializer接口的实现类的onStartup方法。
org.springframework.web包提供的ServletContainerInitializer实现类
org.springframework.web包的/META-INF/services/javax.servlet.ServletContainerInitializer文件指定了ServletContainerInitializer接口的实现类:SpringServletContainerInitializer。源码如下:
- package org.springframework.web;
-
- @HandlesTypes(WebApplicationInitializer.class) //(1)
- public class SpringServletContainerInitializer implements ServletContainerInitializer {
-
- @Override
- public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
- throws ServletException {
-
- List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
-
- 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) waiClass.newInstance()); //(2)
- }
- 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); //(3)
- }
- }
-
- }

(1) 通过@HandlesType注解在onStartup方法的参数列表中注入感兴趣的类,即WebApplicationInitializer;
(2) 将WebApplicationInitializer的每个实现类,都新建一个实例,并放入initializers列表中;
(3) 遍历initializers列表,对每个WebApplicationInitializer实例执行其onStartup方法。
那么问题来了:WebApplicationInitializer有哪些实现类,是用来干什么的?
WebApplicationInitializer的实现类及其功能
WebApplicationInitializer的实现类有很多,重点看一下AbstractAnnotationConfigDispatcherServletInitializer
- package org.springframework.web.servlet.support;
-
- public abstract class AbstractAnnotationConfigDispatcherServletInitializer
- extends AbstractDispatcherServletInitializer {
-
- @Override
- @Nullable
- protected WebApplicationContext createRootApplicationContext() {
- Class<?>[] configClasses = getRootConfigClasses();
- if (!ObjectUtils.isEmpty(configClasses)) {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- context.register(configClasses);
- return context;
- }
- else {
- return null;
- }
- }
-
- @Override
- protected WebApplicationContext createServletApplicationContext() {
- AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
- Class<?>[] configClasses = getServletConfigClasses();
- if (!ObjectUtils.isEmpty(configClasses)) {
- context.register(configClasses);
- }
- return context;
- }
-
- @Nullable
- protected abstract Class<?>[] getRootConfigClasses();
-
- @Nullable
- protected abstract Class<?>[] getServletConfigClasses();
-
- }

这个类提供了两个方法的实现,以及两个抽象方法供子类继承:
(1) createRootApplicationContext:创建根容器;
(2) createServletApplicationContext:创建servlet容器;
(3) getRootConfigClasses:抽象类,用于注册根容器的配置类,相当于applicationContext.xml;
(4) getServletConfigClasses:抽象的类,用于注册servlet容器的配置类,相当于springmvc.xml;
另外,它还从AbstractDispatcherServletInitializer类继承了getServletMappings方法,用于注册servlet的映射。
因此,我们可以自定义一个WebApplicationInitializer的实现类,继承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器启动时,会创建spring根容器和servlet容器,代替web.xml配置文件。同时,我们可以看到,在基于注解的springmvc开发中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,这与本文开头提到的基于注解的servlet开发有些区别。
根容器和Servlet容器
根容器用于管理@Service、@Repository等业务逻辑层和数据库交互层组件;
servlet容器用于管理@Controller、视图解析器、拦截器等跟页面处理有关的组件。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。