SpringMVC02

1.拦截器

1.1基本概念

SpringMVC 中的Interceptor拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理。比如通过它来进行权限验证,或者是来判断用户是否登陆等操作。对于SpringMVC拦截器的定义方式

有两种:

实现接口:org.springframework.web.servlet.Handlerlnterceptor

继承适配器:org.springframework.web.servlet.handler.HandlerlnterceptorAdapter

1.2 拦截器实现

(1)实现HandleInterceptor接口

复制代码
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*拦截器的实现
* 实现HandlerInterceptor接口
* */
public class MyInterceptor01 implements HandlerInterceptor{
   /*
   *
   * 在目标Handler(方法)执行前 执行
   * 返回true 执行Handler方法
   * 返回false 阻止Handler方法的执行
   * @param request
   * @param response
   * @param Exception
   *
   * */


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("目标Handler执行前MyInterceptor01 --》preHandle方法");
   return true;
    }

    /*
     *
     * 在目标Handler(方法)视图生成前 执行
     * 返回true 执行Handler方法
     * 返回false 阻止Handler方法的执行
     * @param request
     * @param response
     * @param Exception
     *
     * */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("目标Handler视图生成前MyInterceptor01 --》postHandle方法");
    }
    /*
     *
     * 在目标Handler(方法)视图生成后 执行
     * 返回true 执行Handler方法
     * 返回false 阻止Handler方法的执行
     * @param request
     * @param response
     * @param Exception
     *
     * */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("目标Handler视图生成后MyInterceptor01 --》 afterCompletion方法");
    }
}

在servlet-context.xml中配置拦截器

方法一:

复制代码
<!-- 配置拦截器 方式一 -->
<mvc:interceptors>
    <!--使用bean标签定义一个Interceptor
    直接定义在mvc:interceptro软标签中,表示会拦截所有的请求。
    -->
    <bean class="com.xxxx.springmvc.intercreptor.MyInterceptor01"/>
</mvc:interceptors>

方法2:

复制代码
<!--拦截器配置 方法2 推荐使用-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--通过mvc:mapping 配置需要被拦截的资源 支持通配符 支持可配置多个-->
        <!-- “/**”表示拦截所有的请求-->
        <mvc:mapping path="/**"/>
        <!--通过mvc:exclude-mapping 配置不需要拦截的资源 支持通配符 支持可配置多个-->
        <mvc:exclude-mapping path="/url/*"/><!-- "/url/*"表示url路径下的所有资源-->
        <bean class="com.xxxx.springmvc.intercreptor.MyInterceptor01"/>
    </mvc:interceptor>
</mvc:interceptors>

运行结果:

(2)继承HandleInterceptorAdapter

复制代码
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

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

/*
* 拦截器实现
* 继承HandleInterceptorAdapter适配器
* */
public class MyInterceptor02 extends HandlerInterceptorAdapter {
    /*
     *
     * 在目标Handler(方法)执行前 执行
     * 返回true 执行Handler方法
     * 返回false 阻止Handler方法的执行
     * @param request
     * @param response
     * @param Exception
     *
     * */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("目标Handler执行前MyInterceptor01 --》preHandle方法");
        return true;
    }

}

在servlet-context.xml中配置拦截器

复制代码
<mvc:interceptors>

    <mvc:interceptor>
        <!--拦截的信息-->
        <mvc:mapping path="/**"/>
        <!--放行的信息-->
        <mvc:exclude-mapping path="/url/test01"/>
        <mvc:exclude-mapping path="/url/test02"/>
        <bean class="com.xxxx.springmvc.intercreptor.MyInterceptor02"/>

    </mvc:interceptor>

</mvc:interceptors>

(3)多个拦截器实现

复制代码
<mvc:interceptors>
    <mvc:interceptor>
        <!--拦截器链 也叫多个拦截器-->
        <!--如果有多个拦截器满足拦截处理的要求 则会根据配置的先后顺序执行
        先配置的拦截器的preHandle方法先执行
        先配置的拦截器的postHandle afterCompletion 方法后执行
        -->
        <mvc:mapping path="/**"/>
        <bean class="com.xxxx.springmvc.intercreptor.MyInterceptor01"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.xxxx.springmvc.intercreptor.MyInterceptor02"/>
    </mvc:interceptor>
</mvc:interceptors>

1.3 拦截器应用

非法请求拦截

(1)用户控制器

UserInfoController定义

复制代码
*
* 用户模块(不需要拦截)
*
* 用户添加(需要拦截)
* 用户更新(需要拦截)
* 用户删除(需要拦截)
* */
@Controller
@RequestMapping("/userInfo")
public class UserinfoController {
/*
* 用户登录
* */
    @RequestMapping("/login")
    public ModelAndView userLogin(HttpSession session){
    System.out.println("用户登录。。。");
    ModelAndView modelAndView=new ModelAndView();
   //设置视图
    modelAndView.setViewName("success");

    //如果用户登录,则设置用户对象到session作用域中。
        User user=new User();
        user.setId(1);
        user.setUserName("admin");
        user.setUserPassword("123334");
     session.setAttribute("user", user);
    return modelAndView;
}
    /*
     * 用户添加
     * */
    @RequestMapping("/add")
    public ModelAndView userAdd(){
        System.out.println("用户添加。。。");
        ModelAndView modelAndView=new ModelAndView();
        //设置视图
        modelAndView.setViewName("success");
        return modelAndView;
    }

    /*
     * 用户更新
     * */
    @RequestMapping("/update")
    public ModelAndView userUpdate(){
        System.out.println("用户更新。。。");
        ModelAndView modelAndView=new ModelAndView();
        //设置视图
        modelAndView.setViewName("success");
        return modelAndView;
    }
    /*
     * 用户删除
     * */
    @RequestMapping("/delete")
    public ModelAndView userDelete(){
        System.out.println("用户删除。。。");
        ModelAndView modelAndView=new ModelAndView();
        //设置视图
        modelAndView.setViewName("success");
        return modelAndView;
    }
}

(2)在servlet-context.xml配置拦截器(这里只配置了登录的)

复制代码
import com.xxxx.springmvc.po.User;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

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

public class Loginintercreptor extends HandlerInterceptorAdapter {
    /*
    * 在目标方法执行前 执行
    * @param request
    * @param response
    * @param
    * @return
    * @throws Exception
    *
    * */

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //获取session作用域中的user
        User user=(User)request.getSession().getAttribute("user");
        //判断对应的session作用域中的session是否为空
        if(user==null){//如果为空,表示用户未登录
            //拦截请求并拦截转到登录界面
            response.sendRedirect(request.getContextPath()+"/login.jsp");
           //不执行目标方法
           return false;
        }
        return true;
    }
}

1.4 文件上传

1.4.1 环境配置

(1)在pom.xml中添加依赖

复制代码
<!-- 添加 commons-fileupload依赖  -->

 <dependency>
     <groupId>commons-fileupload</groupId>
     <artifactId>commons-fileupload</artifactId>
     <version>1.3.2</version>
 </dependency>

(2)在servlet-context.xml中修改(添加)

复制代码
 <!--文件上传-->
    </bean>
    <bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
   <!--  允许文件上传的最大尺寸 -->
  <property name="maxUploadSize">
   <value>104857600</value>
    </property>
<!--
设置文件放入临时文件夹的最小大小限制。
此值是阈值,低于此值,则保存在内存中,如高于此值,则生成硬盘上的临时文件。-->

   <property name="maxInMemorySize">
     <value>4096</value>
      </property>

</bean>

1.4.2 代码实现

(1)单文件上传

复制代码
页面表单
1.表单的method属性设置为POST
2.表单的enctype属性设置为multipart/form-data
3.input的type属性设置为file,且设置对应的name属性。
复制代码
<form method="post" action="uploadFile" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <button>上传</button>
</form>

代码实现

复制代码
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;

/**
 * 文件上传控制器
 */
@Controller
public class FileController {

    /**
     * 单文件上传
     * @param request HttpServletRequest
     * @param file 上传的文件
     * @return 结果视图名
     */
    @RequestMapping("/uploadFile")
    public String uploadFile(HttpServletRequest request, @RequestParam("file") MultipartFile file) {
        // 判断文件是否为空,如果不为空,则进行对应的文件上传操作
        if (!file.isEmpty()) {
            try {
                // 获取项目所在路径(绝对路径)
                String path = request.getServletContext().getRealPath("/");
                // 设置上传文件存放的目录
                File uploadFile = new File(path + "/upload");
                // 判断目录是否存在,如果不存在则创建目录
                if (!uploadFile.exists()) {
                    uploadFile.mkdir();
                }
                // 获取上传文件的文件名
                String originalFilename = file.getOriginalFilename();
                // 获取上传文件的后缀名
                String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
                // 通过当前系统时间毫秒数生成随机的文件名
                String filename = System.currentTimeMillis() + suffix;
                // 转存文件到指定目录
                file.transferTo(new File(path, filename));
                // 如果上传成功,设置作用域
                request.setAttribute("msg", "文件上传成功");
            } catch (IOException e) {
                e.printStackTrace();
                request.setAttribute("msg", "文件上传失败");
            }
        } else {
            request.setAttribute("msg", "文件不存在");
        }
        return "result";
    }
}

(2)多文件上传

页面表单

复制代码
<form method="post" action="uploadFiles" enctype="multipart/form-data">
    <input type="file" name="files"/>
    <input type="file" name="files"/>
    <button>上传</button>
</form>

代码实现

复制代码
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * 文件上传控制器
 */
@Controller
public class FileController {

    /**
     * 多文件上传
     * @param request HttpServletRequest
     * @param file 上传的文件
     * @return 结果视图名
     */
   
    @RequestMapping("uploadFiles")
    public String uploadFiles(HttpServletRequest request, @RequestParam("files") List<MultipartFile> files) {
        if (files != null && files.size() > 0) {
            for (MultipartFile file : files) {
                saveFile(file, request);
            }
        }
        return "result";
    }
    public void saveFile(MultipartFile file,HttpServletRequest request){
        // 判断文件是否为空,如果不为空,则进行对应的文件上传操作
        if (!file.isEmpty()) {
            try {
                // 获取项目所在路径(绝对路径)
                String path = request.getServletContext().getRealPath("/");
                // 设置上传文件存放的目录
                File uploadFile = new File(path + "/upload");
                // 判断目录是否存在,如果不存在则创建目录
                if (!uploadFile.exists()) {
                    uploadFile.mkdir();
                }
                // 获取上传文件的文件名
                String originalFilename = file.getOriginalFilename();
                // 获取上传文件的后缀名
                String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
                // 通过当前系统时间毫秒数生成随机的文件名
                String filename = System.currentTimeMillis() + suffix;
                // 转存文件到指定目录
                file.transferTo(new File(uploadFile, filename));
                // 如果上传成功,设置作用域
                request.setAttribute("msg", "文件上传成功");
            } catch (IOException e) {
                e.printStackTrace();
                request.setAttribute("msg", "文件上传失败");
            }
        } else {
            request.setAttribute("msg", "文件不存在");
        }

    }
    }
在servlet-context.xml中修改(在拦截器里添加多文件上传的请求允许)
复制代码
<mvc:exclude-mapping path="/uploadFiles"/>

1.5 SSM集成与测试

1.5.1环境配置

(1)IDEA下创建项目

创建Mavenapp项目

(2) 配置pom.xml

复制代码
<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.xxxx</groupId>
  <artifactId>SSM</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>SSM Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

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

  <dependencies>
<!--添加坐标依赖-->
    <!--junit 测试 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <!-- spring核心jar -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!--spring测试jar -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <!--spring jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- spring事物-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- aspectj切面编程的jar-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.9.5</version>
    </dependency>
    <!--c3p0 连接池 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>

    <!--mybatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.3</version>
    </dependency>

    <!-- 添加mybatis与Spring整合的核心包 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.3</version>
    </dependency>

    <!-- mysql 驱动包 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.19</version>
    </dependency>
    <!-- 日志打印相关的jar-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-log4j12</artifactId>
      <version>1.7.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.2</version>
    </dependency>

    <!-- 分页插件 -->
    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.10</version>
    </dependency>

    <!-- spring web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <!-- spring mvc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>

    <!-- web servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
    </dependency>
    <!--    添加json 依赖jar包(注意版本问题)-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.10.0</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.10.0</version>
    </dependency>


    <!--commons-fileupload -->
    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.3.2</version>


    </dependency>
  </dependencies>
  <!--设置资源目录和插件-->
  <build>
    <finalName>ssm</finalName>
    <!-- Maven 项目:如果源代码(src/main/java)存在xml、properties、tld 等文件
     Maven 默认不会自动编译该文件到输出目录,如果要编译源代码中xml properties tld 等文件
     需要显示配置 resources 标签-->
    <resources>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
          <include>**/*.tld</include>
        </includes>
        <filtering>false</filtering>
      </resource>

    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
          <encoding>UTF-8</encoding>

        </configuration>
      </plugin>
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>9.4.27.v20200227</version>
        <configuration>
          <scanIntervalSeconds>10</scanIntervalSeconds>

          <!--设置窗口-->
          <httpConnector>
            <port>8080</port>

          </httpConnector>
          <!--设置项目路径-->
          <webAppConfig>
            <contextPath>/ssm</contextPath>
          </webAppConfig>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-war-plugin</artifactId>
        <version>3.3.1</version>
        <configuration>
          <warSourceDirectory>src/main/webapp</warSourceDirectory>
          <failOnMissingWebXml>false</failOnMissingWebXml>
        </configuration>
      </plugin>
    </plugins>

  </build>
</project>

(3)配置web.xml

位于webapp/WEB-INF/下

复制代码
<!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 id="webApp_ID" version="3.0"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">

  <!-- 启动spring容器 -->
  <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:spring.xml</param-value>
  </context-param>
   <!--设置监听器-->
  <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!--编码过滤UTF-8-->

<filter>
<description>char encoding filter</description>
<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>

 <!--servlet请求分发器-->
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:servlet-context.xml</param-value>
</init-param>
  <!--表示启动容器时初始化该Servlet-->
  <load-on-startup>1</load-on-startup>
</servlet>
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <!--这是拦截请求,"/"表示拦截所有请求,"*.do"拦截所有.do请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

(4)配置servlet-context.xml文件

在src/main/resources下创建此.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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
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.xxxx.ssm.controller"/>

 <!--mvc 注解驱动 并添加json 支持-->
<mvc:annotation-driven>
<mvc:message-converters>
 <!--返回信息为字符串时 处理 -->
<bean
class="org.springframework.http.converter.StringHttpMessageConverter"/>
<!--将对象转换为json 对象 -->
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
    <!--使用默认的Servlet响应静态文件-->
    <mvc:default-servlet-handler/>
    <!--配置视图解析器-->

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀:在WEB-INF目录下的jsp目录下 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
 <!--后缀:以.jsp结尾的资源-->
<property name="suffix" value=".jsp"/>
</bean>
    <!--文件上传-->

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
 <!--允许文件上传的最大尺寸 -->
<property name="maxUploadSize">
<value>104857600</value>
</property>

 <!--设置文件放入临时文件夹的最大大小限制。
 此值是阈值,低于此值,则保存在内存中,如高于此值,则生成硬盘上的临时文件。
-->

 <property name="maxInMemorySize">
 <value>4096</value>
 </property>
</bean>
复制代码
</beans>

(5)配置spring.xml文件

在src/main/resources下创建此.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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
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.xxxx.ssm.controller"/>

 <!--mvc 注解驱动 并添加json 支持-->
<mvc:annotation-driven>
<mvc:message-converters>
 <!--返回信息为字符串时 处理 -->
<bean
class="org.springframework.http.converter.StringHttpMessageConverter"/>
<!--将对象转换为json 对象 -->
<bean
class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
    <!--使用默认的Servlet响应静态文件-->
    <mvc:default-servlet-handler/>
    <!--配置视图解析器-->

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀:在WEB-INF目录下的jsp目录下 -->
<property name="prefix" value="/WEB-INF/jsp/"/>
 <!--后缀:以.jsp结尾的资源-->
<property name="suffix" value=".jsp"/>
</bean>
    <!--文件上传-->

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
 <!--允许文件上传的最大尺寸 -->
<property name="maxUploadSize">
<value>104857600</value>
</property>

 <!--设置文件放入临时文件夹的最大大小限制。
 此值是阈值,低于此值,则保存在内存中,如高于此值,则生成硬盘上的临时文件。
-->

 <property name="maxInMemorySize">
 <value>4096</value>
 </property>
</bean>
</beans>

(6)配置mybatis.xml

在src/main/resources下创建此.xml文件

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
    <package name="com.xxxx.ssm.vo"/>
</typeAliases>
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
</configuration>

(7)配置db.properties

在resources目录下创建对应文件

复制代码
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost: 3306/ssm?
useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
jdbc.username=root
jdbc.password=root

(8)配置log4j.properties

同上,在resources目录下创建对应文件

复制代码
log4j.rootLogger=DEBUG, Console
# Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org. apache. log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.java.sql.ResultSet=INFO
log4j.logger.org.apache=INFO
log4j.logger.java.sql.Connection=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

1.5.2 添加源代码

(1)添加包

在项目的src/main/java下创建对应的包结构

com.xxxx.ssm.controller

com.xxxx.ssm.service

com.xxxx.ssm.mapper

com.xxxx.ssm.dao

com.xxxx.ssm.po

(2)添加User.java

在com.xxxx.ssm.po包下创建javaBean文件User.java(数据库字段如下)

复制代码
import java.util.Date;

public class User {
    private Integer userId;
    private String userName;
    private String userPwd;
    private String userEmail;
    private Date createDate;
    private Date updateDate;

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getUserPwd() {
        return userPwd;
    }

    public void setUserPwd(String userPwd) {
        this.userPwd = userPwd;
    }

    public String getUserEmail() {
        return userEmail;
    }

    public void setUserEmail(String userEmail) {
        this.userEmail = userEmail;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Date getUpdateDate() {
        return updateDate;
    }

    public void setUpdateDate(Date updateDate) {
        this.updateDate = updateDate;
    }
}

(3)添加UserDao.java接口

在com.xxxx.ssm.dao包下创建UserDao.java文件,提供对应的用户详情查询功能。

复制代码
package com.xxxx.ssm.dao;

import com.xxxx.ssm.po.User;

public interface UserDao {
    User queryUserByUserId(Integer userId);
}

(4)添加UserMapper.xml映射文件

com.xxxx.ssm.mapper包下创建UserMapping.sml文件,提供select查询标签配置

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxxx.ssm.dao.UserDao">
<select id="queryUserByUserId" parameterType="int" resultType="com.xxxx.ssm.po.User">
select user_id as userId,user_name as userName,user_pwd as userPwd
from tb_user
where user_id = #{userId}
</select>
        </mapper>

(5)添加UserService.java

在com.xxxx.ssm.service包下创建UserService.java文件

复制代码
import com.xxxx.ssm.dao.UserDao;
import com.xxxx.ssm.po.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {
    @Autowired
    private UserDao userDao;
    public User queryUserByUserId(Integer userId){
        return userDao.queryUserByUserId( userId);
    }
}

(6)添加UserController.java

在com.xxxx.ssm.controller包下创建UserController.java文件

复制代码
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class UserController {
    //注入userService
    @Autowired
    private UserService userService;
    @RequestMapping("/hello")
    public String hello(Model model){
        ModelAndView modelAndView=new ModelAndView();
        //调用UserService层查询方法
        User user=userService.queryUserByUserId(1);
        model.addAttribute("user", user);
      return "hello";

    }
}

结果:

1.6 Restful URL

1.6.1 基本概念

模型-视图-控制器(MVC)是一个众所周知的以设计界面应用程序为基础的设计思想。

Restful风格的API是一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件。它主

要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

在Restful风格中,用户请求的url使用同一个url,用请求方式:get,post,delete,put ... 等方式对请求的

处理方法进行区分,这样可以在前后台分离式的开发中使得前端开发人员不会对请求的资源地址产生混淆和大量的

检查方法名的麻烦,形成一个统一的接口。

在Restful风格中,现有规定如下:

· GET(SELECT):从服务器查询,可以在服务器通过请求的参数区分查询的方式。

· POST(CREATE):在服务器端新建一个资源,调用insert操作。

·PUT(UPDATE):在服务器端更新资源,调用update操作。

· PATCH(UPDATE):在服务器端更新资源(客户端提供改变的属性)。(目前jdk7未实现,tomcat7不支

持)。

· DELETE(DELETE):从服务器端删除资源,调用delete语句。

1.6.2 SpringMVC支持RestFul Url风格设计

案例:如何使用Java构造没有扩展名的RESTful url,如/forms/1?

SpringMVC是通过@RequestMapping 及@PathVariable注解提供的。

通过如@RequestMapping(value="/blog/{id}",method=RequestMethod.DELETE),即可处理/blog/1的delete 请求。

1.6.3 RestFul Url映射地址配置实现

1 准备环境

(1)添加Account

在src/resources/java对应的com.xxxx.ssm.po目录下创建Account.java实体类

复制代码
public class Account {
    private Integer id;
    private String aname;
    private String type;
    private Double money;
    private Integer userId;
    private Date createTime;
    private Date updateTime;
    private String remark;

    public Integer getId() {
        return id;
    }

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

    public String getAname() {
        return aname;
    }

    public void setAname(String aname) {
        this.aname = aname;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public Double getMoney() {
        return money;
    }

    public void setMoney(Double money) {
        this.money = money;
    }

    public Integer getUserId() {
        return userId;
    }

    public void setUserId(Integer userId) {
        this.userId = userId;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }
}

(2)添加AccountDao

在src/resources/java对应的com.xxxx.ssm.dao目录下新建AccountDao.java接口类

复制代码
public interface AccountDao {
    public Account selectById(Integer id);
    public int save(Account account);

    public int update(Account account);


    public int delete(Integer id);
}

(3)添加AccountMapper

在src/resources/java对应的com.xxxx.ssm.mapper目录下新建AccountMapper.xml文件

复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxxx.ssm.dao.AccountDao">
    <resultMap id="BaseResultMap" type="com.xxxx.ssm.po.Account">
        <id column="id" property="id" jdbcType="INTEGER"/>
        <result column="aname" property="aname" jdbcType="VARCHAR"/>
        <result column="type" property="type" jdbcType="VARCHAR"/>
        <result column="money" property="money" jdbcType="DOUBLE"/>
        <result column="user_id" property="userId" jdbcType="INTEGER"/>
        <result column="create_time" property="createTime" jdbcType="DATE"/>
        <result column="update_time" property="updateTime" jdbcType="DATE"/>
        <result column="remark" property="remark" jdbcType="VARCHAR"/>
    </resultMap>

    <sql id="Base_Column_List">
        id, aname, type, money, user_id, create_time, update_time, remark
    </sql>

    <!-- 查询操作 -->
    <select id="selectById" resultMap="BaseResultMap" parameterType="java.lang.Integer">
        select
        <include refid="Base_Column_List"/>
        from tb_account
        where id = #{id,jdbcType=INTEGER}
    </select>

    <!-- 删除操作 -->
    <delete id="delete" parameterType="java.lang.Integer">
        delete from tb_account
        where id = #{id,jdbcType=INTEGER}
    </delete>

    <!-- 添加操作 -->
    <insert id="save" parameterType="com.xxxx.ssm.po.Account">
        insert into tb_account
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="id != null">id,</if>
            <if test="aname != null">aname,</if>
            <if test="type != null">type,</if>
            <if test="money != null">money,</if>
            <if test="userId != null">user_id,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="remark != null">remark,</if>
        </trim>
        <trim prefix="values(" suffix=")" suffixOverrides=",">
            <if test="id != null">#{id,jdbcType=INTEGER},</if>
            <if test="aname != null">#{aname,jdbcType=VARCHAR},</if>
            <if test="type != null">#{type,jdbcType=VARCHAR},</if>
            <if test="money != null">#{money,jdbcType=DOUBLE},</if>
            <if test="userId != null">#{userId,jdbcType=INTEGER},</if>
            <if test="createTime != null">#{createTime,jdbcType=DATE},</if>
            <if test="updateTime != null">#{updateTime,jdbcType=DATE},</if>
            <if test="remark != null">#{remark,jdbcType=VARCHAR},</if>
        </trim>
    </insert>

    <!-- 更新操作 -->
    <update id="update" parameterType="com.xxxx.ssm.po.Account">
        update tb_account
        <set>
            <if test="aname != null">aname = #{aname,jdbcType=VARCHAR},</if>
            <if test="type != null">type = #{type,jdbcType=VARCHAR},</if>
            <if test="money != null">money = #{money,jdbcType=DOUBLE},</if>
            <if test="userId != null">user_id = #{userId,jdbcType=INTEGER},</if>
            <if test="createTime != null">create_time = #{createTime,jdbcType=DATE},</if>
            <if test="updateTime != null">update_time = #{updateTime,jdbcType=DATE},</if>
            <if test="remark != null">remark = #{remark,jdbcType=VARCHAR},</if>
        </set>
        where id = #{id,jdbcType=INTEGER}
    </update>
</mapper>

(4)添加AccountService

在src/resources/java对应的com.xxxx.ssm.service目录下创建AccountService.java实体类

复制代码
package com.xxxx.ssm.service;

import com.xxxx.ssm.dao.AccountDao;
import com.xxxx.ssm.po.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountService {
    @Autowired
private AccountDao accountDao;
    public Account selectById(Integer id) {
        return accountDao.selectById(id);
    }


        public int saveAccount(Account account) {
            return accountDao.save(account);
        }
            public int updateAccount(Account account) {
                return accountDao.update(account);
            }
                public int delAccount(Integer id){
                    return accountDao.delete(id);
                }
            }

2 URL映射地址配置

(1)GET请求配置

复制代码
 /*
 * 传统的URL访问:
 * http://Localhost:8080/ssm/account/queryAccountById?id=1
 *
 * RestFul URL访问:
 * @GetMapping("/account/id")
 * http://localhost:8080/ssm/account/1
 * @PathVariable Integer id
 * 将形参设置为参数路径 声明在形参前面
 * @param
 * @return
 * */
/* @RequestMapping("/account/queryAccountById")*/
 @GetMapping("/account/{id}")
 @ResponseBody
 public Account queryAccountById(@PathVariable Integer id) {
     return accountService.selectById(id);


 }

(2)DELETE请求配置

复制代码
/*删除操作
*
*
*
*@param
 *@return
 * */
@DeleteMapping("/account/{id}")
@ResponseBody
public Map<String,String> deleteAccountById(@PathVariable Integer id){
    //调用service层的删除方法,返回受影响的行数
    int row=accountService.delAccount(id);
    //判断受影响的行数是否大于0
    Map<String ,String> map=new HashMap<>();
    if(row>0){
        //删除成功
        map.put("code", "200");
        map.put("msg", "删除成功");
    }else{
        //删除失败
        map.put("code", "500");
        map.put("msg", "删除失败");
    }
    return map;
}

(3)POST请求配置

复制代码
/*
* 添加操作
* */
@PostMapping("/account")
@ResponseBody
public Map<String,String> addAccount(@RequestBody Account account){
    Map<String ,String> map=new HashMap<>();
    // //调用service层的添加方法,返回受影响的行数
    int row=accountService.saveAccount(account);
    //判断受影响的行数是否大于0

    if(row>0){
        //删除成功
        map.put("code", "200");
        map.put("msg", "添加成功");
    }else{
        //删除失败
        map.put("code", "500");
        map.put("msg", "添加失败");
    }
    return map;
}

(4)PUT请求配置

复制代码
/*更新操作
 *
 *
 *
 *@param
 *@return
 * */
@PutMapping("/account")
@ResponseBody
public Map<String,String> updateAccountById(@RequestBody Account account){
    //调用service层的删除方法,返回受影响的行数
    int row=accountService.updateAccount(account);
    //判断受影响的行数是否大于0
    Map<String ,String> map=new HashMap<>();
    if(row>0){
        //删除成功
        map.put("code", "200");
        map.put("msg", "更新成功");
    }else{
        //删除失败
        map.put("code", "500");
        map.put("msg", "更新失败");
    }
    return map;
}

1.7 全局异常统一处理

1.7.1 概念

在JavaEE项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。

SpringMVC对于异常处理这块提供了支持,通过SpringMVC提供的全局异常处理机制,能够将所有类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。

全局异常实现方式Spring MVC处理异常有3种方式:

1.使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver

2.实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器

3.使用@ExceptionHandler 注解实现异常处理

1.7.2 异常处理实现

(1)全局异常处理方式一

1. 配置简单异常处理器

配置SimpleMappingExceptionResolver对象

复制代码
<!--配置简单异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!--页面在转发时出现异常 设置默认的页面 -->
    <property name="defaultErrorView" value="error"/>
    <!--异常发生时 设置异常的变量名-->
    <property name="exceptionAttribute" value="ex"/>
    

可以在处理异常的页面获取异常信息

在WEBINF/jsp目录下创建一个error.jsp

复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<h3>处理异常页面</h3>
<h4>${ex}</h4>
</body>
</html>

2.使用自定义异常

参数异常

复制代码
/*
* 自定义异常:参数异常*/
public class ParamsException extends RuntimeException{
    private Integer code=300;
    private String msg="参数异常";
    public ParamsException(){
        super("参数异常!");
    }

    public ParamsException(String msg) {
        super(msg);
        this.msg = msg;
    }
    public ParamsException(Integer code) {
        super("参数异常!");
        this.code= code;
    }
    public ParamsException(Integer code,String msg) {
        super(msg);
        this.code= code;
    this.msg=msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

业务异常

复制代码
public class BusinessException extends RuntimeException{
private Integer code=400;
private String msg="业务异常";
    public BusinessException(){
        super("业务异常!");
    }

    public BusinessException(String msg) {
        super(msg);
        this.msg = msg;
    }
    public BusinessException(Integer code) {
        super("业务异常!");
        this.code= code;
    }
    public BusinessException(Integer code,String msg) {
        super(msg);
        this.code= code;
        this.msg=msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

3.设置自定义异常和页面的映射

复制代码
<!--设置自定义异常和页面的映射-->
<property name="exceptionMappings">
    <props >
        <!--key代表的是自定义异常的路径:标签中设置的是具体的页面-->
        <prop key="com.xxxx.ssm.exception.ParamsException">params-error</prop>
        <prop key="com.xxxx.ssm.exception.BusinessException">business-error</prop>
    </props>
</property>

(2)全局异常处理方式二(推荐)

1. 实现HandlerExceptionResolver接口

复制代码
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView=new ModelAndView("error");
        modelAndView.addObject("ex", "默认的错误信息");

       
        return modelAndView;
    }
}

2.自定义异常处理

复制代码
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView modelAndView=new ModelAndView("error");
        modelAndView.addObject("ex", "默认的错误信息");
//自定义异常
        if(ex instanceof ParamsException){
            //设置残花异常页面
            modelAndView.setViewName("params-error");
            ParamsException e=(ParamsException)ex;
            modelAndView.addObject("ex", e.getMsg());

        }
        if(ex instanceof BusinessException){
            modelAndView.setViewName("business-error");
            BusinessException e=(BusinessException)ex;
            modelAndView.addObject("ex", e.getMsg());

        }
        //处理json格式
        /*
        * 判断handler返回的类型 视图/json
        * */
        /*response.getWriter().write("json");
        return null;
        */
        return modelAndView;
    }
}

使用实现 HandlerExceptionResolver接口的异常处理器进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,同时,I在异常处理时能获取导致出现异常的对象,有利于提供更详细的异常处理信息。

(3)全局异常处理方法三

页面继承BaseController

复制代码
/*
* 父类:页面处理器虚继承父类(父类中异常处理的方法需添加注解)
* */
public class BaseController {
    @ExceptionHandler
    public String exc(HttpServletRequest request,HttpServletResponse response,Exception ex){
        request.setAttribute("ex", ex);
        if(ex instanceof ParamsException){
            return "params-error";
        }
        if(ex instanceof BusinessException){
            return "business-error";
        }

return "error";
    }
}

使用@ExceptionHandler 注解实现异常处理,具有集成简单、有扩展性好(只需要将要异常处理的

Controller 类继承于BaseController即可)、不需要附加Spring配置等优点,但该方法对已有代码存在入侵性(需要修改已有代码,使相关类继承于BaseController),在异常处理时不能获取除异常以外的数据。

1.7.3 未捕获异常的处理

对于 Unchecked Exception而言,由于代码不强制捕获,往往被忽略,如果运行期产生了Unchecked

Exception,而代码中又没有进行相应的捕获和处理,则我们可能不得不面对尴尬的404、500 ...... 等服务器内部错误提示页面。|

此时需要一个全面而有效的异常处理机制。目前大多数服务器也都支持在web.xml中通过<error-page>(Websphere/Weblogic)或者<error-code>(Tomcat)节点配置特定异常情况的显示页面。修改web.xml文件,增加以下内容:

复制代码
 <!-- 出错页面定义-->
<error-page>
<exception-type>java.lang.Throwable</exception-type>
<location>/500.jsp</location>
</error-page>
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>

</error-page>
相关推荐
minji...7 分钟前
C++ 详细讲解vector类
开发语言·c++
LiuYaoheng10 分钟前
【Android】View 的基础知识
android·java·笔记·学习
勇往直前plus18 分钟前
Sentinel微服务保护
java·spring boot·微服务·sentinel
星辰大海的精灵18 分钟前
SpringBoot与Quartz整合,实现订单自动取消功能
java·后端·算法
小鸡脚来咯21 分钟前
一个Java的main方法在JVM中的执行流程
java·开发语言·jvm
江团1io021 分钟前
深入解析三色标记算法
java·开发语言·jvm
天天摸鱼的java工程师30 分钟前
RestTemplate 如何优化连接池?—— 八年 Java 开发的踩坑与优化指南
java·后端
m0_7381207233 分钟前
CTFshow系列——PHP特性Web97-100
开发语言·安全·web安全·php·ctfshow
你我约定有三34 分钟前
java--泛型
java·开发语言·windows
杨杨杨大侠41 分钟前
第3章:实现基础事件总线
java·github·eventbus