IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 2021-11-10 Spring MVC -> 正文阅读

[Java知识库]2021-11-10 Spring MVC

文章目录

MVC架构模式简介

1、MVC是Model、View和Controller的缩写,分别代表 web应用程序中的3种职责。

  • 模型:用于存储数据以及处理用户请求的业务逻辑。
  • 视图:向控制器提交数据,显示模型中的数据。
  • 控制器:根据视图提出的请求判断将请求和数据交给哪个模型处理,将处理后的有关结果交给哪个视图更新显示。

2、基于servlet的MVC模式

  • 模型:一个或多个JavaBean对象,用于存储数据(实体模型,由JavaBean类创建)和处理业务逻辑(业务模型,由一般的Java类创建)。
  • 视图:一个或多个JSP页面,向控制器提交数据和为模型提供数据显示,JSP页面主要使用HTML标记和JavaBean标记来显示数据。
  • 控制器:一个或多个Servlet对象,根据视图提交的请求进行控制,即将请求转发给处理业务逻辑的JavaBean,并将处理结果存放到实体模型JavaBean中,输出给视图显示。

在这里插入图片描述

SpringMVC简介

1、什么是SpringMVC

SpringMVC是一个基于MVC模式的Web框架,是Spring框架的一个模块。它以SpringloC容器为基础,并利用容器的特性来简化它的配置,所以SpringMVC和Spring可直接整合使用。SpringMVC对MVC流程进行了封装,屏蔽掉很多底层代码,让开发者可以更加轻松快捷的完成基于MVC模式的Web开发。

2、SpringMVC中重要的组件

  • DispatcherServlet:前端控制器,接受所有请求
  • HandlerMapping:处理器映射器,根据配置的映射规则,找到对应的处理器
  • HandlerAdapter:处理器适配器,执行处理器中处理请求的方法
  • ViewResolver:视图解析器,定位视图。

3、SpringMVC工作流程

在这里插入图片描述

工作流程:

  1. 客户端请求提交到DispatcherServlet
  2. 由DispatcherServlet 控制器通过HandlerMapping,找到处理请求的Controller
  3. DispatcherServlet将请求提交到Controller
  4. Controller调用HandlerAdapter执行处理请求的方法
  5. 业务逻辑处理后返回ModelAndView
  6. DispatcherServlet通过ViewResolver视图解析器,找到ModelAndView指定的视图
  7. 视图负责渲染并将结果显示到客户端

SpringMVC的基本使用

1、jar包依赖

Spring核心容器模块

  • spring-beans-5.2.7.RELEASE.jar
  • spring-context-5.2.7.RELEASE.jar
  • spring-core-5.2.7.RELEASE.jar
  • spring-expression-5.2.7.RELEASE.jar

Commons-loggin日志

  • commons-logging-1.2.jar

Spring AOP模块

  • spring-aop-5.2.7.RELEASE.jar

Spring Web模块

  • spring-web-5.2.7.RELEASE.jar

Spring Web MVC模块

  • spring-webmvc-5.2.7.RELEASE.jar

Servlet

  • servlet-api.jar

2、搭建环境

步骤:

  1. 创建web项目

  2. 添加相关jar包

  3. 在src目录下添加springmvc.xml配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    </beans>
    
  4. 添加配置文件中beans的约束文件

在这里插入图片描述

3、配置前端控制器

修改web.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置前端控制器-->
    <servlet>
        <servlet-name>springMvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <!--如未指定springMVC配置文件的名称及路径,前端控制器会去WEB-INFO目录下找springMvc-servlet.xml的文件;
        我们的springmvc.xml文件没有在WEB-INFO目录中,需要手动指定名称和路径-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc.xml</param-value>
        </init-param>

        <!--自启动一个Servlet
        在项目启动时就产生一个servlet,不依赖请求产生-->
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <!--拦截除jsp以外的资源请求-->
        <!--这里的url决定了哪些请求会被拦截,但是不作任何请求和处理的匹配-->
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

4、创建Controller

创建Controller,需要实现Controller接口

package com.bjsxt.web.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

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

public class DemoController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView modelAndView = new ModelAndView();
        //响应内容到哪个页面
        modelAndView.setViewName("/index.jsp");
        //响应内容:属性名称和值
        modelAndView.addObject("msg","hello spring mvc");
        return modelAndView;
    }
}

在index.jsp中通过EL表达式获取响应内容

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>$Title$</title>
    </head>
    <body>
        ${msg}
    </body>
</html>

在springmvc.xml中配置Controller

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

    <!--配置Controller url的绑定可以是id或name-->
    <bean id="/demo" class="com.bjsxt.web.controller.DemoController"></bean>
</beans>

SpringMVC基于注解开发

在SpringMVC的基本使用中我们是以传统方式创建的控制器,它需要实现 Controller接口。传统风格的控制器不仅需要在配置文件中配置映射,而且只能编写一个处理方法,不够灵活。

使用基于注解的控制器具有以下两个优点:

  1. 在基于注解的控制器类中可以编写多个处理方法,进而可以处理多个请求,这就允许将相关的操作编写在同一个控制器类中,从而减少控制器类的数量,方便以后的维护。
  2. 基于注解的控制器不需要在配置文件中部署映射,仅需要使用@RequestMapping 注解就可以将一个URI绑定到类或方法上。

1、注解介绍

  • @Controller Controller注解用于指定Bean对象为控制器

  • @RequesrMapping 用于将一个URI绑定到类上或类的方法中

2、搭建环境

和基本使用中完全相同

3、配置前端控制器

和基本使用中完全相同

4、创建Controller

基于注解创建Controller

package com.bjsxt.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/con")
public class AnnController {

    /**
     * 访问路径为http:localhost:8808:/spring_mvc_ann_demo/con/ann
     * 如果类中未指定url,访问路径为http:localhost:8808:/spring_mvc_ann_demo/ann
     * @return
     */
    @RequestMapping("/ann")
    public ModelAndView abc(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/index.jsp");
        modelAndView.addObject("msg","spring mvc ann");
        return modelAndView;
    }
}

在index.jsp中通过EL表达式获取响应内容

修改spring-mvc-ann.xml文件,添加context命名空间,开启注解扫描

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

    <!--添加context命名空间,开启注解扫描-->
    <context:component-scan base-package="com.bjsxt.web.controller"/>
</beans>

【解决的两个异常】

  1. XML文件错误:web.xml中漏写springmvc配置文件的名称路径
  2. JDK17无法解析某个class文件:换回JDK1.8

5、配置注解驱动

在基于注解方式开发控制器时,需要添加<mvc:annotation-driven>标签,它是启用MVC注解的钥匙。如果没有使用这个标签,而仅仅是使用<context:component-scan/>标签扫描并注册了相关的控制器,那么仅是@Controller @RequestMapping基本功能的注解可以使用除此以外的相关的注解并不能使用。

<mvc:annotation-driven/>的作用是提供扩展功能的。

它的处理类AnnotationDrivenBeanDefinitionParser会注册很多基于注解开发时所用到的Bean对象到容器中。其中包含RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver 三个bean。

  1. 在spring-mvc-ann.xml中添加mvc命名空间

    xmlns:mvc="http://www.springframework.org/schema/mvc"
    
    xsi:schemaLocation="...
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd"
    
  2. 配置注解驱动

    <mvc:annotation-driven/>
    

获取请求参数

在Servlet中我们通过request.getParameter(name)方法获取请求参数。该方式存在两个问题:

  1. 请求参数较多时会出现代码冗余的显现
  2. 与容器紧耦合

在SpringMVC中可以使用HttpServletRequest对象获取请求数据,同时还提供了参数注入的方式用于获取请求数据。

SpringMVC参数注入的优点

  1. 简化参数接收形式(不需要调用任何方法。需要什么参数,就在控制器方法中提供什么参数)
  2. 参数类型不需要自己转换了,如果类型不符会抛出400异常
  3. 可将参数自动封装为对象
  4. 如果没有该参数对应的数据,可为该参数指定默认值

1、通过HttpServletRequest对象获取请求数据

/**
 * 可以将HttpServletRequest对象作为参数传入处理请求的方法中
 * 从HttpServletRequest对象获取数据,再将其响应回index.jsp
 * @param request
 * @return
 */
@RequestMapping("/getData")
public ModelAndView getParameter(HttpServletRequest request){
    ModelAndView modelAndView = new ModelAndView();
    String username = request.getParameter("name");
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("username",username);
    return modelAndView;
}

在index.jsp中通过EL表达式获取响应内容

2、通过参数注入获取请求数据

2.1 注入多参数

2.1.1 不使用注解注入多参数
/**
 * 也可以将请求的参数直接作为参数传入处理请求的方法中
 * @param user_name
 * @param user_age
 * @return
 */
@RequestMapping("/addUsers")
public ModelAndView addUsers(String user_name,int user_age){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("name",user_name);
    modelAndView.addObject("age",user_age);
    return modelAndView;
}

在index.jsp中通过EL表达式获取响应内容

2.1.2 @RequestParam注解

RequestParam:将请求参数绑定到控制器的方法参数上。

  • value:参数名
  • required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错
  • defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false;如果没有传该参数,就使用默认值
/**
 * value 用于指定请求中参数的名称
 * required 某个参数是否为必填项,默认为true,必填
 * defaultValue 如果设置该属性,required=false
 * @param user_name
 * @param user_age
 * @return
 */
@RequestMapping("/addUsers2")
public ModelAndView addUsers2(@RequestParam(value = "name") String user_name,@RequestParam("age") int user_age){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("name",user_name);
    modelAndView.addObject("age",user_age);
    return modelAndView;
}

2.3 注入集合参数

在SpringMVC请求参数注入中,如果有多请求参数的name相同,那么可以使用String或List集合来接收请求参数。如果使用的 List类型需要在该参数前添加@RequestParam注解,String[]则不需要。

创建一个addUsers.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

    <form action="addUsers3">
        用户名:<input type="text" name="username"><br>
        爱好:<br>
        体育:<input type="checkbox" name="hobby" value="sport">
        音乐:<input type="checkbox" name="hobby" value="music">
        艺术:<input type="checkbox" name="hobby" value="art"><br>
        <input type="submit" value="ok">
    </form>

</body>
</html>

在Controller类中添加处理请求的方法

@RequestMapping("/addUsers3")
public ModelAndView addUsers3(String username,String[] hobby){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("name",username);

    String temp = "";
    for(String str:hobby){
        temp += str+" ";
    }
    modelAndView.addObject("hobby",temp);
    return modelAndView;
}

或者

@RequestMapping("/addUsers3")
public ModelAndView addUsers3(String username,@RequestParam(value="hobby") List<String> hobby){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("name",username);

    String temp = "";
    for(String str:hobby){
        temp += str+" ";
    }
    modelAndView.addObject("hobby",temp);
    return modelAndView;
}

在index.jsp中通过EL表达式获取响应内容

2.4 注入对象参数

在SpringMVc的请求参数注入中,可以使用注入POJO方式来接收请求参数。

要求:请求参数的name必须与POJO的属性名相同。

2.4.1 注入单个对象

创建和表单提交信息相应的pojo类

【注意】表单中单个爱好的属性名是hobby,尽管逻辑上Users类中对应的属性应该是hobbies,但是这么写会报错,必须按照上面的要求:name和属性名一致才行。猜测原因可能是注入对象的方式无法使用@RequestParam指定参数名?

package com.bjsxt.poji;

import java.util.List;

public class Users {
    private String username;
    private List<String> hobby;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public List<String> getHobby() {
        return hobby;
    }

    public void setHobby(List<String> hobby) {
        this.hobby = hobby;
    }
}

在Controller类中添加处理请求的方法

@RequestMapping("/addUsers4")
public ModelAndView addUsers4(Users user){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("name",user.getUsername());

    String temp = "";
    for(String str: user.getHobby()){
        temp += str+" ";
    }
    modelAndView.addObject("hobby",temp);
    return modelAndView;
}

在index.jsp中通过EL表达式获取响应内容

2.4.2 注入关联对象

SpringMVC可以根据对象的关联关系实现请求参数的注入。

创建一个新的实体类

package com.bjsxt.poji;

public class Address {
    private String phonenumber;
    private String postcode;

    public String getPhonenumber() {
        return phonenumber;
    }

    public void setPhonenumber(String phonenumber) {
        this.phonenumber = phonenumber;
    }

    public String getPostcode() {
        return postcode;
    }

    public void setPostcode(String postcode) {
        this.postcode = postcode;
    }

    @Override
    public String toString() {
        return "Address{" +
                "phonenumber='" + phonenumber + '\'' +
                ", postcode='" + postcode + '\'' +
                '}';
    }
}

修改Users类,添加address属性,添加get/set方法

package com.bjsxt.poji;

import java.util.List;

public class Users {
    private String username;
    private List<String> hobby;
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public List<String> getHobby() {
        return hobby;
    }

    public void setHobby(List<String> hobby) {
        this.hobby = hobby;
    }

    @Override
    public String toString() {
        return "Users{" +
                "username='" + username + '\'' +
                ", hobby=" + hobby +
                '}';
    }
}

创建addUsers2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

    <form action="addUsers5">
        用户名:<input type="text" name="username"><br>
        爱好:<br>
        体育<input type="checkbox" name="hobby" value="sport">
        音乐<input type="checkbox" name="hobby" value="music">
        艺术<input type="checkbox" name="hobby" value="art"><br>
        地址:<br>
        电话号码:<input type="text" name="address.phonenumber">
        邮编:<input type="text" name="address.postcode"><br>
        <input type="submit" value="ok">
    </form>

</body>
</html>

在Controller中添加处理请求的方法

@RequestMapping("/addUsers5")
public ModelAndView addUsers5(Users user){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("user",user.toString());

    modelAndView.addObject("address",user.getAddress().toString());
    return modelAndView;
}

在index.jsp中通过EL表达式获取响应内容:user 、address

2.4.3 向List中注入对象

在Users类中添加一个泛型为Address的集合类型的属性

private List<Address> addressList;

public List<Address> getAddressList() {
    return addressList;
}

public void setAddressList(List<Address> addressList) {
    this.addressList = addressList;
}

新建addUsers3.jsp

addressList[0].phonenumber,先集合、再到元素address对象、最后到其属性

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

    <form action="addUsers6">
        用户名:<input type="text" name="username"><br>
        爱好:<br>
        体育<input type="checkbox" name="hobby" value="sport">
        音乐<input type="checkbox" name="hobby" value="music">
        艺术<input type="checkbox" name="hobby" value="art"><br>
        地址:<br>
        电话号码:<input type="text" name="addressList[0].phonenumber">
        邮编:<input type="text" name="addressList[0].postcode"><br>
        电话号码:<input type="text" name="addressList[1].phonenumber">
        邮编:<input type="text" name="addressList[1].postcode"><br>
        <input type="submit" value="ok">
    </form>

</body>
</html>

添加处理请求的方法

并没有遍历AddressList集合!是不是可以说会自动跨过集合这层直接读取元素?

@RequestMapping("/addUsers6")
public ModelAndView addUsers6(Users user){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("user",user.toString());

    modelAndView.addObject("address",user.getAddressList());
    return modelAndView;
}
2.4.4 向Map中注入对象

在Users类中添加一个泛型为Address的Map类型的属性,生成get/set方法

private Map<String,Address> addressMap;

public Map<String, Address> getAddressMap() {
    return addressMap;
}

public void setAddressMap(Map<String, Address> addressMap) {
    this.addressMap = addressMap;
}

新建addUsers4.jsp

addressMap[‘one’].phonenumber,Map的key、key对应的value就是Address对象、再到其属性

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>

    <form action="addUsers7">
        用户名:<input type="text" name="username"><br>
        爱好:<br>
        体育<input type="checkbox" name="hobby" value="sport">
        音乐<input type="checkbox" name="hobby" value="music">
        艺术<input type="checkbox" name="hobby" value="art"><br>
        地址:<br>
        电话号码:<input type="text" name="addressMap['one'].phonenumber">
        邮编:<input type="text" name="addressMap['one'].postcode"><br>
        电话号码:<input type="text" name="addressMap['two'].phonenumber">
        邮编:<input type="text" name="addressMap['two'].postcode"><br>
        <input type="submit" value="ok">
    </form>

</body>
</html>

添加处理请求的方法

对于Map,先获取EntrySet对象

@RequestMapping("/addUsers7")
public ModelAndView addUsers7(Users user){
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("/index.jsp");
    modelAndView.addObject("user",user.toString());
    Set<Map.Entry<String, Address>> set = user.getAddressMap().entrySet();
    modelAndView.addObject("address",set);
    return modelAndView;
}

3、字符编码过滤器

在请求参数中如果含有中文,会出现乱码现象。

3.1 Get请求含有中文乱码解决方案

在这里插入图片描述

可通过修改Tomcat的server.xml配置文件,解决中文乱码。

<Connector port="8888" protocol="HTTP/1.1"
           connectionTimeout="2000n"
           redirectPort="8443"lURIEncoding="utf-8"/>

3.2 Post请求含有中文乱码解决方案

org.springframework.web.filter.CharacterEncodingFilter提供了过滤器,不用写,直接配置即可。

<!--spring中提供的字符编码过滤器-->
<filter>
    <filter-name>encFilter</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>encodeFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

指定处理请求类型

1、@RequestMapping

在@RequestMapping 注解中如果并未指定处理请求类型,那么含有该注解的方法既可以处理GET类型请求也可以处理POST类型的请求。如果需要指定只能处理某种类型的请求,可以通过method属性指定请求类型。

 @RequestMapping(value = "/addUsers7",method = RequestMethod.GET)

2、@GetMapping

表示只能处理get方式请求。

//name属性绑定url
@GetMapping(name = "addUsers7")

3、@PostMapping

表示只能处理post方式请求。

//name属性绑定url
@PostMapping(name = "addUsers7")

SpringMVC处理响应

1、配置视图解析器

在SpringMVC中提供了13个视图解析器,用于支持不同的视图技术。视图解析器最大的特点是可以将控制器中处理请求的逻辑和视图中渲染实现解耦InternalResourceViewResolver是SpringMVC中默认的视图解析器,用来解析JSP视图。能将视图名映射为JSP文件。

1.1 简单使用

修改spring-mvc-ann.xml文件

<!--(覆盖)配置视图解析器-->
<!--父类UrlBasedViewResolver的属性prefix suffix-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>

创建新的Controller

package com.bjsxt.web.controller;

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

@Controller
@RequestMapping("/page")
public class PageController {

    @RequestMapping("/login")
    public String toLogin(){
        return "login";
    }
}

1.2 视图解析器的作用:

  1. 预先设置要跳转的页面前缀和后缀
  2. Controller中处理请求的方法返回值类型不再是ModelAndView,变成了String
  3. 可以很方便地指定要跳转的页面

返回值不再是ModelAndView,响应的内容如何回送到浏览器?

在web目录下的jsp文件可以在地址栏直接访问;但实际环境中jsp文件应放在WEB-INF目录下,不允许通过地址栏直接访问;这时候,我们可以通过视图解析器设置前缀和后缀,以处理请求的方法跳转jsp页面。比如,通过上面的设置访问/page/login跳转到WEB-INF/jsp/login.jsp页面。

2、Spring MVC作用域传值

作用域:“数据共享的范围”,也就是说数据能够在多大的范围内有效。

在这里插入图片描述

2.1 Request作用域传值

2.1.1 使用原生的HttpServletRequest

在处理请求的方法中传入HttpServletRequest对象,调用setAttribute方法设置信息,在login.jsp通过EL表达式获取。

请求访问/page/login,跳转login.jsp,通过EL表达式获取该次请求中传递的参数信息。

HttpServletRequest对象是容器提供的,容器与控制器之间是紧耦合。

@RequestMapping("/login")
public String toLogin(HttpServletRequest request){
    request.setAttribute("msg","001");
    return "login";
}
2.1.2 使用Map集合

SpringMVC会为Map接口注入BindingAwareModelMap对象。该对象是由Spring提供的一个实现了Map接口的对象。SpringMVC会把该对象中的数据放入到HttpServletRequest对象中,其目的是为了解除控制器与容器的耦合。

@RequestMapping("/login")
public String toLogin(HttpServletRequest request, Map<String,String> map){
    map.put("msg","hello");
    return "login";
}

【注意】

  1. 这种方式作用域仍然是一次请求中,因为map是放在HttpServletRequest对象中的;
  2. 通过EL表达式取msg时,会自动定位map对象从中取key为msg对应的值,不需要我们指定具体是哪个map,是不是可以认为它本身就具有一层拆封的能力?
2.1.3 使用Model接口

在SpringMVC中提供了一个Model类型的接口,该接口定义了传递数据的基本行为。如果在处理请求的方法中指定了Model类型的参数,那么SpringMVC会注入一个BindingAwareModelMap对象,并通过该对象把数据放入到HttpServletRequest对象中。

@RequestMapping("/login")
public String toLogin(HttpServletRequest request, Map<String,String> map, Model model){
    model.addAttribute("msg","001");
    return "login";
}

2.2 Session作用域传值

Session 作用域表示在当前会话中有效。在SpringMVc中对于Session作用域传值,只能使用HttpSession对象来实现。对于HttpSession对象的获取方式有两种:

  1. 通过参数注入方式获取 HttpSession对象
  2. 通过注入HttpServletRequest对象,并通过该对象获取HttpSession
@RequestMapping("/login")
public String toLogin(HttpServletRequest request, HttpSession session){
    // HttpSession session = request.getSession();
    session.setAttribute("msg","001");
    return "login";
}

2.3 Application作用域传值

Application作用域表示在整个应用范围都有效。在SpringMvc中对于Application作用域传值,只能使用servletContext对象来实现。但是对于该对象的获取方式不能直接向方法中注入

  1. 通过HttpServletRequest对象获取
  2. 通过HttpSession对象获取
@RequestMapping("/login")
public String toLogin(HttpServletRequest request, HttpSession session){
    // ServletContext servletContext = request.getServletContext();
    ServletContext servletContext = session.getServletContext();
    servletContext.setAttribute("msg","002");
    return "login";
}

3、Spring MVC的响应方式

3.1 请求转发

3.1.1 使用Servlet API

在SpringMVC中可以使用HttpServletRequest对象实现请求转发。

@RequestMapping("/login2")
public void showLogin(HttpServletRequest request, HttpServletResponse response) throws Exception{
    request.setAttribute("msg","and one");
    request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
}
3.1.2 使用forward关键字

如果使用forward关键字实现请求转发跳转时,是通过SpringMVC的DispatcherServlet组件来完成的。不会执行视图解析器,所以需要指定请求跳转页面的完整URI

@RequestMapping("/login3")
public String showLogin2(Model model){
    model.addAttribute("msg","003");
    return "forward:/WEB-INF/jsp/login.jsp";
}
3.1.3 使用视图解析器

在SpringMVC的视图解析器中使用的是请求转发方式来实现页面跳转。可以在配置视图解析器时指定视图的前置与后缀。

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
@RequestMapping("/login4")
public String showLogin3(Model model){
    model.addAttribute("msg","003");
    return "login";
}

3.2 重定向

3.2.1 使用Redirect关键字

在SpringMVC需要使用redirect关键字实现重定向的跳转。在重定向跳转中是不经过视图解析器的。

@RequestMapping("/redirectLogin")
public String goRedirect(){
    //redirect:/WEB-INF/jsp/login.jsp会报错
    //重定向地址栏url会改变,所以/WEB-INF/jsp/login.jsp仍然是不被允许直接访问的
    return "redirect:/page/login3";
}

过程:

访问/page/redirectLogin-----------重定向----------->/page/login3-------------请求转发------------>/WEB-INF/jsp/login.jsp

文件上传与下载

1、文件上传

在SpringMVC中提供了用于处理文件上传的组件CommonsMultipartResolver(多部件解析器)。可以通过该组件很方便的实现文件上传。该组件的运行需要依赖于Apache的commons-fileupload 与commons-io包。

1.1 搭建环境

一般步骤:

  1. 创建普通项目

  2. 添加jar包

  3. 添加web框架

  4. 配置Tomcat

  5. 创建controller包

    image-20211105112917220
  6. 添加Spring配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--添加context命名空间-->
        <!--添加mvc命名空间-->
        <!--开启注解扫描-->
        <context:component-scan base-package="com.bjsxt.web.controller"/>
    
        <!--配置注解驱动-->
        <mvc:annotation-driven/>
    
        <!--(覆盖)配置视图解析器-->
        <!--父类UrlBasedViewResolver的属性prefix suffix-->
        <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
        <!--配置多部件解析器-->
        <!--id必须是multipartResolver-->
        <!--一个相似的点:注解式实现声明式事务管理中,配置事务管理器,id必须是transactionManager-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <!--文件名编码格式-->
            <property name="defaultEncoding" value="utf-8"/>
            <!--上传文件总容量,单位字节,不指定表示不限制大小-->
            <property name="maxUploadSize" value="10485760"/>
            <!--单个上传文件的容量,单位字节,不指定表示不限制大小-->
            <property name="maxUploadSizePerFile" value="1024"/>
        </bean>
    </beans>
    
  7. 修改web.xml配置前端控制器

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!--配置前端控制器-->
        <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:spring-mvc.xml</param-value>
            </init-param>
            <!--自启动Servlet-->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springMvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

1.2 单文件上传

1.2.1 修改项目的访问路径

在这里插入图片描述

1.2.2 创建文件上传页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
	<%--请求方式必须是post--%>
    <form action="/file/singleFile" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="username"><br>
        文件上传:<input type="file" name="file"><br>
        <input type="submit" value="ok">
    </form>
</body>
</html>

1.2.3 创建成功页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Title</title>
    </head>
    <body>
        <p>
            OK...
        </p>
    </body>
</html>
1.2.3 创建页面跳转的控制器
package com.bjsxt.web.controller;

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

/**
 * 用于跳转到.jsp页面
 */
@Controller
@RequestMapping("/page")
public class PageController {

    /**
     * 跳转到singleFile.jsp
     * @return
     */
    @RequestMapping("/singleFile")
    public String toSingleFile(){
        return "singleFile";
    }

    /**
     * 跳转到ok.jsp
     * @return
     */
    @RequestMapping("/ok")
    public String toOK(){
        return "ok";
    }
}
1.2.4 创建实现文件上传的控制器
package com.bjsxt.web.controller;

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.UUID;

/**
 * 实现文件上传
 */
@Controller
@RequestMapping("/file")
public class FileUploadController {

    @RequestMapping("/singleFile")
    public String singleFileUpload(@RequestParam("file") MultipartFile files, String username, HttpServletRequest request){
        String filename = UUID.randomUUID().toString()+files.getOriginalFilename().substring(files.getOriginalFilename().lastIndexOf("."));
        String realPath = request.getServletContext().getRealPath("/fileUpload");
        File file = new File(realPath,filename);
        try {
            files.transferTo(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "redirect:/page/ok";
    }
}

1.3 单文件上传总结

  • 实现文件上传的表单,提交方式为post,编码格式enctype=multipart/form-data
  • 控制器分离,一个用于实现文件上传,一个用于跳转jsp页面
  • 实现文件上传的控制器中,FileUploadController类和singleFileUpload()方法都映射应当和表单请求的路径对应
  • singleFileUpload()方法中的参数名files,最好和表单中上传文件的input标签中name属性的值一致,否则用@RequestParam指定
  • singleFileUpload()方法的参数MultipartFile files,包含文件名、大小等信息
  • singleFileUpload()方法需要HttpServletRequest对象来获取ServletContext对象,获取绝对路径
  • 为避免文件名相似或重复,使用UUID工具类的randomUUID()方法和subString()方法生成一个不易重复的文件名+扩展名
  • files.transferTo()自动读取和写入文件
  • 成功上传的文件位于 /out/artifacts/fileupload_war_exploded/fileUpload目录,而不是/WEB-INF/fileUpload

过程图示

在这里插入图片描述

1.4 实现多文件上传

1.4.1 创建多文件上传页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>

    <form action="/file/multiFile" method="post" enctype="multipart/form-data">
        用户名:<input type="text" name="username"><br>

        <%--文件上传name属性的值必须一样--%>
        文件上传1:<input type="file" name="file"><br>
        文件上传2:<input type="file" name="file"><br>
        <input type="submit" value="ok">
    </form>

</body>
</html>
1.4.2 添加页面跳转方法
/**
 * 跳转到multiFile.jsp
 * @return
 */
@RequestMapping("/multiFile")
public String toMultiFile(){
    return "multiFile";
}
1.4.3 创建实现多文件上传的方法
@RequestMapping("/multiFile")
public String multiFileUpload(MultipartFile file[],String username, HttpServletRequest request){
    for(MultipartFile temp:file){
        String fileName = UUID.randomUUID().toString()+temp.getOriginalFilename().substring(temp.getOriginalFilename().lastIndexOf("."));
        String realPath = request.getServletContext().getRealPath("/fileUpload");
        File aFile = new File(realPath,fileName);
        try{
            temp.transferTo(aFile);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    return "redirect:/page/ok";
}

总结

传入multiFileUpload()方法的MultipartFile参数是一个数组,数组中的每个元素就是一个MultipartFile对象。数组的名称和上传文件的input标签name属性的值应当一致。

2、文件下载

2.1 添加jstl标签库jar包

  • jstl.jar
  • standard.jar

2.2 创建下载页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>File Download</title>
</head>
<body>
    <%--item 需要用EL表达式取files--%>
    <c:forEach items="${files}" var="file">
        <%--显示文件名,点击请求下载文件--%>
        <%--两条错误代码--%>
        <%--<a href="/file/download?fileName="+${file}>${file}</a><br>--%>
        <%--<a href="/file/download?fileName="${file}>${file}</a><br>--%>
        <a href="/file/download?fileName=${file}">${file}</a><br>
    </c:forEach>
</body>
</html>

2.3 添加跳转方法

/**
 * 1、跳转到文件下载的fileDownload.jsp页面
 * 2、展示已有文件
 * @return
 */
@RequestMapping("/fileDownload")
public String toFileDownload(Model model, HttpServletRequest request){
    //目标文件所在的文件夹路径
    String realPath = request.getServletContext().getRealPath("/fileUpload");
    File file = new File(realPath);
    
    //获取该文件夹下所有文件的文件名
    String[] arr = file.list();
    model.addAttribute("files",arr);
    return "fileDownload";
}

2.4 添加下载文件的方法

/**
 * @param fileName 下载哪个文件
 * @param request IO流、涉及路径转换
 * @param response 将通过字符流输出到浏览器
 */
@RequestMapping("/download")
public void fileDownload(String fileName, HttpServletRequest request,HttpServletResponse response){
    //下载文件需要设置的响应头
    response.setHeader("Content-Disposition","attachment;filename="+fileName);

    //从fileupload_war_exploded目录往下找
    //这里提供的是要下载的文件所在的文件夹的路径,无法定位到具体文件,点击会下载一个位置大小的txt文件
    //如果指定具体文件的目录,又无法实现下载文件的可选性
    String realPath = request.getServletContext().getRealPath("/fileUpload");
    File file = new File(realPath,fileName);
    try {
        ServletOutputStream os = response.getOutputStream();
        os.write(FileUtils.readFileToByteArray(file));
        os.flush();
        os.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

2.5 总结

  • new File(realPath).list()的返回值是包含文件名的String数组
  • 在fileDownload.jsp页面中通过c:forEach标签展示文件名,使用超链接点击请求下载,同时传递要下载文件的文件名
  • 设置响应头response.setHeader("Content-Disposition","attachment;filename="+fileName);
  • 通过路径和文件名定位到文件,将其通过FileUtils读取为为字节数组
  • 获取响应的字节输出流输出到浏览器

过程图示

在这里插入图片描述

代码写错出现异常的解决过程:

<a href="/file/download?fileName=${file}">${file}</a>

写成了

<a href="/file/download?fileName="${file}>${file}</a>

导致点击下载文件时无法传递fileName,通过浏览器审查发现响应头信息中attachment;filename=为空,有想到可能是fileName没有传递过去,但是看到Tomcat提示信息:getRealPath()方法中的参数/fileUpload是一个目录而不是文件,思考方向就一直固定在目录上,通过尝试:fileUpload、/fileUpload、/fileUpload/、/fileUpload/xxx.jpg四种方式发现只有最后一种方式可行,所以根本原因还是没有定位到要下载的文件,就算这样处理不论点哪个文件都会下载一个固定的文件,显然不合理。最后,终于发现在fileDownload.jsp中传递fileName的超链接的访问地址有问题,原来是将${file}当作字符串拼接了!!!

静态资源映射

当在DispatcherServlet的url-pattern中配置拦截“/”时,除了.jsp不会拦截以外,其他所有的请求都会经过前端控制器进行匹配,此时静态资源,例如 .css、.js、.jpg…就会被前端控制器拦截,导致不能访问,出现404问题。

在web目录下新建image目录,copy一个图片,在WEB-INF/jsp目录下新建一个jsp文件展示该图片,添加一个跳转该页面的方法,测试发现图片无法显示。

原因:web.xml中设置了<url-pattern>/</url-pattern>,原本直接可以访问的静态资源现在需要通过前端控制器去寻找图片资源,但是如此配置又会拦截图片等静态资源。

1、通过DefaultServlet处理静态资源

F:\envrionment\apache-tomcat-9.0.34\conf目录下有一个全局的web.xml文件,项目启动时这个全局的配置文件会和项目自身的配置文件合并生效。全局web.xml文件中有一个默认的DefaultServlet,在项目自身的web.xml中,通过对这个DefaultServlet进行配置(无需新建),来放行对静态资源的访问。删除out目录,重启Tomcat测试OK。

缺点:如果静态资源的类型很多,放行配置重复代码很多

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--配置前端控制器-->
    <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:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!--无需新建,直接配置-->
    <servlet-mapping>
        <servlet-name>default</servlet-name>
        <!--.jpg优先级高于/-->
        <url-pattern>*.jpg</url-pattern>
    </servlet-mapping>
</web-app>

2、通过静态资源映射器处理静态资源

在spring3.0.4以后的SpringMVc模块提供了静态资源映射器组件。通过mvc:resources标签配置静态资源映射器。

修改SpringMVC配置文件

<!--mapping表示请求访问的url   location表示资源所在路径
    对于非静态资源,由<url-pattern>/</url-pattern>控制拦截;
    对于静态资源交给<mvc:resources/>负责,访问哪一类静态资源,就给指明该类资源所在的路径
    -->
<mvc:resources mapping="/image/**" location="/image/"/>
<mvc:resources mapping="/js/**" location="/js/"/>

3、通过default-servlet-handler处理静态资源

在SpringMVC的配置文件中配置<mvc:default-servlet-handler/>后,会在Spring MVC上下文中定义一个org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler,它会像一个检查员,对进入 DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。

<!--将对静态资源请求,交给服务器默认的Servlet处理-->
<mvc:default-servlet-handler/>

4、对比三种方式

方式一和方式三本质相同。

如果静态资源放在web目录下,方式一和方式三有效;如果静态资源放在WEB-INF目录下,只能使用方式二。

SpringMVC的异常处理

1、搭建环境

  1. 创建项目添加jar包

  2. 创建Spring MVC配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--配置注解扫描-->
        <context:component-scan base-package="com.bjsxt.web.controller"/>
    
        <!--配置注解驱动-->
        <mvc:annotation-driven/>
    
        <!--配置视图解析器-->
        <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    </beans>
    
  3. 配置前端控制器

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!--配置前端控制器-->
        <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:spring-mvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springMvc</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    </web-app>
    

2、使用@ExceptionHandler注解处理异常

缺点:只能处理当前控制器内的异常。

package com.bjsxt.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/user")
public class UsersController {

    @RequestMapping("/getUsers")
    public String getUsers(){
        /*String str = null;
        str.length();*/
        /*int i = 1/0;*/
        int[] arr = new int[1];
        arr[2] = 2;
        return "getUsers";
    }

   //处理空指针异常、算术运算异常 
  @ExceptionHandler({java.lang.NullPointerException.class,java.lang.ArithmeticException.class})
    public String nullPointerExceptionHandle(Exception e, Model model){
        model.addAttribute("msg",e);
        return "error1";
    }
	//处理其他异常
    @ExceptionHandler({java.lang.Exception.class})
    public String exceptionHandle(Exception e, Model model){
        model.addAttribute("msg",e);
        return "error2";
    }
}

创建相关页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>Get Users</title>
    </head>
    <body>
        getUsers...
    </body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>error1</title>
    </head>
    <body>
        出错了。${msg}
    </body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
    <head>
        <title>error2</title>
    </head>
    <body>
        未知异常。${msg}
    </body>
</html>

3、使用@ControllerAdvice和@ExceptionHandler处理异常

GlobalExceptionHandler所在的包应当能够被注解扫描。

package com.bjsxt.web.controller;

import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 全局异常处理器
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler({java.lang.NullPointerException.class,java.lang.ArithmeticException.class})
    public String nullPointerExceptionHandle(Exception e, Model model){
        model.addAttribute("msg",e);
        return "error1";
    }

    @ExceptionHandler({java.lang.Exception.class})
    public String exceptionHandle(Exception e, Model model){
        model.addAttribute("msg",e);
        return "error2";
    }
}

4、使用SimpleMappingExceptionResolver处理异常

缺点:只能实现跳转,无法传递信息。

【注意】@Configuration @Bean

package com.bjsxt.web.controller;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.Properties;

@Configuration
public class GlobalExceptionHandler2 {

    @Bean
    public SimpleMappingExceptionResolver getResolver(){
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("java.lang.NullPointerException","error1");
        properties.setProperty("java.lang.ArithmeticException","error2");
        resolver.setExceptionMappings(properties);
        return resolver;
    }
}

5、自定义HandlerExceptionResolve处理器处理异常

通过实现HandlerExceptionResolver接口自定义异常处理器,既可以跳转页面,也可以传递信息。

【注意】仍然有@Configuration注解。

package com.bjsxt.web.controller;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

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


@Configuration
public class GlobalExceptionHandler3 implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        ModelAndView modelAndView = new ModelAndView();
        if(e instanceof NullPointerException){
            modelAndView.setViewName("error1");
        }
        if(e instanceof ArithmeticException){
            modelAndView.setViewName("error2");
        }
        modelAndView.addObject("msg",e);
        return modelAndView;
    }
}

SpringMVC拦截器

1、拦截器简介

1.1 什么是拦截器

Spring MVC的拦截器 (Interceptor) 与Servlet的过滤器 (Filter) 类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

1.2 执行流程

在这里插入图片描述

1.3 拦截器与过滤器的区别

  • 拦截器是SpringMVC组件,而过滤器是Servlet 组件
  • 拦截器不依赖容器,过滤器依赖容器
  • 拦截器只能对控制器请求起作用,而过滤器则可以对所有的请求起作用
  • 拦截器可以获取IOC容器中的各个bean,而过滤器就不太方便

2、定义拦截器

通过实现HandlerInterceptor接口自定义拦截器。

2.1 方法介绍

  • preHandle()

    该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作。返回true表示继续向下执行,返回false表示中断后续操作。

  • postHandle()

    该方法在控制器的处理请求方法执行之后、视图解析之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。

  • afterCompletion()

    该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

2.2 拦截器的使用

项目结构图

在这里插入图片描述

  1. 创建项目、添加jar包等步骤重复,省略

  2. 创建拦截器,实现HandlerInterceptor接口的MyInterceptor拦截器

    package com.bjsxt.interceptor;
    
    import org.springframework.ui.Model;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 自定义拦截器
     */
    public class MyInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("preHandle...处理请求方法前");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("postHandle...处理请求方法执行之后、视图解析之前");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("afterCompletion...处理请求方法执行完成后");
        }
    }
    
  3. 在spring-mvc.xml中配置拦截器

    <!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--拦截哪些请求-->
            <mvc:mapping path="/**"/>
            <!--不拦截哪些请求-->
            <mvc:exclude-mapping path="/user/getUsers"/>
            <bean class="com.bjsxt.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>
    

    【注意】不拦截是指不对该请求做相关处理,而不是拒绝访问该请求。

3、定义全局拦截器

全局拦截器是指可以拦截所有被控制器所处理URL。作用等同于/**

3.1 创建全局拦截器

package com.bjsxt.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

public class GlobalInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("GlobalInterceptor preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("GlobalInterceptor postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("GlobalInterceptor afterCompletion");
    }
}

3.2 在spring-mvc.xml中配置全局拦截器

<!--配置拦截器-->
<mvc:interceptors>
    <!--配置全局拦截器-->
    <bean class="com.bjsxt.interceptor.GlobalInterceptor"/>
    <mvc:interceptor>
        <!--拦截哪些请求-->
        <mvc:mapping path="/**"/>
        <!--不拦截哪些请求-->
        <mvc:exclude-mapping path="/user/getUsers"/>
        <bean class="com.bjsxt.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

3.3 说明

  • 如果全局拦截器和局部拦截器同时存在,方法的执行顺序:

    Global preHandle —> preHandle —> postHandle —> Global postHandle —> aftercompletion —> Global afterCompletion

  • 在拦截器的执行流程中,拦截器在前端控制器之后,它所拦截器都是控制器能够处理的请求,对于控制器不能处理的请求不会生效。

  • 如果一个URL能够被多个拦截器所拦截,全局拦截器最先执行,其他拦截器根据配置文件中配置的上下顺序来决定执行顺序的。先配置谁,谁就先执行。

SpringMVC对Restful风格的支持

1、Restful简介

REST: Representational State Transfer(表象层状态转变),是一种设计风格。

传统风格:http://localhost:8888/user/addUsers?username=oldlu&userage=30
Restful风格:http://localhost:8888/user/addUsers/oldlu/30

【优点】
结构清晰、符合标准、易于理解、扩展方便

2、处理 Restful风格的请求

  • { }占位符
  • @PathVaribale 将URI中占位符参数绑定到控制器处理方法的入参中
package com.bjsxt.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

//一个方法实现多个页面的跳转
@Controller
@RequestMapping("/page")
public class PageController {

    @RequestMapping("/{jsp}/aaa/{id}")
    public String toPage(@PathVariable(value="jsp") String page, @PathVariable String id, Model model){
        model.addAttribute("msg",id+"");
        return page;
    }
}

【异常信息】java.lang.Integer cannot be cast to java.lang.String

@PathVariable注解已经将浏览器地址栏传递的String类型的id转为Integer类型。但在jsp页面中通过EL表达式获取时,一直提示类型转换失败!!model.addAttribute(“msg”,id+"")解决。

JSON数据处理

在SpringMVC中使用的是Jackson APIl实现对JSON格式数据处理。需要添加Jackson的jar包。

  • jackson-annotations-2.11.0.jar
  • jackson-core-2.11.0.jar
  • jackson-databind-2.11.0.jar

1、搭建环境

  1. 创建项目添加jar包

  2. 添加spring配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    </beans>
    
  3. 修改web.xml配置前端控制器

    <!--配置前端控制器-->
    <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:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springMvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
  4. 在src目录下创建com.bjsxt.web.controller包

  5. 修改sprin配置文件,开启直接扫描,配置注解驱动,配置视图解析器

    <!--添加context命名空间,开启注解扫描-->
    <context:component-scan base-package="com.bjsxt.web.controller"/>
    
    <!--配置注解驱动-->
    <mvc:annotation-driven/>
    
    <!--配置视图解析器-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
  6. 在WEB-INF/js目录下添加Jquery.js

  7. 修改spring配置文件,配置静态资源映射器

    <!--配置静态资源映射器-->
    <mvc:resources mapping="/js/**" location="/WEB-INF/js/"/>
    

2、处理请求中的JSON格式数据

在处理请求中的JSON格式数据时需要使用@RequestBody 注解。

@RequestBody
RequestBody注解可以将JSON格式的数据转为Java对象。但是要求content-type 不是默认的application/x-www-form-urlcoded编码的内容。一般情况下来说常用其来处理application/json类型。

  1. 创建Users实体类

    package com.bjsxt.pojo;
    
    public class Users {
        private String username;
        private int userage;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public int getUserage() {
            return userage;
        }
    
        public void setUserage(int userage) {
            this.userage = userage;
        }
    
        @Override
        public String toString() {
            return "Users{" +
                    "username='" + username + '\'' +
                    ", userage=" + userage +
                    '}';
        }
    }
    
  2. 添加addusers.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
        <script src="/js/jquery.js"></script>
        <script>
            $(function(){
               $("#button").click(function(){
                  var name = $("#username").val();
                  var age = $("#userage").val();
                  var obj = {
                    username:name,
                    userage:age
                  };
                  var user = JSON.stringify(obj);
                  $.ajax({
                      url:"/user/addUsers",
                      contentType:"application/json",
                      type:"post",
                      data:user,
                      success:function(res){
                          alert(res)
                      }
                  });
    
               });
            });
        </script>
    </head>
    <body>
    
        用户姓名:<input type="text" id="username"><br>
        用户年龄<input type="text" id="userage"><br>
        <input type="button" value="ok" id="button">
    
    </body>
    </html>
    
  3. 创建PageController

    package com.bjsxt.web.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/page")
    public class PageController {
    
        @RequestMapping("/{page}")
        public String showPage(@PathVariable String page){
            return page;
        }
    }
    
  4. 创建UsersController

    package com.bjsxt.web.controller;
    
    import com.bjsxt.pojo.Users;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.io.PrintWriter;
    
    @Controller
    @RequestMapping("/user")
    public class UsersController {
    
        @RequestMapping("/addUsers")
        public void addUsers(@RequestBody Users users, HttpServletResponse response) throws IOException {
            System.out.println(users);
            PrintWriter writer = response.getWriter();
            writer.print("ok");
            writer.flush();
            writer.close();
        }
    }
    

3、处理响应中的json格式数据

将响应结果转换成JSON格式数据时,需要使用@ResponseBody注解。

@ResponseBody

@ResponseBody注解的作用是将处理请求方法返回的对象通过转换器转换为JSON格式数据,同时写入到response对象的body区,通常用来返回JSON 数据。需要注意,在使用此注解之后不会经过视图解析器,而是直接将数据写入到输出流中,他的效果等同于通过response对象输出指定格式的数据。

  • 如果处理请求方法返回的是String时,@ResponseBody注解不会进行JSON转换。响应的Content-Type为text/plain;charset=lSO-8859-1。

  • 如果处理请求方法返回的是除了String类型以外的其他object类型时,@ResponseBody注解会进行JSON转换。响应的Content-Type为application/json。

  1. 修改控制器

    返回值是String类型

    package com.bjsxt.web.controller;
    
    import com.bjsxt.pojo.Users;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.io.IOException;
    
    @Controller
    @RequestMapping("/user")
    public class UsersController {
    
        @RequestMapping(value = "/addUsers",produces = MediaType.TEXT_PLAIN_VALUE+";charset=utf-8")
        @ResponseBody
        public String addUsers(@RequestBody Users users) throws IOException {
            System.out.println(users);
            return "ok";
        }
    }
    

    返回值是其他类型

    package com.bjsxt.web.controller;
    
    import com.bjsxt.pojo.Users;
    import org.springframework.http.MediaType;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import java.io.IOException;
    
    @Controller
    @RequestMapping("/user")
    public class UsersController {
    
        @RequestMapping(value = "/addUsers",produces = MediaType.APPLICATION_JSON_VALUE+";charset=utf-8")
        @ResponseBody
        public Users addUsers(@RequestBody Users users) throws IOException {
            System.out.println(users);
            return users;
        }
    }
    
  2. 解决响应中JSON数据中文乱码问题

    //返回值是String类型时
    @RequestMapping(value="/addUsers",produces = MediaType.TEXT_PLAIN_VALUE+";charset=utf-8")
    
    //返回值是其他类型时
    @RequestMapping(value="/addUsers",produces=MediaType.APPLICATION_JSON_VALUE+";charset=utf-8")
    

SpringMVC解决跨域请求问题

1、什么是同源策略

同源策略是浏览器的一个安全功能,所谓的同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。

哪些不受同源策略限制:

  1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的
  2. 跨域资源的引入是可以的。如嵌入到页面中的scrpit img link href frame等

2、什么是跨域请求

在JavaScript的请求中当一个请求URL的协议、域名、端口三者之间任意一个与当前页面URL不同时即为跨域。浏览器执行JavaScript脚本时,会检查当前请求是否同源,如果不是同源中的资源,就不会被执行。

在这里插入图片描述

3、通过@CrossOrigin解决跨域

  • CrossOrigin:注解用于处理跨域请求访问。是SpringMVC4.2版本中推出的注解
  • origins:允许可访问的域列表,String类型的数组
  • maxAge:准备响应前的缓存持续的最大时间(以秒为单位)
@Controller
@RequestMapping("/user")
public class UsersController {

    @RequestMapping(value = "/addUsers",produces = MediaType.APPLICATION_JSON_VALUE+";charset=utf-8")
    @ResponseBody
    @CrossOrigin(origins = "http://localhost:8888")
    public Users addUsers(@RequestBody Users users) throws IOException {
        System.out.println(users);
        return users;
    }
}

SpringMVC常用注解

  1. @Controller
  2. @RequestMapping
  3. @GetMapping
  4. @PostMapping
  5. @PathVariable,restful风格
  6. @RequestParam
  7. @RequestBody
  8. @ResponseBody
  9. @RequestHeader
  10. @CookieValue
@Controller
@RequestMapping("/page")
public class PageController {

    @RequestMapping("/{page}")
    public String showPage(@PathVariable String page, @RequestHeader("Accept-Language") String language, @CookieValue("JSESSIONID") String value){
        System.out.println(language);
        System.out.println(value);
        return page;
    }
}

SSM框架整合

1、SSM框架整合介绍

1.1 整合步骤

  1. 创建项目,添加jar包
  2. 创建包,创建实体与Mapper
  3. 添加连接数据库的properties文件
  4. 添加Log4j配置文件
  5. 添加Spring配置文件
  6. 添加SpringMVC配置文件
  7. 修改web.xml文件

1.2 jar包依赖

ssm整合jar包依赖共计27个jar包。所有jar包位于springMVC\软件\ssmlib目录。

2、搭建环境

2.1 创建表

CREATE TABLE `users` (
  `userid` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) NOT NULL,
  `userpwd` varchar(30) NOT NULL,
  PRIMARY KEY (`userid`),
  UNIQUE KEY `username_uk` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2 创建项目及结构

  1. 创建项目,添加jar包

  2. 创建包,创建实体类与Mapper

在这里插入图片描述

2.3 配置SSM整合

  1. 添加连接数据的properties文件

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/ssm?useSSL=false
    jdbc.username=root
    jdbc.password=1615
    
  2. 添加log4j配置文件

    log4j.rootLogger=info,console
    
    ### appender.consoleê?3?μ?????ì¨ ###
    log4j.appender.console=org.apache.log4j.ConsoleAppender
    log4j.appender.console.layout=org.apache.log4j.PatternLayout
    log4j.appender.console.layout.ConversionPattern=<%d> %5p (%F:%L) [%t] (%c) - %m%n
    log4j.appender.console.Target=System.out
    
    ### appender.logfileê?3?μ?è??????t ###
    log4j.appender.logfile=org.apache.log4j.RollingFileAppender
    log4j.appender.logfile.File=SysLog.log
    log4j.appender.logfile.MaxFileSize=500KB
    log4j.appender.logfile.MaxBackupIndex=7
    log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
    log4j.appender.logfile.layout.ConversionPattern=<%d> %p (%F:%L) [%t] %c - %m%n
    
  3. 添加Spring配置文件

    在SSM框架整合时为了能够清晰的对Spring 配置项进行管理,我们可以采用“分而治之”的方式来定义Spring配置文件。将Spring配置文件分解成三个配置文件,在不同的配置文件中配置不同的内容。

    • applicationContext-dao.xml 该配置文件的作用是用来配置与Mybatis框架整合
    • applicationContext-trans.xml 该配置文件的作用是用来配置Spring声明式事务管理
    • applicationContext-service.xml 该配置文件的作用是用来配置注解扫描以及其他的Spring配置
  4. 配置整合Mybatis

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--配置解析db.properties的工具-->
        <context:property-placeholder location="classpath:db.properties"/>
    
        <!--配置数据源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
        <!--配置SqlSessionFactoryBean-->
        <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <property name="typeAliasesPackage" value="com.bjsxt.pojo"/>
        </bean>
    
        <!--配置MapperScannerConfigurer-->
        <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
            <property name="basePackage" value="com.bjsxt.mapper"/>
        </bean>
    </beans>
    
  5. 配置声明式事务管理

    <?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:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!--配置事务管理器切面-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!--配置事务管理器的属性-->
        <tx:advice id="myAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="add*" propagation="REQUIRED"/>
                <tx:method name="modify*" propagation="REQUIRED"/>
                <tx:method name="drop*" propagation="REQUIRED"/>
                <tx:method name="find*" read-only="true"/>
            </tx:attributes>
        </tx:advice>
    
        <!--配置切面-->
        <aop:config>
            <aop:pointcut id="myPointCut" expression="execution(* com.bjsxt.service.*.*(..))"/>
            <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
        </aop:config>
    </beans>
    
  6. 开启Spring注解扫描

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--开启注解扫描-->
        <context:component-scan base-package="com.bjsxt.service" />
    </beans>
    
  7. 配置SpringMVC

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--配置注解扫描-->
        <context:component-scan base-package="com.bjsxt.web.controller"/>
    
        <!--配置注解驱动-->
        <mvc:annotation-driven/>
    
        <!--配置视图解析器-->
        <bean id="servlet" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
        <!--配置静态资源映射器-->
        <mvc:resources mapping="/js/**" location="/WEB-INF/js/"/>
        <mvc:resources mapping="/img/**" location="/WEB-INF/img/"/>
        <mvc:resources mapping="/css/**" location="/WEB-INF/css/"/>
    </beans>
    
  8. 配置web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
             version="4.0">
    
        <!--指定Spring配置文件名称和位置-->
        <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext-*.xml</param-value>
        </context-param>
    
        <!--配置启动Spring框架的监听器-->
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
    
        <!--配置前端控制器-->
        <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:spring-mvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springMvc</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>
    

3、Spring 与 SpringMVC父子容器问题

Spring的ContextLoaderListener会创建Spring的IoC容器,标记为A容器;

SpringMVC的DispatcherServlet会创建SpringMVC的IoC容器,标记为a容器;

SpringMVC会将A容器设置为其父容器。

父容器对子容器可见,所以在子容器中可以访问父容器的Bean对象;而子容器对父容器不可见。

注解扫描时需要注意

  • 如果在Spring MVC配置文件中扫描所有注解,声明式事务会失效

    因为Spring声明式事务管理的切面位于A容器,a容器扫描注解得到的Bean对象对A容器不可见,所以事务管理器无法对B容器中的Bean对象实现事务控制。

  • 如果在Spring配置文件中扫描所有注解,无法找到控制器报404异常

    此时控制器对象位于A容器,但HandleMapping在根据URI查找控制器时,只会去a容器中查找,所以会出现404异常。

正确使用方式

在Spring MVC配置文件中扫描@Controller注解,在Spring配置文件中扫描除Controller 以外的其他注解。在Spring MVC的控制器可以注入SpringloC容器中的Bean对象,因为父容器对子容器是可见的。

4、实现用户登录

需求:实现用户登录业务,同时记录用户的登录日志。登录日志要求记录用户名、登录时间以及登录的IP地址。

4.1 用户登录

创建业务层

package com.bjsxt.service;

import com.bjsxt.pojo.Users;

public interface UsersService {
    Users userLogin(Users users);
}
package com.bjsxt.service.impl;

import com.bjsxt.exception.UserNotFoundException;
import com.bjsxt.mapper.UsersMapper;
import com.bjsxt.pojo.Users;
import com.bjsxt.pojo.UsersExample;
import com.bjsxt.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UsersServiceImpl implements UsersService {

    @Autowired
    private UsersMapper usersMapper;

    /**
     * 用户登录
     * @param users
     * @return
     */
    @Override
    public Users userLogin(Users users) {
        UsersExample usersExample = new UsersExample();
        UsersExample.Criteria criteria = usersExample.createCriteria();
        criteria.andUsernameEqualTo(users.getUsername());
        criteria.andUserpwdEqualTo(users.getUserpwd());
        List<Users> list = usersMapper.selectByExample(usersExample);

        if(list == null || list.size() <= 0){
            throw new UserNotFoundException("用户名或密码有误");
        }
        return list.get(0);
    }
}

创建自定义异常

package com.bjsxt.exception;

public class UserNotFoundException extends RuntimeException{
    public UserNotFoundException() {
        super();
    }

    public UserNotFoundException(String message) {
        super(message);
    }

    public UserNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
}

创建用户登录控制器

package com.bjsxt.web.controller;

import com.bjsxt.pojo.Users;
import com.bjsxt.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpSession;

/**
 * 用户管理控制器
 */
@Controller
@RequestMapping("/user")
public class UsersController {

    @Autowired
    private UsersService usersService;

    /**
     * 处理用户登录请求
     */
    public String userLogin(Users users, HttpSession session){
        Users user = this.usersService.userLogin(users);
        session.setAttribute("user",user);
        return "redirect:/page/index";
    }
}

全局异常处理器

package com.bjsxt.web.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 全局异常处理器
 */
@ControllerAdvice
public class GlobalExceptionController {
    @ExceptionHandler({com.bjsxt.exception.UserNotFoundException.class})
    public String userNotFoundExceptionHandle(Exception e, Model model){
        model.addAttribute("msg",e.getMessage());
        return "login";
    }

    @ExceptionHandler({java.lang.Exception.class})
    public String exceptionHandle(Exception e){
        return "redirect:/page/error";
    }

}

创建用户登录页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    ${msg}
    <form action="/user/userLogin" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="text" name="userpwd"><br>
        <input type="submit" value="ok">
    </form>
</body>
</html>

创建异常提示页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Error</title>
</head>
<body>
    出错了,请与管理员联系。
    email:1615669687@qq.com
</body>
</html>

创建页面跳转控制器

package com.bjsxt.web.controller;

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

/**
 * 关于为什么不在PageController加@RequestMapping注解?
 * Tomcat启动默认访问index,url中没有/page。
 */
@Controller
public class PageController {

    @RequestMapping("/")
    public String showIndex(){
        return "index";
    }

    /**
     * 除首页以外页面的跳转
     */
    @RequestMapping("/page/{page}")
    public String showPage(@PathVariable String page){
        return page;
    }
}

创建拦截器——判断用户是否登录

package com.bjsxt.interceptor;

import com.bjsxt.pojo.Users;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

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

public class LoginInterceptor implements HandlerInterceptor {

    /**
     * 判断用户是否登录
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Users user = (Users)session.getAttribute("user");

        if(user == null || user.getUsername().length() <= 0){
            response.sendRedirect("/page/login");
            return false;
        }
        return true;
    }
}

配置拦截器

<!--配置拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/page/login"/>
        <mvc:exclude-mapping path="/user/userLogin"/>
        <mvc:exclude-mapping path="/page/error"/>
        <bean class="com.bjsxt.interceptor.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

4.2 记录用户登录日志

  1. 创建表

    CREATE TABLE `logs` (
      `logid` int(10) NOT NULL AUTO_INCREMENT,
      `username` varchar(30) DEFAULT NULL,
      `ip` varchar(30) DEFAULT NULL,
      `logtime` date DEFAULT NULL,
      PRIMARY KEY (`logid`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
  2. 创建实体类与Mapper

  3. 创建日志记录切面

    package com.bjsxt.aop;
    
    import com.bjsxt.mapper.LogsMapper;
    import com.bjsxt.pojo.Logs;
    import com.bjsxt.pojo.UsersExt;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    /**
     * 用户登录日志记录切面
     */
    @Aspect
    @Component
    public class UsersLoginLogAOP {
    
        @Autowired
        private LogsMapper logsMapper;
    
        /**
         * 配置切点
         */
        @Pointcut("execution(* com.bjsxt.service.UsersService.userLogin(..))")
        public void myPointCut(){}
    
        /**
         * 后置通知 记录日志
         */
        @AfterReturning("myPointCut()")
        public void userLoginLog(JoinPoint joinPoint){
            Object[] objs = joinPoint.getArgs();
            UsersExt user = (UsersExt)objs[0];
            Logs logs = new Logs();
    
            logs.setUsername(user.getUsername());
            logs.setLogtime(new Date());
            logs.setIp(user.getIp());
            this.logsMapper.insertSelective(logs);
        }
    }
    
  4. 创建Users扩展实体类

    package com.bjsxt.pojo;
    
    public class UsersExt extends Users{
        private String ip;
    
        public String getIp() {
            return ip;
        }
    
        public void setIp(String ip) {
            this.ip = ip;
        }
    }
    
  5. 修改控制器

    package com.bjsxt.web.controller;
    
    import com.bjsxt.pojo.Users;
    import com.bjsxt.pojo.UsersExt;
    import com.bjsxt.service.UsersService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.http.HttpRequest;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    /**
     * 用户管理控制器
     */
    @Controller
    @RequestMapping("/user")
    public class UsersController {
    
        @Autowired
        private UsersService usersService;
    
        /**
         * 处理用户登录请求
         */
        @RequestMapping("/userLogin")
        public String userLogin(UsersExt users, HttpSession session, HttpServletRequest request){
            String ip = request.getRemoteAddr();
            users.setIp(ip);
            Users user = this.usersService.userLogin(users);
            session.setAttribute("user",user);
            // return "index";
            //关于为什么这里必须是重定向,且redirect:/page/index
            return "redirect:/page/index";
    
        }
    }
    
  6. 配置切面

    • 在applicationContext-service.xml中开启aop包注解扫描

      <!--开启注解扫描-->
      <context:component-scan base-package="com.bjsxt.service,com.bjsxt.aop" />
      
    • 在applicationContext-trans.xml中开启aspecyJ自动代理和登录操作的事务管理

      <?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:aop="http://www.springframework.org/schema/aop"
             xmlns:tx="http://www.springframework.org/schema/tx"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
             http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
             http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
      
          <!--配置事务管理器切面-->
          <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
              <property name="dataSource" ref="dataSource"/>
          </bean>
      
          <!--配置事务管理器的属性-->
          <tx:advice id="myAdvice" transaction-manager="transactionManager">
              <tx:attributes>
                  <tx:method name="add*" propagation="REQUIRED"/>
                  <tx:method name="modify*" propagation="REQUIRED"/>
                  <tx:method name="drop*" propagation="REQUIRED"/>
                  <tx:method name="find*" read-only="true"/>
                  <!--由于用户登录的操作会使得日志记录表有插入操作,所以该方法也需要事故管理-->
                  <tx:method name="userLogin" propagation="REQUIRED"/>
              </tx:attributes>
          </tx:advice>
      
          <!--配置切面-->
          <aop:config>
              <aop:pointcut id="myPointCut" expression="execution(* com.bjsxt.service.*.*(..))"/>
              <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointCut"/>
          </aop:config>
      
          <aop:aspectj-autoproxy proxy-target-class="true"/>
      </beans>
      

【一个异常】提示找不到Logs LogsMapper类

原因:对于aop包,不应在spring-mvc.xml中扫描注解。

基于Maven搭建SSM整合

使用Spring两个问题:

  • jar包管理不易
  • 配置文件管理不易

Maven能够很好的解决jar包管理的问题,通过给定坐标,自动导入jar包依赖。

1、创建Maven项目

将普通的jar项目改造为war项目

  1. 创建目录和相关文件

    src—>main—>webapp—>WEB-INF—>web.xml

  2. 在pom.xml文件中添加

    <groupId>com.bjsxt</groupId>
    <artifactId>maven_ssm_demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <!--新添加-->
    <packaging>war</packaging>
    
  3. 如果webapp目录不能直接新建jsp文件,在项目结构中通过modules添加web组件

在这里插入图片描述

2、配置镜像地址

Maven中央仓库是国外站点,下载构件较慢,可配置国内的阿里镜像或其他镜像地址。配置镜像地址时需要Maven的settings.xml配置文件。该配置文件需要从Apache下载的Maven中获取。最后在ldea中指定该配置文件路径。

3、添加jar包

Maven通过坐标管理jar包或插件。在中央仓库查找依赖构件的坐标,并将该坐标添加到Maven的pom.xml文件中。

Maven查找构件的顺序:先从本地仓库中查找,如果本地仓库没有则会去中央仓库下载并保存到本地仓库中。

中央仓库地址:

https://mvnrepository.com/

本地仓库默认路径为:

C:\Users\86150\.m2\repository

可以在settings.xml文件中指定本地仓库的路径

<localRepository>F:\.m2\repository</localRepository>

也可以在ldea中指定Maven的设置文件和本地仓库路径

在这里插入图片描述

3.1 配置依赖jar包的坐标

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
        <scope>runtime</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.12</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.3.12</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.12</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>

    <!-- 注意【https://mvnrepository.com/artifact/javax.servlet】 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.48</version>
    </dependency>
</dependencies>

4、配置Tomcat插件

5、配置资源拷贝路径

<build>
    <plugins>
        <!-- 配置Tomcat插件 -->
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <!-- 配置Tomcat监听端口 -->
                <port>8080</port>
                <!-- 配置项目的访问路径(Application Context) -->
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
    <!--配置资源拷贝路径-->
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>

配置Tomcat插件显示异常

Cannot resolve plugin org.apache.tomcat.maven:tomcat7-maven-plugin

【解决】使用version标签添加版本号

为什么要配置资源拷贝路径?

【原因】Maven默认从resources目录加载配置文件,但UsersMapper.xml、UsersMapper.java等映射配置文件位于com.bjsxt.mapper包下,为了加载全部的配置文件,配置java和resources两个资源拷贝目录。

启动Tomcat插件直接报错,process terminated,意思是本地仓库都默认路径拒绝Maven访问

【解决】将repository目录和settings.xml文件复制到F盘,在IDEA中重新指定路径。

6、配置框架配置文件

UsersMapper.java、UsersMapper.java等放在mapper包中,其他的.properties文件、.xml文件放在resources目录中。

在这里插入图片描述

7、实现用户查询

  1. 创建查询用户到控制器

    package com.bjsxt.web.controller;
    
    import com.bjsxt.pojo.Users;
    import com.bjsxt.service.UsersService;
    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.List;
    
    @Controller
    @RequestMapping("/user")
    public class UsersController {
    
        @Autowired
        private UsersService usersService;
    
        @RequestMapping("/findUsers")
        public String findUsersAll(Model model){
            List<Users> list = this.usersService.findUsersAll();
            model.addAttribute("list",list);
            return "showUsers";
        }
    }
    
  2. 创建展示用户信息的页面

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <html>
    <head>
        <title>show users</title>
    </head>
    <body>
        <table border="1px" align="center">
            <tr>
                <th>用户ID</th>
                <th>用户名</th>
                <th>密码</th>
            </tr>
            <%--注意这里的list也要使用EL表达式取--%>
            <c:forEach items="${list}" var="user">
                <tr>
                    <td>${user.userid}</td>
                    <td>${user.username}</td>
                    <td>${user.userpwd}</td>
                </tr>
            </c:forEach>
        </table>
    </body>
    </html>
    
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-12 19:27:07  更:2021-11-12 19:27:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 1:19:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码