跳至主要內容

SpringMVC开发实战-1

wangdx大约 22 分钟

WEB 开发与 MVC 设计模式

WEB 开发

  • WEB 开发是现在开发之中最为常用的处理形式,不管采用的是传统的单机 WEB 运行模式,还是前后端分离开发模式,其核心的本质都需要提供有完善的 WEB 服务端,所有的客户端基于 HTTP 协议通过 WEB 服务器获取所需要的数据,最终的数据会基于浏览器或手机移动端进行界面的展示

Spring 框架技术整合

  • 考虑到项目的设计与维护,所以在进行服务端应用开发时,往往都会采用 MVC(Model-View-Controller、模型-视图-控制)设计模式,基于有效的分层设计,可以便于代码的开发与维护。考虑到代码的维护管理,实际项目开发中,也会采用 MVC 框架进行项目的实现

SSM 整合

  • 伴随着 Java 技术的发展,出现过许多的 MVC 开发框架,例如:Struts、WebWork、Tapestry、JSF、SpringMVC,而现在在国内使用较多的 MVC 框架主要以 SpringMVC 为主,因为其可以与 Spring 实现更好的整合,而且也没有出现过任何的安全漏洞,但是一个完整的应用项目的开发,不仅仅只有 MVC 开发框架,也可能需要使用到各类的 ORMappinq 组件、消息服务以及各类第三方服务整合,而这些组件都可以基于 Spring 框架实现统一的整合。

搭建 SpringMVC 项目

1、
https://gradle.org/

2、
project_group=com.yootk
project_version=1.0.0
project_jdk=17

3、
ext.versions = [                // 定义全部的依赖库版本号
    spring                          : '6.0.0-M3',      // Spring版本号
    junit                           : '5.8.2',          // Junit版本编号
    junitPlatform                   : '1.8.2',          // Junit版本编号
    slf4j                           : '1.7.36',         // SLF4J日志标准的版本号
    logback                         : '1.2.11',         // 日志实现标准的版本号,
    annotationApi                   : '2.1.0',
    mysql                           : '8.0.29',          // MySQL驱动版本
    hikaricp                        : '5.0.1'           // HikariCP连接池组件版本
]
ext.libraries = [            // 依赖库引入配置
    // 以下的配置为Spring基础以来支持包
    'spring-context'                :  "org.springframework:spring-context:${versions.spring}",
    'spring-core'                   :  "org.springframework:spring-core:${versions.spring}",
    'spring-context-support'        :  "org.springframework:spring-context-support:${versions.spring}",
    'spring-beans'                  :  "org.springframework:spring-beans:${versions.spring}",
    // 以下的配置为JUnit5相关的测试环境依赖
    'junit-bom'                     : "org.junit:junit-bom:${versions.junit}",
    'junit-jupiter-api'             : "org.junit.jupiter:junit-jupiter-api:${versions.junit}",
    'junit-vintage-engine'          : "org.junit.vintage:junit-vintage-engine:${versions.junit}",
    'junit-jupiter-engine'          : "org.junit.jupiter:junit-jupiter-engine:${versions.junit}",
    'junit-platform-launcher'       : "org.junit.platform:junit-platform-launcher:${versions.junitPlatform}",
    'spring-test'                   : "org.springframework:spring-test:${versions.spring}",
    // 以下的配置为Logback日志组件所需要的依赖库
    'slf4j-api'                     : "org.slf4j:slf4j-api:${versions.slf4j}",
    'logback-classic'               : "ch.qos.logback:logback-classic:${versions.logback}",
    // 配置JSR-250依赖库
    'jakarta.annotation-api'        : "jakarta.annotation:jakarta.annotation-api:${versions.annotationApi}",
    // 配置Spring-AOP核心依赖库
    'spring-aop'                    : "org.springframework:spring-aop:${versions.spring}",
    'spring-aspects'                : "org.springframework:spring-aspects:${versions.spring}",
    // 添加JDBC开发有关的核心依赖库
    'spring-jdbc'                   : "org.springframework:spring-jdbc:${versions.spring}",
    'mysql-connector-java'          : "mysql:mysql-connector-java:${versions.mysql}",
    'HikariCP'                      : "com.zaxxer:HikariCP:${versions.hikaricp}"
]

4、
group project_group  					// 组织名称
version project_version 					// 项目版本
def env = System.getProperty("env") ?: 'dev'  		// 获取env环境属性
subprojects {  						// 配置子项目
    apply plugin: 'java'  					// 子模块插件
    sourceCompatibility = project_jdk 			// 源代码版本
    targetCompatibility = project_jdk 			// 生成类版本
    repositories {      					// 配置Gradle仓库
        mavenLocal()					// Maven本地仓库
        maven{ 						// 阿里云仓库
            allowInsecureProtocol = true
            url 'http://maven.aliyun.com/nexus/content/groups/public/'}
        maven { 						// spring官方仓库
            allowInsecureProtocol = true
            url 'https://repo.spring.io/libs-milestone'
        }
        mavenCentral()					// Maven远程仓库
    }
    dependencies { 					// 公共依赖库管理
        testImplementation(enforcedPlatform("org.junit:junit-bom:5.8.1"))
        testImplementation('org.junit.jupiter:junit-jupiter-api:5.8.1')
        testImplementation('org.junit.vintage:junit-vintage-engine:5.8.1')
        testImplementation('org.junit.jupiter:junit-jupiter-engine:5.8.1')
        testImplementation('org.junit.platform:junit-platform-launcher:1.8.1')
        testImplementation('org.springframework:spring-test:6.0.0-M3')
        implementation('org.springframework:spring-context:6.0.0-M3')
        implementation('org.springframework:spring-core:6.0.0-M3')
        implementation('org.springframework:spring-beans:6.0.0-M3')
        implementation('org.springframework:spring-context-support:6.0.0-M3')
        implementation('org.springframework:spring-aop:6.0.0-M3')
        implementation('org.springframework:spring-aspects:6.0.0-M3')
        implementation('javax.annotation:javax.annotation-api:1.3.2')
        implementation('org.slf4j:slf4j-api:1.7.32') 	// 日志处理标准
        implementation('ch.qos.logback:logback-classic:1.2.7')        // 日志标准实现
    }
    sourceSets { 						// 源代码目录配置
        main { 						// main及相关子目录配置
            java { srcDirs = ['src/main/java'] }
            resources { srcDirs = ['src/main/resources', "src/main/profiles/$env"] }
        }
        test { 						// test及相关子目录配置
            java { srcDirs = ['src/test/java'] }
            resources { srcDirs = ['src/test/resources'] }
        }
    }
    test {  						// 配置测试任务
        useJUnitPlatform()					// 使用JUnit测试平台
    }
    task sourceJar(type: Jar, dependsOn: classes) {     	// 源代码打包任务
        archiveClassifier = 'sources'   			// 设置文件后缀
        from sourceSets.main.allSource  			// 所有源代码读取路径
    }
    task javadocTask(type: Javadoc) {  			// JavaDoc文档打包任务
        options.encoding = 'UTF-8'     			// 设置文件编码
        source = sourceSets.main.allJava  			// 定义所有Java源代码
    }
    task javadocJar(type: Jar, dependsOn: javadocTask) {   	// 先生成JavaDoc再打包
        archiveClassifier = 'javadoc' 			// 文件标记类型
        from javadocTask.destinationDir  			// 通过Javadoc任务找到目标路径
    }
    tasks.withType(Javadoc) {    				// 文档编码配置
        options.encoding = 'UTF-8'   			// 定义编码
    }
    tasks.withType(JavaCompile) {    			// 编译编码配置
        options.encoding = 'UTF-8'  			// 定义编码
    }
    artifacts {   						// 最终打包的操作任务
        archives sourceJar      				// 源代码打包
        archives javadocJar     				// javadoc打包
    }
    gradle.taskGraph.whenReady {  				// 在所有的操作准备好后触发
        tasks.each { task -> 				// 找出所有任务
            if (task.name.contains('test')) {  		// 如果发现有test任务
                task.enabled = true  			// 执行测试任务
            }
        }
    }
    [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'// 编码配置
}





5、
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


6、
// https://mvnrepository.com/artifact/org.springframework/spring-webmvc
implementation('org.springframework:spring-web:6.0.0-M3')
implementation('org.springframework:spring-webmvc:6.0.0-M3')
implementation('jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:3.0.0')
implementation('org.mortbay.jasper:taglibs-standard:10.0.2')
compileOnly('jakarta.servlet.jsp:jakarta.servlet.jsp-api:3.1.0')
compileOnly('jakarta.servlet:jakarta.servlet-api:5.0.0')

7、
    'spring-web'                    : "org.springframework:spring-web:${versions.spring}",
    'spring-webmvc'                 : "org.springframework:spring-webmvc:${versions.spring}",
    'jstl-api'                      : "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:${versions.jstl}",
    'taglibs-standard'              : "org.mortbay.jasper:taglibs-standard:${versions.taglib}",
    'jsp-api'                        : "jakarta.servlet.jsp:jakarta.servlet.jsp-api:${versions.jsp}",
    'servlet-api'                   : "jakarta.servlet:jakarta.servlet-api:${versions.servlet}",

8、
https://tomcat.apache.org/
现在最新版本的Tomcat 10(你随意下载任何的版本都可以,但是一定要是10)

配置 SpringMVC 开发环境

Spring 的核心机制在于 Spring 容器的启动,传统的 Java 应用开发中,使用 AnnotationConfigApplicationContext(注解配置启动上下文)或者使用 ClassPathXmlApplicationContext(XML 配置启动上下文)即可,而对于所有的 WEB 项目,由于其是运行在 WEB 容器之中,所以就需要通过 WEB 容器组件的支持来实现 Spring 容器的启动

1.spring/spring-base.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-4.3.xsd">
    <context:annotation-config/>                    <!-- 注解配置支持 -->
    <context:component-scan base-package="com.yix.service"/>    <!-- 扫描包 -->
</beans>
2.spring/spring-mvc.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
            https://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    <context:component-scan base-package="com.yix.action"/>    <!-- 扫描包 -->
    <mvc:annotation-driven/>                        <!-- 注解配置 -->
    <mvc:default-servlet-handler/>                <!-- 请求分发处理 -->
</beans>
3.web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
                      https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0"
         metadata-complete="true">
    <context-param>
        <param-name>contextConfigLocation</param-name> <!-- 固定的配置项标记 -->
        <param-value>classpath:spring/spring-base.xml</param-value> <!-- Spring核心配置文件 -->
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>SpringMVCServlet</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>SpringMVCServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <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>
    </filter>
    <filter-mapping>
        <filter-name>EncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

SpringMVC 编程入门

SpringMVC 核心开发架构

  • 在 SpringMVC 的开发之中,用户所发送的所有请求都会提交到 DispatcherServlet 类之中,而后由该 Servet 根据请求路径,将用户请求分发到不同的 Action 控制器处理类中而后再根据需要调用业务层的相关处理方法
1、
package com.yootk.service;

public interface IMessageService {
    public String echo(String msg); // 消息处理
}


2、
package com.yootk.service.impl;

import com.yootk.service.IMessageService;
import org.springframework.stereotype.Service;

@Service // Bean注册
public class MessageServiceImpl implements IMessageService {
    @Override
    public String echo(String msg) {
        return "【ECHO】" + msg;
    }
}


3、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller // 控制层注解
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @RequestMapping("/pages/message/echo") // 映射地址
    public ModelAndView echo(@RequestParam("msg") String msg) {
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        ModelAndView mav = new ModelAndView("/pages/message/show.jsp"); // 视图路径
        // 在传统的MVC之中控制层需要传递属性到视图层,所以此时利用MAV包装处理
        mav.addObject("echoMessage", this.messageService.echo(msg)); // 业务调用
        return mav; // 路径跳转
    }
}


4、
<%@ page pageEncoding="UTF-8"%>
<html>
    <head><title>SpringMVC开发框架</title></head>
    <body>
        <h1>${echoMessage}</h1>
    </body>
</html>

5、
localhost/pages/message/echo?msg=www.yootk.com

ModelAndView

ModelAndView

  • 在 MVC 的标准设计过程之中,所有的用户请求要通过控制层进行接收,而后控制层根据用户请求的类型调用不同的模型层进行请求处理,最终再由控制层将模型层的处理结果交由视图层进行展示

1、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.HashMap;
import java.util.Map;

@Controller // 控制层注解
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @RequestMapping("/pages/message/echo") // 映射地址
    public ModelAndView echo(String msg) {
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        ModelAndView mav = new ModelAndView(); // 结果的封装
        mav.setViewName("/pages/message/show.jsp"); // 设置视图名称
        // 在传统的MVC之中控制层需要传递属性到视图层,所以此时利用MAV包装处理
        mav.addObject("echoMessage", this.messageService.echo(msg)); // 业务调用
        // 业务层有很多时候也是会返回Map集合的,那么常规的做法是将Map集合迭代保存在属性范围之中
        Map<String, Object> result = new HashMap<>();
        result.put("yootk", "沐言科技:www.yootk.com");
        result.put("edu", "李兴华编程训练营:edu.yootk.com");
        mav.addAllObjects(result); // 保存结果
        return mav; // 路径跳转
    }
}


2、
<%@ page pageEncoding="UTF-8"%>
<html>
    <head><title>SpringMVC开发框架</title></head>
    <body>
        <h1>回应消息:${echoMessage}</h1>
        <h1>YOOTK消息:${yootk}</h1>
        <h1>EDU消息:${edu}</h1>
    </body>
</html>

3、
http://localhost/pages/message/echo?msg=www.yootk.com

4、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.HashMap;
import java.util.Map;

@Controller // 控制层注解
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @RequestMapping("/pages/message/echo") // 映射地址
    public String echo(String msg, Model model) { // 通过Model传递属性
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        // 在传统的MVC之中控制层需要传递属性到视图层,所以此时利用MAV包装处理
        model.addAttribute("echoMessage", this.messageService.echo(msg)); // 业务调用
        // 业务层有很多时候也是会返回Map集合的,那么常规的做法是将Map集合迭代保存在属性范围之中
        Map<String, Object> result = new HashMap<>();
        result.put("yootk", "沐言科技:www.yootk.com");
        result.put("edu", "李兴华编程训练营:edu.yootk.com");
        model.addAllAttributes(result); // 保存结果
        return "/pages/message/show.jsp"; // 路径跳转
    }
}

WebApplicationContext

ContextLoaderistener

  • 在 Spring 之中容器的应用上下文是通过 ApplicationContext 接口表示的,由于 SpringWEB 需要运行在 WEB 容器之中,所以又提供了一个 WebApplicationContext 子接口,而在使用 ContextLoaderListener 监听器时,实际上就是根据 XML 配置文件创建了一个 XmlWebApplicationContext 对象实例,从而实现 Spring 容器的启动

WebApplicationContext 继承结构

  • 在 WEB 中启动 Spring 容器的核心在于 WebApplicationContext 接口,在 SpringWEB 设计时考虑到不同配置环境的需要,提供了基于 XML 配置方式启动的或者基于 Annotation 注解启动的配置类
1、
ContextLoaderListener-> contextInitialized{initWebApplicationContext}
ContextLoader

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
   if (servletContext.getAttribute(WebApplicationContext.
          ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  // 存在有指定的属性
      throw new IllegalStateException(// 提醒用户有可能进行了多次配置
 "Cannot initialize context because there is already a root application context present - " +
 "check whether you have multiple ContextLoader* definitions in your web.xml!");
   }
   servletContext.log("Initializing Spring root WebApplicationContext");
   Log logger = LogFactory.getLog(ContextLoader.class);
   if (logger.isInfoEnabled()) { // 日志的处理
      logger.info("Root WebApplicationContext: initialization started");
   }
   long startTime = System.currentTimeMillis();// 启动的时间
   try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
         this.context = createWebApplicationContext(servletContext); // 创建上下文
      }
      if (this.context instanceof ConfigurableWebApplicationContext cwac && !cwac.isActive()) {
         // The context has not yet been refreshed -> provide services such as
         // setting the parent context, setting the application context id, etc
         if (cwac.getParent() == null) { // 父容器为空
            // The context instance was injected without an explicit parent ->
            // determine parent for root web application context, if any.
            ApplicationContext parent = loadParentContext(servletContext);
            cwac.setParent(parent); // 父容器配置
         }
         configureAndRefreshWebApplicationContext(cwac, servletContext); // 刷新处理
      }
      servletContext.setAttribute(WebApplicationContext.
              ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); // 属性设置
      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
         currentContext = this.context;
      }
      else if (ccl != null) {
         currentContextPerThread.put(ccl, this.context);
      }
      if (logger.isInfoEnabled()) {
         long elapsedTime = System.currentTimeMillis() - startTime;
         logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
      }
      return this.context;
   }
   catch (RuntimeException | Error ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext
               .ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
   }
}


2、
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
   if (ObjectUtils.identityToString(wac).equals(wac.getId())) { // 判断应用的ID
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); // 获取应用ID参数
      if (idParam != null) { // 参数不为空
         wac.setId(idParam); // 内容设置
      }
      else { // Generate default id...,如果用户没有配置该参数就自动生成一个
         wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
               ObjectUtils.getDisplayString(sc.getContextPath()));
      }
   }
   wac.setServletContext(sc); // WebApplicationContext中保存ServletContext实例
   String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); // 配置文件
   if (configLocationParam != null) { // 配置文件路径不为空
      wac.setConfigLocation(configLocationParam); // 配置文件路径的保存
   }
   // The wac environment's #initPropertySources will be called in any case when the context
   // is refreshed; do it eagerly here to ensure servlet property sources are in place for
   // use in any post-processing or initialization that occurs below prior to #refresh
   ConfigurableEnvironment env = wac.getEnvironment();// 配置环境的定义
   if (env instanceof ConfigurableWebEnvironment cwe) { // 实例判断
      cwe.initPropertySources(sc, null); // 初始化PropertySources属性源
   }
   customizeContext(sc, wac);
   wac.refresh();// 刷新上下文
}

WebApplicationInitializer

动态注册配置

  • 为了进一步简化对 XML 配置文件的依赖,在 SpringWEB 中提供了注解启动 WEB 应用上下文的支持,而该操作的实现主要依靠了 Servlet 之中的组件动态注册机制来实现的

1、
package com.yootk.web.config;

import jakarta.servlet.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.DispatcherServlet;

import java.util.EnumSet;

public class StartWEBApplication implements WebApplicationInitializer { // 启动类
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // 在该类之中可以直接获取到当前的ServletContext接口实例(WEB上下文对象)
        // 当前为WEB应用,所以要启动的是WebApplicationContext接口的实例
        AnnotationConfigWebApplicationContext springContext =
                new AnnotationConfigWebApplicationContext(); // 注解上下文启动
        springContext.scan("com.yootk.action", "com.yootk.service"); // 配置扫描包
        springContext.refresh(); // 刷新上下文
        // 在SpringMVC里面需要设置有一个DispatcherServlet分发处理类
        ServletRegistration.Dynamic servletRegistration =
                servletContext.addServlet("DispatcherServlet",
                        new DispatcherServlet(springContext)); // 动态注册组件
        servletRegistration.setLoadOnStartup(1); // 容器启动的时候加载
        servletRegistration.addMapping("/"); // 分发处理路径
        // 在整个的WEB里面除了包含有分发Servlet之外还需要配置编码过滤器
        FilterRegistration.Dynamic filterRegistration =
                servletContext.addFilter("EncodingFilter",
                        new CharacterEncodingFilter()); // 编码过滤
        filterRegistration.setInitParameter("encoding", "UTF-8"); // 编码配置
        filterRegistration.addMappingForUrlPatterns(
                EnumSet.of(DispatcherType.FORWARD, DispatcherType.REQUEST),
                false, "/*");
    }
}

AbstractAnnotationConfigDispatcherServletInitializer

AbstractAnnotationConfigDispatcherServletInitializer

  • 使用自定义的 WebApplicationlnitializer 接口子类时,所有需要配置的 Servlet 和 Filter 可以直接在 onStartup()方法中进行定义,考虑到更加规范化的开发设计需要 SpringWEB 又提供了 AbstractAnnotationConfigDispatcherServletInitializer 抽象类

自定义 SpringWEB 配置类

  • 在配置类继承 AbstractAnnotationConfigDispatcherServletInitializer 抽象类之中需要在该配置类中覆写指定的方法,以实现 Spring 配置类、SpringWEB 配置类 DispatcherServlet 访问路径以及过滤器的配置定义,如果要基于扫描的方式启动 Spring 容器,就可以在相应的配置类上使用“@ComponentScan”注解进行定义
1、
AbstractAnnotationConfigDispatcherServletInitializer

2、
package com.yootk.context.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.yootk.service,com.yootk.dao,com.yootk.config") // Spring扫描包
public class SpringApplicationContextConfig { // Spring容器的配置类
}


3、
package com.yootk.context.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.yootk.action")
public class SpringWEBContextConfig { // WEB上下文配置类
}


4、
package com.yootk.web.config;

import com.yootk.context.config.SpringApplicationContextConfig;
import com.yootk.context.config.SpringWEBContextConfig;
import jakarta.servlet.Filter;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class StartWEBApplication extends
        AbstractAnnotationConfigDispatcherServletInitializer { // 启动类
    @Override
    protected Class<?>[] getRootConfigClasses() { // 获取Spring容器的启动类
        return new Class[] {SpringApplicationContextConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() { // SpringWeb容器启动类
        return new Class[] {SpringWEBContextConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] {"/"}; // DispatcherServlet分发处理
    }

    @Override
    protected Filter[] getServletFilters() { // 配置过滤器
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8"); // 编码设置
        encodingFilter.setForceEncoding(true); // 强制编码
        return new Filter[] {encodingFilter};
    }
}


5、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;

@Controller // 控制层注解
public class ContextAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(ContextAction.class);
    @Autowired
    private WebApplicationContext webApplicationContext; // WEB容器
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @RequestMapping("/pages/context/show") // 映射地址
    public String show() { // 通过Model传递属性
        LOGGER.info("【子容器】ID:{}、NAME:{}", this.webApplicationContext.getId(),
                this.webApplicationContext.getApplicationName());
        LOGGER.info("【父容器】ID:{}、NAME:{}", this.webApplicationContext.getParent().getId(),
                this.webApplicationContext.getParent().getApplicationName());
        return null; // 路径跳转
    }
}


6、
http://localhost/pages/context/show

@RequestMapping 注解

@RequestMapping 路径绑定

  • 为了实现项目功能的区分,一般都会使用不同的控制器处理类完成不同的业务调用,同时每一个业务层的处理方法都需要搭配“@RequestMapping’”注解才可以对外提供服务支持

配置 HTTP 请求模式

  • 在默认情况下使用“@RequestMapping 进行访问时,如果没有设置 method 属性项实际上会绑定五种 HTTP 请求方式,分别是:GET、POST、PUT、PATCH、DELETE。如果要想进行更加准确的 HTTP 请求方式的绑定,则可以采用具体形式的注解

某一个路径只允许 GET 请求访问,可以使用“@GetMapping

配置父子路径

  • 在一个控制层处理类中往往会定义有若干个不同的处理方法,而这些方法一般都会统定义父路保存在一个父路径下这时就可以在类声明处使用“@RequestMapping 径,而在方法上使用“@RequestMapping”定义子路径,而这些路径在 DispatcherServlet 处理时会合并为一个完整路径
1、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;

import java.util.HashMap;
import java.util.Map;

@Controller // 控制层注解
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @RequestMapping(value = "/pages/message/echo", method = RequestMethod.POST) // 映射地址
    public String echo(String msg, Model model) { // 通过Model传递属性

        SpringServletContainerInitializer s;

        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        // 在传统的MVC之中控制层需要传递属性到视图层,所以此时利用MAV包装处理
        model.addAttribute("echoMessage", this.messageService.echo(msg)); // 业务调用
        // 业务层有很多时候也是会返回Map集合的,那么常规的做法是将Map集合迭代保存在属性范围之中
        Map<String, Object> result = new HashMap<>();
        result.put("yootk", "沐言科技:www.yootk.com");
        result.put("edu", "李兴华编程训练营:edu.yootk.com");
        model.addAllAttributes(result); // 保存结果
        return "/pages/message/show.jsp"; // 路径跳转
    }
}


2、


3、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.SpringServletContainerInitializer;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;

import java.util.HashMap;
import java.util.Map;

@Controller // 控制层注解
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @PostMapping("/pages/message/echo") // 映射地址
    public String echo(String msg, Model model) { // 通过Model传递属性
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        // 在传统的MVC之中控制层需要传递属性到视图层,所以此时利用MAV包装处理
        model.addAttribute("echoMessage", this.messageService.echo(msg)); // 业务调用
        // 业务层有很多时候也是会返回Map集合的,那么常规的做法是将Map集合迭代保存在属性范围之中
        Map<String, Object> result = new HashMap<>();
        result.put("yootk", "沐言科技:www.yootk.com");
        result.put("edu", "李兴华编程训练营:edu.yootk.com");
        model.addAllAttributes(result); // 保存结果
        return "/pages/message/show.jsp"; // 路径跳转
    }
    @PostMapping("/pages/message/add")
    public String add(){
        return null;
    }
    @PostMapping("/pages/message/edit")
    public String edit(){
        return null;
    }
    @PostMapping("/pages/message/delete")
    public String delete(){
        return null;
    }
}


4、

package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("echo") // 映射地址
    public String echo(String msg, Model model) { // 通过Model传递属性
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        // 在传统的MVC之中控制层需要传递属性到视图层,所以此时利用MAV包装处理
        model.addAttribute("echoMessage", this.messageService.echo(msg)); // 业务调用
        // 业务层有很多时候也是会返回Map集合的,那么常规的做法是将Map集合迭代保存在属性范围之中
        Map<String, Object> result = new HashMap<>();
        result.put("yootk", "沐言科技:www.yootk.com");
        result.put("edu", "李兴华编程训练营:edu.yootk.com");
        model.addAllAttributes(result); // 保存结果
        return "/pages/message/show.jsp"; // 路径跳转
    }
    @PostMapping("add")
    public String add(){
        return null;
    }
    @PatchMapping("edit")
    public String edit(){
        return null;
    }
    @DeleteMapping("delete")
    public String delete(){
        return null;
    }
}

SpringMVC 与表单提交

表单提交请求参数

  • 在动态 WEB 项目开发中,请求参数一般都会采用表单的形式进行提交所以在标准的 WEB 架构开发中,用户首先应该通过控制层提供的路径访问表单页面,而后再通过表单将请求数据提交到控制层之中
1、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("input")
    public String input() {
        // 由于此处不需要进行任何属性的传递,所以直接返回字符串的路径即可
        return "/pages/message/input.jsp";
    }
    @PostMapping("echo") // 映射地址
    public ModelAndView echo(String msg) { // 通过Model传递属性
        ModelAndView mav = new ModelAndView("/pages/message/show.jsp");
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        mav.addObject("echoMessage", this.messageService.echo(msg)); // 业务调用
        return mav; // 路径跳转
    }
}


2、
<%@ page pageEncoding="UTF-8"%>
<html>
    <head><title>SpringMVC开发框架</title></head>
    <body>
        <form action="${request.contextPath}/pages/message/echo" method="post">
            请输入信息:<input type="text" name="msg" value="www.yootk.com">
            <button type="submit">发送</button>
        </form>
    </body>
</html>

3、
<%@ page pageEncoding="UTF-8"%>
<html>
    <head><title>SpringMVC开发框架</title></head>
    <body>
        <h1>回应消息:${echoMessage}</h1>
    </body>
</html>

4、
http://localhost/pages/message/input

@RequestParam

@RequestParam

  • 在 SpringMVC 的开发中,用户所发送的请求参数,可以直接利用控制层方法的参数进行接收,在接收时会根据方法的参数名称自动匹配,并依据参数的类型实现请求参数数据的转换处理。而如果此时用户发送的请求参数名称和控制层方法的接收参数名称不匹配时,也可以利用“@RequestParam”注解来实现接收参数的定义
1、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("input")
    public String input() {
        // 由于此处不需要进行任何属性的传递,所以直接返回字符串的路径即可
        return "/pages/message/input.jsp";
    }
    @GetMapping("echo") // 映射地址
    public ModelAndView echo(@RequestParam("message") String msg) {
        ModelAndView mav = new ModelAndView("/pages/message/show.jsp");
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        mav.addObject("echoMessage", this.messageService.echo(msg)); // 业务调用
        return mav; // 路径跳转
    }
}



2、
http://localhost/pages/message/echo?message=www.yootk.com

3、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("input")
    public String input() {
        // 由于此处不需要进行任何属性的传递,所以直接返回字符串的路径即可
        return "/pages/message/input.jsp";
    }
    @GetMapping("echo") // 映射地址
    public ModelAndView echo(@RequestParam(value = "message", defaultValue = "edu.yootk.com") String msg) {
        ModelAndView mav = new ModelAndView("/pages/message/show.jsp");
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        mav.addObject("echoMessage", this.messageService.echo(msg)); // 业务调用
        return mav; // 路径跳转
    }
}


4、
http://localhost/pages/message/echo

@PathVariable

路径参数

  • 除了使用传统的方式进行 HTTP 访问参数的接收之外,在 SpringMVC 中又扩展了一种路径参数的支持,即:开发者可以采用“/控制器路径参数/参数”的形式进行请求参数的传递,而在接收参数时就需要通过@PathVariable 注解进行定义
1、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("echo/{title}/{info}/{level}") // 映射地址
    public ModelAndView echo(
            @PathVariable(name="title") String title,
            @PathVariable("info") String msg,
            @PathVariable("level") int level) { // 自动实现数据转型
        LOGGER.info("消息回应处理,title = {}", title); // 日志输出
        LOGGER.info("消息回应处理,msg = {}", msg); // 日志输出
        LOGGER.info("消息回应处理,level = {}", level); // 日志输出
        return null; // 路径跳转
    }
}


2、
http://localhost/pages/message/echo/yootk/沐言科技:www.yootk.com/1

@MatrixVariable

@MatrixVariable

  • 矩阵参数传递是一种 SpringMVC 内部参数接收的扩展机制,在进行参数传递时,可以在完整的请求路径之后,采用“参数名称=内容; 参数名称=内容;..."的语法格式传递多个参数项,而这些参数项的接收就需要通过“@MatrixVariable”注解来完成。在默认情况下 SpringMVC 未开启这种参数传递机制,所以需要开发人员手工开启后才可以使用,下面通过具体的代码演示这一操作机制的实现。
1、
package com.yootk.context.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

@Configuration
@EnableWebMvc // 加入此注解才表示WEBMVC配置类有效
@ComponentScan("com.yootk.action")
public class SpringWEBContextConfig implements WebMvcConfigurer { // WEB上下文配置类
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) { // 矩阵参数的配置
        var urlPathHelper = new UrlPathHelper(); // 配置一个对象
        urlPathHelper.setRemoveSemicolonContent(false); // 启用矩阵参数接收
        configurer.setUrlPathHelper(urlPathHelper); // 配置路径参数
    }
}


2、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("echo/{mid}") // 映射地址
    public ModelAndView echo(
            @PathVariable("mid") String mid, // 绑定路径参数
            @MatrixVariable("title") String title,
            @MatrixVariable("content") String content,
            @MatrixVariable("level") int level) { // 自动实现数据转型
        LOGGER.info("消息回应处理,mid = {}", mid); // 日志输出
        LOGGER.info("消息回应处理,title = {}", title); // 日志输出
        LOGGER.info("消息回应处理,content = {}", content); // 日志输出
        LOGGER.info("消息回应处理,level = {}", level); // 日志输出
        return null; // 路径跳转
    }
}


3、
http://localhost/pages/message/echo/1;title=yootk;content=www.yootk.com;level=1

4、
package com.yootk.action;

import com.yootk.service.IMessageService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.MatrixVariable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import java.util.Map;

@Controller // 控制层注解
@RequestMapping("/pages/message/") // 父路径
public class MessageAction {
    private static final Logger LOGGER = LoggerFactory.getLogger(MessageAction.class);
    @Autowired
    private IMessageService messageService; // 业务接口实例
    @GetMapping("echo/{mid}") // 映射地址
    public ModelAndView echo(
            @PathVariable("mid") String mid, // 绑定路径参数
            @MatrixVariable("title") String title,
            @MatrixVariable("content") String content,
            @MatrixVariable("level") int level) { // 自动实现数据转型
        LOGGER.info("消息回应处理,mid = {}", mid); // 日志输出
        LOGGER.info("消息回应处理,title = {}", title); // 日志输出
        LOGGER.info("消息回应处理,content = {}", content); // 日志输出
        LOGGER.info("消息回应处理,level = {}", level); // 日志输出
        return null; // 路径跳转
    }
    @GetMapping("echo_map/{.*}") // 映射地址
    public ModelAndView echoMap(
            @MatrixVariable Map<String, String> params) { // 自动实现数据转型
        for (Map.Entry<String, String> entry : params.entrySet()) {
            LOGGER.info("【消息参数】{} = {}", entry.getKey(), entry.getValue());
        }
        return null; // 路径跳转
    }
}


5、
http://localhost/pages/message/echo_map/title=yootk;content=www.yootk.com;level=1

demo


上次编辑于: