《Spring in action 4》(五)SpringMVC起步

SpringMVC起步

介绍

​ SpringMVC基于模型-视图-通知器(Model-View-Controller,MVC)模式实现的,它能够帮助我们构建像Spring框架那样灵活和松耦合的Web应用程序。

跟踪SpringMVC流程

图示:

步骤说明:

  1. 用户发送请求到前端控制器(DispatchServlet),该控制器会过滤出哪些请求可以访问servlet、哪些不能访问。就是URL-Pattern的作用,并且会加载SpringMVC.xml配置文件。

  2. 前端控制器会找到处理器映射器(HandlerMapping),通过HandlerMapping完成URL到Controller映射的组件,简单来说,就是将在SpringMVC.xml中配置的或者注解的URL与对应的处理类找到并进行存储,用Map<URL,handler>这样的方式来存储。

  3. HandlerMapping有了映射关系,并且找到了URL对应的处理器,HandlerMapping就会将其处理器(Handler)返回,在放回前,会加上很多拦截器。

  4. DispatchServlet拿到Handler之后,找到HandlerAdapter(处理器适配器),通过它来访问处理器,并执行处理器。

  5. 执行处理器。

  6. 处理器会返回一个ModelAndView对象到HandlerAdapter。

  7. 通过HandlerAdapter将ModelAndView对象返回到前端控制器DispatchServlet。

  8. 前端控制器请求视图解析器(ViewResolver)去进行视图解析,根据逻辑视图名解析成真真的视图(JSP),其实就是将ModelAndView对象中存放视图的名称进行查找,找到对应的页面形成视图对象。

  9. 返回视图对象到前端控制器。

  10. 视图渲染,就是将ModelAndView对象中的数据放到Request域中,用来让页面加载数据的。

  11. 通过第8步,通过名称找到了对应的页面,通过第10步,request域中有了所需要的数据,那么就可以进行视图渲染了。最后将其返回即可。

这里再附上网上的一张详细的图:

web.xml版本的Web项目

1. 项目结构

说明:蓝色的Java文件夹,标注为 sources,标有条形圆筒的resources文件夹标注为Resources。

  • java文件夹用来存放java源码。
  • resources文件夹用来存放资源文件:如spring配置文件,mapper文件。
  • webapp目录则用来存放web相关的资源。

File-->Project Structure

2. Pom 文件

<?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.ooyhao.spring</groupId>
    <artifactId>spring-in-action-05-01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <name>spring-in-action-05-01 Maven Webapp</name>
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.15.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.8.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>

		<!-- json 解析,不配置转化器和引入依赖,返回List则会抛出异常-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.5</version>
        </dependency>


    </dependencies>

</project>

3. 生成web.xml

项目结构搭建好之后,需要生成web.xml文件并配置。

web.xml文件:

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>

    <!--配置applicationContext配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring.xml</param-value>
    </context-param>


    <!--配置编码过滤器-->
    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>
          org.springframework.web.filter.CharacterEncodingFilter
        </filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置Spring上下文加载器的监听器-->
    <!--默认加载classpath:下面的applicationContext.xml文件。需要自定义可以配置
        <context-param></context-param> 进行配合使用
    -->
    <listener>
        <listener-class>
          org.springframework.web.context.ContextLoaderListener
         </listener-class>
    </listener>

    <!--配置前端控制器-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>
          org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

4. 配置Spring和SpringMVC

springmvc.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
">

    <context:component-scan base-package="com.ooyhao.spring.**.controller"/>


    <!--注解驱动,配置controller返回的转换器-->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </mvc:message-converters>
    </mvc:annotation-driven>

    <!--解决@ResponseBody返回中文乱码情况-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter" >
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes">
                        <list>
                            <value>text/plain;charset=utf-8</value>
                            <value>text/html;charset=UTF-8</value>
                        </list>
                    </property>
                </bean>
            </list>
        </property>
    </bean>
    <!--静态页面,如html,css,js,images可以访问-->
    <mvc:default-servlet-handler/>

    <bean    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass"
                  value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/WEB-INF/jsp"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

spring.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
">
    <!--组件扫描-->
    <context:component-scan base-package="com.ooyhao.spring">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

5. 编写逻辑代码

User类:

public class User implements Serializable {
    private String username;
    private Integer age;
    private String sex;
	//noArgsConstructor
  	//allArgsConstructor
  	//getter and setter
  	//toString
}

UserService类:

@Service
public class UserService {
    public List<User> findAllUsers(){
        List<User> users = new ArrayList<>();
        users.add(new User("林黛玉",18,"女"));
        users.add(new User("贾宝玉",20,"男"));
        return users;
    }
}

UserController类:

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/hello")
    @ResponseBody
    public List<User> hello(){
        return userService.findAllUsers();
    }
}

6. 测试和结果

启动程序,并测试 localhost:8080/SpringDemo/getAllUsers

无配置文件的Web项目

如果使用过SpringBoot的都知道,SpringBoot可以做到零配置,即没有xml文件,这是因为Spring3.2之后,引入了WebApplicationInitializer。我们可以看一下这个类的源码中:

容在下,拿着这一级英语水平去翻译。

/**
Interface to be implemented in Servlet 3.0+ environments in order to configure the {@link ServletContext} programmatically  as opposed to (or possibly in conjunction with) the traditional {@code web.xml}-based approach.
译:
 在servlet3.0版本以上,通过实现接口以编程的方式进行配置ServletContext,这与传统的web.xml的形式近乎相反(不用web.xml文件,同样可以创建一个web应用)

Implementations of this SPI will be detected automatically by {@link
SpringServletContainerInitializer}, which itself is bootstrapped automatically by any Servlet 3.0 container.
译:
 实现了这个SPI,将会被SpringServletContainerInitializer自动识别,并自动启动。

See {@linkplain SpringServletContainerInitializer its
Javadoc} for details on this bootstrapping mechanism.
译:
 可以通过查看SpringServletContainerInitializer文档去了解自启动机制的详情。
 
<h2>Example</h2>
译:示例
 <h3>The traditional, XML-based approach</h3>
 译:传统基于Xml的方式
Most Spring users building a web application will need to register Spring's {@code DispatcherServlet}. For reference, in WEB-INF/web.xml, this would typically be done as,follows:
 译:大部分Spring用户在构建一个web应用程序都需要去注册一个Spring的前端控制器(DispatchServlet),而其典型的就是在WEB-INF下的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>/WEB-INF/spring/dispatcher-config.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>

以上配置就是使用传统的xml配置文件的方式,向servlet容器中注册一个DispatchServlet。

The code-based approach with {@code WebApplicationInitializer}
 * Here is the equivalent {@code DispatcherServlet} registration logic,
 * {@code WebApplicationInitializer}-style:
 译:
 	通过使用WebApplicationInitializer的基于代码的方式,其与xml是一个相同的注册逻辑。WebApplicationInitializer代码方式:
 
 * public class MyWebAppInitializer implements WebApplicationInitializer {
 *    @Override
 *    public void onStartup(ServletContext container) {
 *      XmlWebApplicationContext appContext = new XmlWebApplicationContext();
 *      appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
 *
 *      ServletRegistration.Dynamic dispatcher =
 *        container.addServlet("dispatcher", new 	
 						DispatcherServlet(appContext));
 *      dispatcher.setLoadOnStartup(1);
 *      dispatcher.addMapping("/");
 *    }
 * }
 *
 * As an alternative to the above, you can also extend from {@link
org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}.

译:你也可以通过集成自AbstractDispatcherServletInitializer来实现,以上二者任选其一


 * As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method we're actually registering an instance of the {@code DispatcherServlet}, and this means that the {@code DispatcherServlet} can now be treated like any other object receiving constructor injection of its application context in this case.
 
译:如你所见,正是由于servlet3.0 的新方法(ServletContext#addServlet)出现,我们可以通过它注册一个DispatchServlet的实例了。这也意味着DispatchServlet可以像其他对象那
样,通过构造注入的方式接受一个Application Context

This style is both simpler and more concise. There is no concern for dealing with init-params, etc, just normal JavaBean-style properties and constructor arguments. You are free to create and work with your Spring application contexts as necessary before injecting them into the {@code DispatcherServlet}.

译:这种方式既简单又简洁。我们不需要如何去处理初始化参数,等,仅仅需要处理像普通JavaBean那样的属性和构造方法参数。在你必须将他们注入到DispatchServlet中之前,你可以很自由的构建和处理你的spring应用程序。

 Most major Spring Web components have been updated to support this style of registration.  You'll find that {@code DispatcherServlet}, {@code FrameworkServlet},{@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support constructor arguments. Even if a component (e.g. non-Spring, other third party) has not been specifically updated for use within {@code WebApplicationInitializers}, they still
 may be used in any case. The Servlet 3.0 {@code ServletContext} API allows for setting init-params, context-params, etc programmatically.
 
 译:大部分Spring web 组件都进行了更新,以至于能够支持这个注册方式,你可以发现像 DispatcherServlet、FrameworkServlet、ContextLoaderListener和DelegatingFilterProxy ,现在全部都支持构造参数注入。甚至是一些非spring,其他第三方组织的组件在WebApplicationInitializers中使用的没有支持的也会更新,以至于一直可以使用。Servlet3.0 API 也可以通过编程的方式 去设置 初始化参数、容器参数等
 

A 100% code-based approach to configuration In the example above, 
{@code WEB-INF/web.xml} was successfully replaced with code in
 * the form of a {@code WebApplicationInitializer}, but the actual
 * {@code dispatcher-config.xml} Spring configuration remained XML-based.
 * {@code WebApplicationInitializer} is a perfect fit for use with Spring's code-based
 * {@code @Configuration} classes. 
  See @{@link org.springframework.context.annotation.Configuration Configuration} Javadoc for complete details, but the following example demonstrates refactoring to use Spring's {@link org.springframework.web.context.support.AnnotationConfigWebApplicationContext AnnotationConfigWebApplicationContext} in lieu of {@code XmlWebApplicationContext}, and user-defined {@code @Configuration} classes {@code AppConfig} and {@code DispatcherConfig} instead of Spring XML files.
 
 译 :上面是一个完全由代码的配置方式的案例,web.xml文件已经被一个WebApplicationInitializer 成功取代。当然,实际上 Spring的配置任然保持着基于xml方式,WebApplicationInitializer 是一个更加适合 Spring代码的 配置类。可以查看Configuration的文档,了解完整的详情。但是下面这个例子是使用Spring的 AnnotationConfigWebApplicationContext 来代替XmlWebApplicationContext进行重构的。并且用户通过@Configuration,AppConfig,DispatcherConfig来代替xml配置文件。
 

 
 This example also goes a bit beyond those above to demonstrate typical configuration of the 'root' application context and registration of the {@code ContextLoaderListener}:
译:这个例子比上面的例子更进一步,使用root application context 来重构的典型配置,并且注册ContextLoaderListener
 
 * public class MyWebAppInitializer implements WebApplicationInitializer {
 *    @Override
 *    public void onStartup(ServletContext container) {
 *      // Create the 'root' Spring application context
 		创建root Spring的应用上下文
 *      AnnotationConfigWebApplicationContext rootContext =
 *        new AnnotationConfigWebApplicationContext();
 *      rootContext.register(AppConfig.class);
		
 *      // Manage the lifecycle of the root application context
 		管理root 应用上下文的生命周期
 *      container.addListener(new ContextLoaderListener(rootContext));

 *      // Create the dispatcher servlet's Spring application context
 		// 创建了Servlet的前端控制器的Spring应用上下文
 *      AnnotationConfigWebApplicationContext dispatcherContext =
 *        new AnnotationConfigWebApplicationContext();
 *      dispatcherContext.register(DispatcherConfig.class);

 *      // Register and map the dispatcher servlet
 		//注册并且映射前端控制器
 *      ServletRegistration.Dynamic dispatcher =
 *        container.addServlet("dispatcher", new 
 					DispatcherServlet(dispatcherContext));
 *      dispatcher.setLoadOnStartup(1);
 *      dispatcher.addMapping("/");
 *    }
 * }
 
 
 
 *
 * As an alternative to the above, you can also extend from {@link
 org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}.
 你也可以通过集成自AbstractAnnotationConfigDispatcherServletInitializer,以上二者任选其一。
 
Remember that {@code WebApplicationInitializer} implementations are  
  detected automatically -- so you are free to package them within your application as you see fit.
  
记住,WebApplicationInitializer 的实现类 都是自动识别的。所以,你可以自由的将他们打包到你认为合适的应用程序中。
  

Ordering {@code WebApplicationInitializer} execution
 {@code WebApplicationInitializer} implementations may optionally be annotated at the
 * class level with Spring's @{@link org.springframework.core.annotation.Order Order}
 * annotation or may implement Spring's {@link org.springframework.core.Ordered Ordered}
 * interface. If so, the initializers will be ordered prior to invocation. This provides
 * a mechanism for users to ensure the order in which servlet container initialization
 * occurs. Use of this feature is expected to be rare, as typical applications will likely
 * centralize all container initialization within a single {@code WebApplicationInitializer}.
 
Caveats :说明、警告

web.xml versioning 
web.xml版本

{@code WEB-INF/web.xml} and {@code WebApplicationInitializer} use are not mutually exclusive; for example, web.xml can register one servlet, and a {@code WebApplicationInitializer} can register another. An initializer can even modify registrations performed in {@code web.xml} through methods such as {@link ServletContext#getServletRegistration(String)}. 

译:web.xml与WebApplicationInitializer不会相互排斥,例如,web.xml可注册一个Servlet,而WebApplicationInitializer可以注册另一个,后者甚至可以修改在xml中注册的。


However, if {@code WEB-INF/web.xml} is present in the application, its {@code version} attribute must be set to "3.0" or greater, otherwise {@code ServletContainerInitializer} bootstrapping will be ignored by the servlet container

译:然而,如果web.xml存在于应用中,那么它的版本必须在3.0或以上,否则ServletContainerInitializer 自己化启动将会被Servlet容器忽略。
 

 * <h3>Mapping to '/' under Tomcat</h3>、
 在tomcat下映射/
 * <p>Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions
<= 7.0.14, this servlet mapping cannot be overridden programmatically 
 7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested successfully under GlassFish 3.1
译:在tomcat7.0.14及以下,tomcat使用其自身的DefaultServlet去映射/,这个Servlet映射将不能被编程方式重写在7.0.15版本。在GlassFish 3.1下, 成功测试出可以重写/这个Servlet映射器。
 */

public interface WebApplicationInitializer {

	/**
	 * Configure the given {@link ServletContext} with any servlets, filters, listeners context-params and attributes necessary for initializing this web application. 
	 为你这个web容器配置任何必要的 filter,listener,servlet,及其参数和属性
	 
	 See examples {@linkplain WebApplicationInitializer above}.
	 * @param servletContext the {@code ServletContext} to initialize
	 * @throws ServletException if any call against the given {@code ServletContext}
	 * throws a {@code ServletException}
	 */
	void onStartup(ServletContext servletContext) throws ServletException;

}

​ 当你耐心的看完源码和翻译,你大概就知道SpringBoot为何可以做到不用web.xml文件就可以搭建一个web项目。这是使用了WebApplicationInitializer。
其实当我们好好学习Spring,会发现曾经SpringBoot那些神秘面纱,慢慢被揭开了。这也是我学了SpringBoot基础之后,又回头学习Spring的原因。
废话不多说了,开始搭建我们无配置文件的wen项目。

1. 替代web.xml的InitWeb

web.xml这里使用InitWeb.java代替,这里使用的是AbstractAnnotationConfigDispatcherServletInitializer,WebApplicationInitializer的子类,代码如下:

public class InitWeb extends AbstractAnnotationConfigDispatcherServletInitializer {


    @Override
    protected Class<?>[] getRootConfigClasses() {
      	//配置rootConfig,即原来的applicationContext.xml
        return new Class[]{RootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
      	//配置ServletConfig,即原来的springmvc.xml
        return new Class[]{ServletConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
      	//配置servlet映射,即之前在web.xml中DispatchServlet中配置的。
        return new String[]{"/"};
    }
}

2. POM

<?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.ooyhao.spring</groupId>
  <artifactId>spring-in-action-05-02</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>spring-in-action-05-02 Maven Webapp</name>
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.5</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>

  </dependencies>

</project>

3. RootConfig

/*相当于spring.xml*/
@ComponentScan(basePackages = "com.ooyhao.spring", useDefaultFilters = true,excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = EnableWebMvc.class)
})
public class RootConfig {

扫描基础包,使用默认的过滤器。即全部都扫描。排除标有Controller注解的。

4. ServletConfig

/*相当于springmvc.xml*/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.ooyhao.spring.**.controller")
public class ServletConfig implements WebMvcConfigurer {


    /*配置JSP视图解析器*/
    @Bean
    public ViewResolver viewResolver(){
        InternalResourceViewResolver resolver =
                new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    /*配置静态资源的处理*/
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

可以看出,SpringMvc的配置文件,比前面的spring配置文件多了很多内容。扫描所有的Controller,配置视图解析器,配置静态资源处理。开启webmvc(@EnableWebMvc)

@EnableWebMvc 相当于之前在springmvc.xml文件中配置的<mvc:annotation-driven>

5. 编写逻辑代码

Article:

public class Article implements Serializable {


    private Integer id;
    private String title;
    private String content;
    private String author;

    public Article() {}

    public Article(Integer id, String title, String content, 
                   String author) {
        this.id = id;
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public String toString() {
        return "Article{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", author='" + author + '\'' +
                '}';
    }
}

ArticleService:

@Service
public class ArticleService {

    private static List<Article> articles = new ArrayList<>();

    static {
        Article article1 = new Article(1,"红楼梦","红楼梦","曹雪芹");
        Article article2 = new Article(2,"三国演义","三国演义","罗贯中");
        Article article3 = new Article(3,"水浒传","水浒传","施耐庵");
        Article article4 = new Article(4,"西游记","西游记","吴承恩");
        articles.add(article1);
        articles.add(article2);
        articles.add(article3);
        articles.add(article4);
    }

    public List<Article> findAllArticle(){
        return articles;
    }
}

ArticleController:

@Controller
public class ArticleController {
    @Autowired
    private ArticleService articleService;

    @RequestMapping("/findAllArticles")
    @ResponseBody
    public List<Article> findAllArticles(){
        List<Article> articles = articleService.findAllArticle();
        return articles;
    }
}

6. 测试和结果

接受请求参数的输入

SpringMVC允许以多种方式将客户端中的数据传送到控制器的处理器方法中,包括:

  • 查询参数(Query Parameter)
  • 表单参数(Form Patameter)
  • 路径参数(Path Variable)

将上述示例进行了一定的修改,配合页面进行展示:

1. 查询参数

查询参数,我们以分页来做示例:

ArticleService:

@Service
public class ArticleService {
    private static Map<Integer,Article> articles = new HashMap<>();

    static {
        Article article1 = new Article(1,"红楼梦","林黛玉、贾宝玉","曹雪芹");
        Article article2 = new Article(2,"三国演义","诸葛亮、刘备","罗贯中");
        Article article3 = new Article(3,"水浒传","鲁智深、武松","施耐庵");
        Article article4 = new Article(4,"西游记","孙悟空、猪八戒","吴承恩");
        articles.put(1,article1);
        articles.put(2,article2);
        articles.put(3,article3);
        articles.put(4,article4);
    }

    public List<Article> findAllArticle(){
        return new ArrayList<>(articles.values());
    }

    public Article findArticleById(Integer id) {
        return articles.get(id);
    }

    public List<Article> findArticleByPage(Integer page, Integer size){
        int firstIndex = (page-1) * size;
        int lastIndex = page * size;
        return new ArrayList<>
          (articles.values()).subList(firstIndex,lastIndex);
    }

}

ArticleController:

@Controller
public class ArticleController {

    @Autowired
    private ArticleService articleService;

    @GetMapping("/article")
    public String article(Model model,
           @RequestParam(value = "page",defaultValue = "1") Integer page,
           @RequestParam(value = "size",defaultValue = "2") Integer size){
        List<Article> articles = articleService.findArticleByPage(page,
                                                                  size);
        model.addAttribute("articles",articles);
        return "articleList";
    }
}

articleList.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
  Created by IntelliJ IDEA.
  User: ouYang
  Date: 2019/9/1
  Time: 11:13
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

    <%--文章信息--%>
    <h1>文章列表</h1>

    <c:forEach items="${articles}" var="article">
        <ul>
            <li>ID:<c:out value="${article.id}"/></li>
            <li>标题:<c:out value="${article.title}"/></li>
            <li>内容:<c:out value="${article.content}"/></li>
            <li>作者:<c:out value="${article.author}"/></li>
        </ul>
    </c:forEach>
</body>
</html>

测试及结果:

说明:

​ 请求参数会拼接在请求路径后面使用?隔开,参数和值之间使用=连接。多个参数之间使用&进行拼接。Controller可以通过@RequestParam注解接收,可以设置默认值,当然,也可以省略@RequestParam注解。

2. 通过路径参数

ArticleController:

/*将参数写到路径上*/
@GetMapping("/article/{id}")
public String article(Model model,@PathVariable("id") Integer id){
  Article article = articleService.findArticleById(id);
  List<Article> articles = new ArrayList<>();
  articles.add(article);
  model.addAttribute("articles",articles);
  return "articleList";
}

测试及结果:

说明:

​ 可以看出,路径参数与请求参数是不一样的,路径参数是请求地址中的一部分,在Controller中通过@PathVariable注解来注释接受。/article/{id} 在路径上使用{}来标注参数的位置。

3. 处理表单

处理表单参数,首先我们需要一个表单,这里以注册为例:

register jsp文件:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%--
  Created by IntelliJ IDEA.
  User: ouYang
  Date: 2019/9/1
  Time: 10:29
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Blog</title>
</head>
<body>

    <h1>欢迎加入Spring的大家庭</h1>
    <form method="post" >
        用户名:<input name="username" type="text" ><br>
        密码:<input name="password" type="password" ><br>
        年龄:<input name="age" type="number" ><br>
        <input type="submit" value="提交"><br>
    </form>
</body>
</html>

一个普通的表单,使用post请求提交,当我们没有指定action属性的时候,点击提交之后,会使用原地址,即请求表单页面的地址,通过post请求提交,本例中是/register. 表单包括 用户名,密码和年龄三个属性。后台进行相应的Bean接受(User)

User:

public class User implements Serializable {
    private String username;
    private String password;
    private Integer age;
  	//all/noArgConstructor, getter and setter, toString
}

UserService:

@Service
public class UserService {
    private static List<User> users = new ArrayList<>();
    public void saveUser(User user){
        users.add(user);
    }
}

IndexController:

@Controller
public class IndexController {

    @Autowired
    private UserService userService;

    @GetMapping("/")
    public String home(){
        return "home";
    }

    @GetMapping("/register")
    public String toRegister(){
        return "register";
    }

    /*处理表单数据,并验证*/
    @PostMapping("/register")
    public String register(User user){
        userService.saveUser(user);
        return "redirect:/registerSuccess";
    }

    @GetMapping("/registerSuccess")
    public String registerSuccess(){
        return "registerSuccess";
    }

    @GetMapping("/registerFail")
    public String registerFail(){
        return "registerFail";
    }
}

​ 通过上面代码可以看出,通过get请求类型,请求路径为/register,通过返回逻辑视图名,DispatchServlet将逻辑视图名去查找视图,并渲染返回,就可以进入注册表单页面。显而易见,前端提交的参数会自动填写到User对象中,这就是SpringMVC的强大之处,可以将数据自动与实体进行映射。

注册成功之后就会跳到注册成功的页面:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>注册成功~</h1>
</body>
</html>

如下:

但是此时我们可以尝试,在页面不填写任何数据,同样是可以提交成功的,这明显不符合我们的业务要求。所以这里需要进行参数校验。

4. 参数校验

首先,我们需要导入一个Jar包

<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.1.0.Alpha3</version>
</dependency>

其次我们需要修改我们的实体类

public class User implements Serializable {
    @NotNull
    @Size(min = 4,max = 20)
    private String username;

    @NotNull
    @Size(min = 6,max = 32)
    private String password;

    @NotNull
    @Min(1)
    @Max(150)
    private Integer age;

    //all/noArgConstructor, getter and setter, toString
}

并且需要在Controller的方式接受参数的地方加上@Valid注解,如下:

/*处理表单数据,并验证*/
@PostMapping("/register")
public String register(@Valid User user, Errors errors){
  if (errors.hasErrors()){
    return "redirect:/registerFail";
  }
  userService.saveUser(user);
  return "redirect:/registerSuccess";
}

​ 可以看出这里有个Errors参数,这个参数必须紧跟在@Valid注解之后,并且可以通过它判断参数填写是否正确。因为即使加了@Valid,也无法阻止方法的执行,所以需要手动判断处理。

我们增加一个注册失败的提示页面。

registerFail:

<%@ taglib prefix="c" uri="http://www.springframework.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Blog</title>
</head>
<body>
    <h1>注册失败,请检查数据是否填写正确~</h1>
</body>
</html>

如未按要求填写数据就提交:

4.1 参数检验注解

注解 描述
@AssertFalse 所注解的元素必须是Boolean类型,并且值为false
@AssertTrue 所注解的元素必须是Boolean类型,并且值为true
@DecimalMax 所注解的元素必须是数字,并且它的值要小于或等于给定的BigDecimalString的值
@DecimalMin 所注解的元素必须是数字,并且它的值要大于或等于给定的BigDecimalString的值
@Digists 所注解的元素必须是数字,并且它的值必须有指定的位数
@Future 所注解的元素的值必须是一个将来的日期
@Max 所注解的元素必须是数字,并且它的值要小于或等于给定的值
@Min 所注解的元素必须是数字,并且它的值要大于或等于给定的值
@NotNull 所注解元素的值必须不能为null
@Null 所注解元素的值必须为null
@Past 所注解的元素的值必须是一个已过去的日期
@Pattern 所注解的元素的值必须匹配给定的正则表达式
@Size 所注解的元素的值必须是String,集合或数组,并且它的长度要符合给定的范围

5. 本地中的POM 文件

<?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.ooyhao.spring</groupId>
  <artifactId>spring-in-action-05-02</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>spring-in-action-05-02 Maven Webapp</name>
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- @ResponseBody自动转JSON -->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.9.5</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.9.5</version>
    </dependency>


    <!-- servlet参数 -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.9.RELEASE</version>
    </dependency>


<!-- 参数校验 -->
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>6.1.0.Alpha3</version>
    </dependency>

<!--jsp中使用jstl标签-->
    <dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>spring-in-action-05-02</finalName>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

总结:

​ 这一节主要是将web.xml构建web项目和WebApplicationInitializer构建项目对比,手动搭建一个无xml文件的web项目。同时也揭开了SpringBoot无配置文件启动的神秘面纱。并且连接了三种参数输入的方式。

案例地址:

https://gitee.com/ooyhao/JavaRepo_Public/tree/master/Spring-in-Action/spring-in-action-05

最后

如果觉得不错的话,那就关注一下小编哦!一起交流,一起学习

posted @ 2019-09-21 10:31  ooyhao  阅读(373)  评论(0编辑  收藏  举报