您的位置 首页 java

Java单体应用 – 常用框架 – 07.Spring MVC(iot-admin3)

1.概述

Spring MVC 也叫 Spring Web MVC ,属于展示层框架。Spring MVC 是 Spring 框架的一部分。

Spring Web MVC 框架提供了 mvc (模型 – 视图 – 控制器) 架构和用于开发灵活和松散耦合的 Web 应用程序的组件。 MVC 模式导致应用程序的不同方面(输入逻辑,业务逻辑和 UI 逻辑)分离,同时提供这些元素之间的松散耦合。

  • 模型 (Model):封装了应用程序数据,通常它们将由 POJO 类组成。
  • 视图 (View):负责渲染模型数据,一般来说它生成客户端浏览器可以解释 HTML 输出。
  • 控制器 (Controller):负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。

Dispatcher servlet 组件类

Spring Web MVC 框架是围绕 DispatcherServlet 设计的,它处理所有的 HTTP 请求 响应 Spring Web MVC DispatcherServlet 的请求处理工作流如下图所示:

DispatcherServlet – 光束云 – work100.net

以下是对应于到 DispatcherServlet 的传入 HTTP 请求的事件顺序:

  • 在接收到 HTTP 请求后,DispatcherServlet 会查询 HandlerMapping 以调用相应的 Controller。
  • Controller 接受请求并根据使用的 GET 或 POST 方法调用相应的服务方法。 服务方法将基于定义的业务逻辑设置模型数据,并将视图名称返回给 DispatcherServlet。
  • DispatcherServlet 将从 ViewResolver 获取请求的定义视图。
  • 当视图完成,DispatcherServlet 将模型数据传递到最终的视图,并在浏览器上呈现。

所有上述组件,即: HandlerMapping、Controller 和 ViewResolver 是 WebApplicationContext 的一部分,它是普通 ApplicationContext 的扩展,带有 Web 应用程序所需的一些额外功能。

2.配置和结构重构

我们继续以上一章节 Spring Web 中的案例项目 iot-admin2 为基础,复制一份重命名为 iot-admin3 ,修改 pom.xml <artifactId>iot-admin3</artifactId>

接下来我们使用 Spring MVC 重构 iot-admin3 项目:

2.1.修改POM

spring-web 的依赖改为对 spring-webmvc 的依赖,同时删除对 spring-context 的依赖:

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

完整的 pom.xml 代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="#34;
         xmlns:xsi="#34;
         xsi:schemaLocation=" #34;>
    <modelVersion>4.0.0</modelVersion>

    <groupId>net.work100.training.stage2</groupId>
    <artifactId>iot-admin3</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <spring.version>5.2.3.RELEASE</spring.version>
        <javax.servlet-api.version>4.0.1</javax.servlet-api.version>
        <javax.jstl.version>1.2</javax.jstl.version>
        <junit.version>4.12</junit.version>
        <slf4j.version>1.7.25</slf4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${javax.servlet-api.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>${javax.jstl.version}</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>
</project>  

2.2.配置 web.xml

CharacterEncodingFilter

配置字符集过滤器,用于解决中文编码问题

 <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>  

DispatcherServlet

配置 Spring 的 Servlet 分发器处理所有 HTTP 的请求和响应

 <servlet>
    <servlet-name>springServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:/spring-mvc*.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>springServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>  

load-on-startup 标记容器是否在启动的时候就加载这个servlet,当值为0或者大于0时,表示容器在应用启动时就加载这个servlet;当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载;党委正数时,其值越小,启动该servlet的优先级越高。

完整 web.xml 代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="#34;
         xmlns:xsi="#34;
         xsi:schemaLocation=" #34;
         version="4.0">

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-context*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <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>

    <servlet>
        <servlet-name>springServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:/spring-mvc*.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>  

2.3.配置 spring-mvc.xml

在目录 src/main/resources 下新建文件 spring-mvc.xml ,代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="#34; xmlns:xsi="#34;
       xmlns:context="#34;
       xmlns:mvc="#34;
       xsi:schemaLocation=" 
         
         #34;>

    <description>Spring MVC Configuration</description>

    <!-- 加载配置属性文件 -->
    <context:property-placeholder ignore-unresolvable="true" location="classpath:iot-admin.properties"/>

    <!-- 使用 Annotation 自动注册 Bean,只扫描 @Controller -->
    <context:component-scan base-package="net.work100.training.stage2.iot.admin" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 默认的注解映射的支持 -->
    <mvc:annotation-driven />

    <!-- 定义视图文件解析 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="${web.view.prefix}"/>
        <property name="suffix" value="${web.view.suffix}"/>
    </bean>

    <!-- 静态资源映射 -->
    <mvc:resources mapping="/ static /**" location="/static/" cache-period="31536000"/>
</beans>  

相关配置说明:

  • context:property-placeholder:动态加载属性配置文件以变量的方式引用需要的值
  • context:component-scan:当前配置文件为 MVC 相关,故只需要扫描包含 @Controller 的注解即可,由于 spring-context.xml 配置文件中也配置了包扫描,所以还需要排除 @Controller 的注解扫描。
  • InternalResourceViewResolver:视图文件解析器的一种,用于配置视图资源的路径和需要解释的视图资源文件类型,这里有两个需要配置的属性 prefix(前缀)以及 suffix(后缀)。prefix:配置视图资源路径,如:/WEB-INF/views/suffix:配置视图资源类型,如:.jsp
  • mvc:resources:静态资源映射,主要用于配置静态资源文件存放路径,如:JS、CSS、Image 等

2.4.配置及结构完善

配置 iot-admin.properties

spring-mvc.xml 中,我们配置了 <context:property-placeholder ignore-unresolvable=”true” location=”classpath:iot-admin.properties”/> 用于动态加载属性配置文件,实际开发中我们会将系统所需的一些配置信息封装到 .properties 配置文件中便于统一的管理。

在目录 src/main/resources 下创建一个名为 iot-admin.properties 的配置文件,内容如下:

 #============================#
#==== Framework settings ====#
#============================#

# views path
web.view.prefix=/WEB-INF/views/
web.view.suffix=.jsp
  

重构项目结构

WEB-INF 下新建目录 views ,然后将 .jsp 文件移动到 views 下。

webapp 下新建目录 static ,然后将 assets 文件夹及其所属文件移动到 static 下。

目录结构如下图:

项目结构 – 光束云 – work100

2.5.修改 spring-context.xml

由于 spring-mvc.xml 中已经配置了 @Controller 注解的扫描,而 spring-context.xml 中配置的是扫描全部注解,故在这里需要将 @Controller 注解的扫描配置排除。

修改 spring-context.xml 配置,代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="#34;
       xmlns:xsi="#34;
       xmlns:context="#34;
       xsi:schemaLocation="   #34;>

    <context:annotation-config/>

    <!-- 使用 Annotation 自动注册 Bean,在主容器中不扫描 @Controller 注解,在 SpringMVC 中只扫描 @Controller 注解。-->
    <context:component-scan base-package="net.work100.training.stage2.iot.admin">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>  

3.Controller控制器重构

3.1.修改注解方式

UserDaoImpl 类

修改 UserDaoImpl 类的注解方式,代码如下:

 package net.work100.training.stage2.iot.admin.dao.impl;

import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Repository;

/**
 * <p>Title: UserDaoImpl</p>
 * <p>Description: </p>
 * <p>Url: 
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:23
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始创建
 * -----------------------------------------------
 */@Repository
public class UserDaoImpl implements UserDao {

    private static final Logger logger = LoggerFactory.getLogger(UserDaoImpl.class);

    public User getUser(String loginId, String loginPwd) {
        logger.debug("调用方法 getUser(loginId:{}, loginPwd:{})", loginId, loginPwd);

        // 根据 loginId 查询出用户信息
        User user = getUserByLoginId(loginId);
        if (user != null) {
            // 验证 loginPwd 是否正确(区分大小写)
            if (user.getLoginPwd().equals(loginPwd)) {
                return user;
            }
        }
        return null;
    }


    /**
     * 获取模拟的用户数据
     *
     * @param loginId 登录ID
     * @return
     */    private User getUserByLoginId(String loginId) {
        // 模拟 DB 存在的用户数据
        User dbUser = new User();
        dbUser.setUserName("Xiaojun");
        dbUser.setLoginId("admin");
        dbUser.setLoginPwd("admin");

        // 判断是否存在 loginId 的用户(忽略大小写)
        if (dbUser.getLoginId().equalsIgnoreCase(loginId)) {
            logger.info("匹配上用户:{}", dbUser);
            return dbUser;
        }
        logger.warn("未匹配任何用户,将返回 null");
        return null;
    }
}  

UserServiceImpl 类

修改 UserServiceImpl 类的注解方式,代码如下:

 package net.work100.training.stage2.iot.admin.service.impl;

import net.work100.training.stage2.iot.admin.dao.UserDao;
import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * <p>Title: UserServiceImpl</p>
 * <p>Description: </p>
 * <p>Url: 
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:26
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始创建
 * -----------------------------------------------
 */@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    public User login(String loginId, String loginPwd) {
        return userDao.getUser(loginId, loginPwd);
    }
}  

注解说明

  • @Repository :不需要指定名称,因为只有一个实现类
  • @Service : 不需要指定名称,因为只有一个实现类
  • @Autowired :自动注入,Spring 自动寻找实现类来实例化对象

3.2.重构 Controller 代码

修改 LoginController

修改 LoginController 类,代码如下:

 package net.work100.training.stage2.iot.admin.web.controller;

import net.work100.training.stage2.iot.admin.entity.User;
import net.work100.training.stage2.iot.admin.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * <p>Title: LoginController</p>
 * <p>Description: </p>
 * <p>Url: 
 *
 * @author liuxiaojun
 * @date 2020-02-13 13:28
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-13   liuxiaojun     初始创建
 * -----------------------------------------------
 */@Controller
public class LoginController {

    @Autowired
    private UserService userService;

    /**
     * 登录页面
     *
     * @return
     */    @RequestMapping(value = {"", "login"}, method = RequestMethod.GET)
    public String login() {
        return "login";
    }

    /**
     * 登录逻辑
     *
     * @param loginId  登录ID
     * @param loginPwd 登录密码
     * @return
     */    @RequestMapping(value = "login", method = RequestMethod.POST)
    public String login(@RequestParam(required = true) String loginId, @RequestParam(required = true) String loginPwd) {

        User user = userService.login(loginId, loginPwd);

        // 登录成功
        if (user != null) {
            return "redirect:main";
        }
        // 登录失败
        else {
            return login();
        }
    }
}  

新建 MainController

新建 MainController 类,代码如下:

 package net.work100.training.stage2.iot.admin.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

/**
 * <p>Title: MainController</p>
 * <p>Description: </p>
 *
 * @author liuxiaojun
 * @date 2020-02-20 15:19
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-20   liuxiaojun     初始创建
 * -----------------------------------------------
 */@Controller
public class MainController {

    /**
     * 主页
     *
     * @return
     */    @RequestMapping(value = "main", method = RequestMethod.GET)
    public String main() {
        return "main";
    }
}  

运行

重新启动 Tomcat 验证效果。

注解说明

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

4.使用拦截器

4.1.简介

Spring Web MVC 的处理器拦截器,类似于 Servlet 开发中的过滤器 Filter ,用于对处理器进行预处理和后处理。

4.2.应用场景

  • 日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算 PV(Page View)等
  • 权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面
  • 性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间
  • 通用行为:读取 Cookie 得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取 Locale、Theme 信息等,只要是多个处理器都需要的即可使用拦截器实现

4.3.如何使用拦截器

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

4.4.创建登录拦截器

我们知道对系统的相关操作是需要登录后才可以使用的,当未登录时是无法直接访问需要登录权限的操作的,为了做到这个效果,我们使用登录拦截器来判断用户是否登录,如果用户已登录则放行让用户继续操作,否则就将其跳转到登录页。

net.work100.training.stage2.iot.admin.web 包下新建一个包 interceptor ,然后在 interceptor 包下定义一个名为 LoginInterceptor 的拦截器,代码如下:

 package net.work100.training.stage2.iot.admin.web.interceptor;

import net.work100.training.stage2.iot.admin.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>Title: LoginInterceptor</p>
 * <p>Description: </p>
 * <p>Url: 
 *
 * @author liuxiaojun
 * @date 2020-02-20 16:22
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-20   liuxiaojun     初始创建
 * -----------------------------------------------
 */public class LoginInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("LoginInterceptor");

        User user = (User) request.getSession().getAttribute("user");

        // 未登录
        if (user == null) {
            response.sendRedirect("/login");
        }

        // 为 true 时放行,进入 postHandle
        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}  

LoginInterceptor 拦截器的主要功能是检测用户是否登录,如果未登录则跳转至登录页(/login)

4.5.在 spring-mvc.xml 中配置拦截器

拦截器定义后还需要在 spring-mvc.xml 中配置拦截器,代码如下:

 <mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/login"/>
        <mvc:exclude-mapping path="/static/**"/>
        <bean class="net.work100.training.stage2.iot.admin.web.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>  

相关配置说明:

Java单体应用 - 常用框架 - 07.Spring MVC(iot-admin3)

4.6.完善使用场景

我们实际的使用场景中还应该有个判断逻辑:当用户已经登录了,访问 /login 时,需要将请求跳转至 /main

创建拦截器 PermissionInterceptor

这时我们需要再创建一个拦截器 PermissionInterceptor ,代码如下:

 package net.work100.training.stage2.iot.admin.web.interceptor;

import net.work100.training.stage2.iot.admin.entity.User;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <p>Title: PermissionInterceptor</p>
 * <p>Description: </p>
 * <p>Url: 
 *
 * @author liuxiaojun
 * @date 2020-02-20 21:16
 * ------------------- History -------------------
 * <date>      <author>       <desc>
 * 2020-02-20   liuxiaojun     初始创建
 * -----------------------------------------------
 */public class PermissionInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("PermissionInterceptor");

        return true;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if ("login".equals(modelAndView.getViewName())) {
            User user = (User) request.getSession().getAttribute("user");
            if (user != null) {
                response.sendRedirect("/main");
            }
        }
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}  

修改 spring-mvc.xml 配置

增加 PermissionInterceptor 拦截器的配置,代码如下:

 <!-- 拦截器配置(拦截器的执行顺序为:从下往上) -->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/login"/>
        <mvc:exclude-mapping path="/static/**"/>
        <bean class="net.work100.training.stage2.iot.admin.web.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="net.work100.training.stage2.iot.admin.web.interceptor.PermissionInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>  

拦截器的执行顺序为:从下往上,即先定义的后执行

spring-mvc.xml 完整配置:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="#34; xmlns:xsi="#34;
       xmlns:context="#34;
       xmlns:mvc="#34;
       xsi:schemaLocation=" 
         
         #34;>

    <description>Spring MVC Configuration</description>

    <!-- 加载配置属性文件 -->
    <context:property-placeholder ignore-unresolvable="true" location="classpath:iot-admin.properties"/>

    <!-- 使用 Annotation 自动注册 Bean,只扫描 @Controller -->
    <context:component-scan base-package="net.work100.training.stage2.iot.admin" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- 默认的注解映射的支持 -->
    <mvc:annotation-driven/>

    <!-- 定义视图文件解析 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="${web.view.prefix}"/>
        <property name="suffix" value="${web.view.suffix}"/>
    </bean>

    <!-- 静态资源映射 -->
    <mvc:resources mapping="/static/**" location="/static/" cache-period="31536000"/>

    <!-- 拦截器配置(拦截器的执行顺序为:从下往上) -->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/login"/>
            <mvc:exclude-mapping path="/static/**"/>
            <bean class="net.work100.training.stage2.iot.admin.web.interceptor.LoginInterceptor"/>
        </mvc:interceptor>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <bean class="net.work100.training.stage2.iot.admin.web.interceptor.PermissionInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>
</beans>  

4.7.运行验证

重启 Tomcat ,分别验证 LoginInterceptor PermissionInterceptor 拦截器是否生效。

5.实例源码

实例源码已经托管到如下地址:

文章来源:智云一二三科技

文章标题:Java单体应用 – 常用框架 – 07.Spring MVC(iot-admin3)

文章地址:https://www.zhihuclub.com/186530.shtml

关于作者: 智云科技

热门文章

网站地图