SpringMVC学习笔记

SpringMVC学习笔记

SpringMVC概述

SpringMVC简介

SpringMVC 也叫 Spring Web MVC,是 Spring框架的一部分,在 Spring3.0后发布的

  • 基于 MVC架构,功能分工明确,解耦合

  • 容易理解上手快,可以使用注解快速开发一个 SpringMVC项目,同时也是一个轻量级的框架,jar很小,不依赖特定的接口和类

  • 作为 Spring框架的一部分,能够使用 Spring的 IoC和 AOP

  • 方便整合 Struts, MyBatis, Hibernate, JPA等框架

  • SpringMVC可以使用强化注解

    • @Controller创建处理器对象,@Service创建业务对象,@Autowired或者@Resource可以在控制器类中注入 Service

第一个注解的SpringMVC程序

所谓 SpringMVC的注解开发是指,在代码中通过对类和方法的注解,便可完成处理器在 SpringMVC容器的注册

1)创建 maven项目

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

2)注册中央调度器

<!-- 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:springmvc.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>

DispatcherServlet是中央调度器

<load-on-startup>标记什么时候创建 Servlet实例。即是否在 Web服务器启动时调用执行该 Servlet的 init()方法

  • 必须是一个整数
  • 大于等于0 表示容器在启动时就加载并初始化这个 Servlet,数值越小,优先级越高,越早创建
  • 小于0或未指定 表示该 Servlet在真正被使用时才创建
  • 当值相同时,容器会自己选择创建顺序

3)创建 SpringMVC配置文件

4)创建处理器

在类上与方法上添加相应注解即可。

@Controller:表示当前类为处理器

@RequestMapping:表示当前方法为处理器方法,该方法要对 value属性所指定的 URI进行处理与响应

@Controller
public class MyController {
    @RequestMapping(value="/some.do")
    public ModelAndView doSome() {
        System.out.println("处理 some.do请求");
        // 调用 service 处理请求,把处理结果放入到返回值 ModelAndView
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "使用注解的SpringMVC应用");
        mv.addObject("fun", "doSome");
        mv.setViewName("show");
        return mv;
    }
}

若有多个请求路径均可匹配该处理器方法的执行,则 @RequestMapping的 value属性中可以写上一个数组。

ModelAndView类中的 addObject()方法用于向其 Model中添加数据,Model的底层是一个 HashMap

Model中的数据存储在 Request域中,SpringMVC默认采用转发的方式跳转到视图,本次请求结束,模型中的数据被销毁。

5)声明组件扫描器

<context:component-scan base-package="com.bjpowernode.controller" />

6)定义目标页面

<html>
    <head>
        <title>title</title>
    </head>
    
    <body>
        /WEB-INF/view/show.jsp<br>
        <h3>
            msg数据:${msg}
        </h3>
        <h3>
            fun数据:${fun}
        </h3>
    </body>
</html>

7)配置视图解析器

SpringMVC框架为了避免对于请求资源路径与扩展名上的冗余,在视图解析器 InternalResourceViewResolver中引入了请求的前缀和后缀。

在 ModelAndView中只需要给出要跳转页面的文件名即可,具体的文件路径和扩展名,视图解析器会自动完成拼接

<!-- springmvc.xml -->
<!-- 注册视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceVireResolver">
	<property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
</bean>

8)使用 SpringMVC框架 web请求处理顺序

SpringMVC的MVC组件

SpringMVC的执行流程

简单的流程分析

  1. 浏览器提交请求到中央调度器
  2. 中央调度器直接将请求转给处理器映射器
  3. 处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行链后返回给中央调度器
  4. 中央调度器根据处理器执行链中的处理器,找到能够执行该处理器的处理器适配器
  5. 处理器适配器调用执行处理器
  6. 处理器将处理结果及要跳转的视图封装到一个对象 ModelAndView中,并将其返回给处理器适配器
  7. 处理器适配器直接将结果返回给中央调度器
  8. 中央调度器调用视图解析器,将 ModelAndView中的视图名称封装为视图对象
  9. 视图解析器将封装了的视图对象返回给中央调度器
  10. 中央调度器调用视图对象,让其自己进行渲染,即进行数据填充,形成响应对象
  11. 中央调度器响应浏览器

SpringMVC注解式开发

@RequestMapping定义请求规则

指定模块名称

通过 @RequestMapping注解可以定义处理器对于请求的映射规则。该注解可以作用在方法上、类上。但意义不同,value属性值常以 / 开头

@RequestMapping 的 value属性用于定义所匹配请求的 URI,但对于注解在方法上与类上,value属性所指定的 URI的意义不同

一个 @Controller注解的类中,可以定义多个处理器方法。当然,不同的处理器方法所匹配的 URI是不同的。这些不同的 URI被指定在注解与方法之上的 @RequestMapping 的 value属性中。但若这些请求具有相同的 URI部分,则这些相同的 URI可以被抽取到注解在类之上的 @RequestMapping的 value属性中。此时的这个 URI表示模块的名称,URI的请求是相对于 Web的根目录的。

换个角度说,要访问处理器的指定方法,必须要在方法指定 URI之前加上处理器类前定义的模块名称

1)修改 MyController

@Controller
@RequestMappint(value="/test")
public class MyController {
    
    @RequestMapping(value="/some.do")
    public ModelAndView doSome() {
        return new ModelAndView("some");
    }
    
    
    @RequestMapping(value="/other.do")
    public ModelAndView doSome() {
        return new ModelAndView("other");
    }
}

2)添加视图页面

<!-- 页面一 -->
<body>
    some page
</body>


<!-- 页面二 -->
<body>
    other page
</body>

定义请求提交方式

对于 @RequestMapping,有一个属性 method,用于对被注解方式所处理请求的提交方式进行限制,即只有满足该 method属性指定的提交方式的请求,才会执行该被注解方法。

Method属性的取值为 RequestMethod 枚举常量。常用的为 RequestMethod.GET 与 RequestMethod.POST,分别表示 GET提交和 POST提交。

1)修改MyController

@Controller
public class MyController {
    
    @RequestMapping(value="/some.do", method=RequestMethod.GET)
    public ModelAndView doSome() {
        return new ModelAndView("some");
    }
    
    
    @RequestMapping(value="/other.do", method=RequestMethod.POST)
    public ModelAndView doSome() {
        return new ModelAndView("other");
    }
}

2)修改前端页面

<a href="some.do">跳转到some页面</a><br />
<a href="other.do">跳转到other页面</a><br />

<form action="some.do" method="POST">
    <input type="submit" value="跳转到some页面" />
</form>
<br />
<form action="other.do" method="POST">
    <input type="submit" value="跳转到other页面" />
</form>

处理器方法的参数

处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序员可以在方法内直接调用

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • 请求中所携带的请求参数

逐个参数接收

只要保证请求参数名与该请求处理方法的参数名相同即可

1)修改前端页面

<form action="register.do" method="POST">
    姓名:<input type="text" name="name" /><br>
    年龄:<input type="text" name="age" /><br>
    <input type="submit" value="注册" />
</form>

2)修改MyController

@Controller
public class MyController {
    
    @RequestMapping(value="/some.do")
    public ModelAndView doSome(String name, int age) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("myname", name);
        mv.addObject("myage", age);
        mv.setViewName("show")
        return mv;
    }
}

3)添加 show页面

在 /WEB-INF/jsp 下添加 show.jsp 页面

<body>
    name=${myname}<br>
    age=#{myage}<br>
</body>

请求参数中文乱码问题

对于前面所接收的请求参数,若出现中文,则会产生中文乱码问题。Spring对于请求参数中的中文乱码问题,给出了专门的字符集过滤器:CharacterEncodingFilter

1)在 web.xml 中注册字符集过滤器

<filter>
	<filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
    	<param-name>encoding</param-name>
        <param-value>utf8</param-value>
    </init-param>
</filter>
<filter-mapping>
	<filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*/</url-pattern>
</filter-mapping>

2)源码分析

字符集设置核心方法:

@RequestParam 校正请求参数

所谓校正请求参数名,是指若请求 URL所携带的参数名称与处理方法中指定的参数名不相同时,则需在处理方法参数前,添加一个注解 @RequestParam(“请求参数名”)

该注解是对处理器方法参数进行修饰的,value属性指定请求参数的名称

1)修改 index页面

<form action="register.do" method="POST">
    姓名:<input type="text" name="rname" /><br>
    年龄:<input type="text" name="rage" /><br>
    <input type="submit" value="注册" />
</form>

2)修改处理器类 MyController

@Controller
public class MyController {
    
    @RequestMapping(value="/some.do")
    public ModelAndView doSome(@RequestParam(value="rname") String name, @RequestParam(value="rage") int age) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("myname", name);
        mv.addObject("myage", age);
        mv.setViewName("show")
        return mv;
    }
}

required属性:boolean类型,默认值是 true 表示请求中必须有参数;设置为 false后,表示可以没有参数

@RequestMapping(value="some.do")
public ModelAndView doSome(@RequestParam(value="rage", required=false) Integer age, @RequestParam(value="rname", required=false) String name) {}

对象参数接收

将处理器方法的参数定义为一个对象,只要保证请求参数名和这个对象的属性同名即可。

1)定义实体类

// 定义实体类
public class Student {
    private String name;
    private Integer age;
    // getter and setter, toString()
}

2)修改处理器类 MyController

@Controller
public class MyController {
    
    @RequestMapping(value="/register.do")
    public ModelAndView doSome(Student student) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("mystudent", student);
        return mv;
    }
}

3)修改 show页面

<body>
    studnet = ${myStudent} <br>
</body>

处理器方法的返回值

使用 @Controller注解的处理器的处理方法,通常有四种类型的返回值:

  • ModelAndView
  • String
  • void
  • 自定义类型

返回ModelAndView

若处理器方法处理完之后,需要跳转到其他资源,且又要在跳转的资源间传递数据,此时处理器方法返回 ModelAndView 比较好。

当然,若要返回 ModelAndView,则处理器方法中需要定义 ModelAndView 对象

在使用时,若该处理器方法只是进行跳转而不传递数据,或只是传递数据而不向任何资源跳转(如对页面 Ajax的异步响应),此时若返回 ModelAndView,则不合适。

返回 String

处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址。

返回内部资源逻辑视图名

若要跳转的资源为内部资源,则视图解析器可以使用 InternalResourceViewResolver内部资源视图解析器。此时处理器方法返回的字符串就是要跳转页面的文件名去掉文件扩展名后的部分。这个字符串与视图解析器中的 prefix, suffix相结合,即可形成要访问的 URI

<!-- 注册视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/WEB-INF/admin/" />
    <property name="suffix" value=".jsp" />
</bean>
// 修改处理器类 MyController
@Controller
public class MyController {
    @RequestMapping(value="/register.do")
    public String register(HttpServletRequest request, Student student) {
        request.setAttribute("myStudent", student);
        return "show";
        // 当然也可以直接返回资源的物理视图名,此时就不需要再在视图解析器中再配置前缀和后缀了
        // return "/WEB-INF/jsp/welcome.jsp";
    }
}

返回 void

响应Ajax请求是处理器方法返回 void的主要应用场景

若处理器对请求处理后,无需跳转到其它任何资源,此时可以让处理器方法返回 void

1)添加 pom依赖

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

2)引入 jQuery cdn文件

<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

3)定义 index页面

<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    $(function() {
        $("button").click(function() {
            $.ajax({
                url:"myajax.do",
                data:{
                    name:"zs",
                    age:"24"
                },
                type="post",
                dataType="json",
                success:function(resp) {
                alert("resp:" + resp.name + "  " + resp.age);
            }
        })
    })
    })
</script>

<body>
    index.jsp<br>
    <button>
        发起Ajax请求
    </button>
</body>

4)定义实体类

// 定义实体类
public class Student {
    // 属性名和请求参数名一样
    private String name;
    private Integer age;
    // setter and getter, toString()
}

5)修改处理器类 MyController

处理器对于 Ajax请求中所提交的参数,可以使用逐个接收的方式,也可以以对象的方式整体接收。只要保证 Ajax请求参数与接收的对象类型属性同名即可。

// 修改处理器类,处理器方法返回 void,不能表示数据,也没有视图
// 可以通过 HttpServletResponse 的输出对象,把数据输出到浏览器
@RequestMapping(value="/myajax.do")
public void doSome(Integer age, String name, HttpServletResponse response) throws IOException {
    // 调用 Service处理请求,把处理结果转为一个对象存储
    Student student = new Student(name, age);
    // 调用 jackson工具库,把 Student转为 json
    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(studnet);
    System.out.println("json:" + json);
    // 使用 HttpServletResponse输出数据到浏览器
    PrintWriter pw = response.getWriter();
    pw.print(json);
    pw.flush();
    pw.close();
}

返回对象 Object

处理器方法也可以返回 Object对象。这个 Object可以是 Integer, String, 自定义对象, Map, List等。但返回的对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的。

返回对象,需要使用 @ResponseBody注解,将转换后的 JSON数据放入到响应体中

1)添加 pom依赖

由于返回 Object数据,一般都是将数据转化为 JSON对象后传递给浏览器页面的,而这个由 Object转换为 JSON,是由 Jackson工具完成的,所以需要导入 Jackson的相关 jar包

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

2)声明注解驱动

将 Object数据转化为 JSON数据需要由消息转换器 HttpMessageConverter完成,因此需要在 springmvc.xml配置文件中添加 <mvc:annotation-driven />标签

SpringMVC使用消息转换器实现请求数据和对象,处理器方法返回对象和响应输出之间的自动转换。

HttpMessageConverter接口中定义了方法:canRead(), Read(), canWrite(), Write()

  • canRead():指定转换器可以读取的对象类型,指定支持的 MIME类型
  • Read():将请求信息流转换为指定类型的对象
  • canWrite():指定转换器可以写出的对象类型,响应流支持的媒体类型在 MediaType中定义
  • Write():将指定类型的对象写到响应流中,同时指定相应的媒体类型为 contentType

当 Spring容器进行初始化过程中,在 <mvc:annotaion-driven />处创建注解驱动时,默认创建了七个 HttpMessageConverter对象。

HttpMessageConverter 接口实现类 作用
ByteArrayHttpMessageConverter 负责读取二进制格式的数据和写出二进制格式的数据
StringHttpMEssageConverter 负责读取字符串格式的数据和写出字符串格式的数据
ResourceHttpMessageConverter 负责读取资源文件和写出资源文件数据
SourceHttpMessageConverter 能够 读/写来自 HTTP的请求与响应的 javax.transform.Source,支持 DOMSource, SAXSource 和 StreamSource 的 XML格式
AllEncompassingFormHttpMessageConverter 负责处理表单数据
Jaxb2RootElementHttpMessageConverter 使用 JAXB负责读取和写入 XML标签格式的数据
MappingJackson2HttpMessageConverter 负责读取和写入 json格式的数据,利用 Jackson的 ObjectMapper读写 json数据

3)返回自定义类型对象

返回自定义类型对象时,不能以对象的形式直接返回给客户端浏览器,而是将对象转换为 JSON格式的数据发送给浏览器的。

// 定义返回对象
public class Student {
    private String name;
    private int age;
    // getter and setter, toString()
}

4)修改MyController

// 修改 MyController对象
@Controller
public class MyController {
    @RequestMapping(value="/myajax.do")
    @ResponseBody
    public Student doStudentJson() {
        // 创建java对象,转为json
        Studnet student = new Student("张三", 20);
        return student;
    }
}

5)修改 index页面

<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    $(function() {
        $("button").click(function() {
            $.ajax({
                url:"myajax.do",
                success:function(resp) {
                alert("resp:" + resp.name + "  " + resp.age);
            }
        })
    })
    })
</script>

<body>
    index.jsp<br>
    <button>
        发起Ajax请求
    </button>
</body>

6)返回 List集合

// 修改 MyController
public class MyController {
    @RequestMapping(value="myajax.do");
    @ResponseBody
    public List<Student> doStudentJsonArray() {
        // 创建List对象
        List<Student> students = new ArrayList<>();
        students.add(new Student("张三", 22));
        students.add(new Student("李四", 55));
        return students;
    }
}

7)修改 index页面

<script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
    $(function() {
        $("button").click(function() {
            $.ajax({
                url:"myajax.do",
                success:function(resp) {
                $.each(data, function(i, n) {
                    alert(n.name + "====" + n.age);
                })
            }
        })
    })
    })
</script>

<body>
    index.jsp<br>
    <button>
        发起Ajax请求
    </button>
</body>

8)返回字符串对象

若要返回非中文字符串,将前面返回数值类型数据的返回值直接修改为字符串即可。但若返回的字符串中带有中文字符,则会乱码

此时需要使用 @RequestMapping 的 produces属性指定字符集

// 修改 MyController
@Controller
public class MyController {
    @RequestMapping(value="/myajax.do", produces="text/plain;charset=utf-8")
    @ResponseBody
    public String doText() {
        return "HelloSpringMVC使用注解开发";
    }
}

9)修改页面

<script type="text/javascript">
	$(function() {
        $("button").click(function() {
            $.ajax({
                url:"myajax.do",
                success:function(data) {
                    alert(date);
                }
            })
        })
    })
</script>

解读<url-pattern>

配置详解

1)*.do

在没有特殊要求的情况下,SpringMVC的中央调度器 DispatcherServlet的 <url-pattern> 常使用后缀配置方式,如:*.do *.action *.mvc等

2)/

也可以配置为 /

因为 DispatcherServlet会将向静态资源的请求截取,例如 xxx.css xxx.js xxx.jpe等。会把对静态资源的访问视作对普通 Controller的访问。此时访问静态资源时会报 404异常

静态资源访问

<url-pattern>的值并不是说写为 / 以后,静态资源就完全无法访问了。解决方案如下:

A)使用 <mvc:default-servlet-handler>

声明了 <mvc:default-servlet-handler>之后,SpringMVC框架会在容器中创建 DefaultServletHttpRequestHandler处理器对象。

它就会向一个检察员,对进入 DispatcherServlet的 URL进行筛查,如果发现是静态资源的请求,就将该请求转由 Web应用服务器默认的 Servlet处理。

在 Tomcat中,有一个专门用于处理静态资源访问的 Servlet名叫 DefaultServlet,其 <servlet-name>为 default。可以处理各种静态资源访问请求。该 Servlet注册在 Tomcat服务器的 web.xml中

此时只需要在 springmvc.xml配置文件中 添加 <mvc:default-servlet-handler/>标签即可。

<mvc:default-servlet-handler />

B)使用 <mvc:resources />

在 Spring3.0版本之后,Spring定义了专门用于处理静态资源访问请求的处理器 ResourceHttpRequestHandler,并添加了 <mvc:resources />标签,专门用于解决静态资源无法访问的问题。需要在 SpringMVC配置文件中添加如下形式的配置:

<mvc:resources location="/images/" mapping="images/**" />
  • location:表示静态资源所在的目录,当然不能是 /WEB-INF/ 及其子目录
  • mapping:表示对该资源的请求,如以 /images/开始的请求,/images/beauty.jpg, /images/car.png 等等;**表示该路径及其子路径下

声明注解驱动

以上两种方法都会导致 SpringMVC中静态资源和动态资源产生冲突,因此需要在 SpringMVC文件中下如如下配置

<mvc:annotation-driven />

SSM整合开发

SSM编程,即 SpringMVC + Spring + MyBatis整合,是当前最为流行的 JavaEE开发技术架构。其实 SSM整合的实质就是将 MyBatis整合到 Spring中,因为 SpringMVC原本就是 Spring的一部分而已,不用专门整合。

SSM整合的实现方式有两种:基于 XML配置方式,基于注解方式

搭建环境

1)添加 pom依赖

<!--servlet-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<!-- jsp 依赖 -->
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2.1-b03</version>
    <scope>provided</scope>
</dependency>
<!--springmvc-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
<!--事务的-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
<!--aspectj 依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
<!--jackson-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<!--mybatis 和 spring 整合的-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.1</version>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.1</version>
</dependency>
<!--mysql 驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.9</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.12</version>
</dependency>
</dependencies>

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory><!--所在的目录-->
            <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>false</filtering>
        </resource>
    </resources>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

2)配置 web.xml

<!-- 注册 Spring的监听器 -->
<context-param>
	<param-name>contextConfigLocation</param-name>
    <param-valur>classpath:conf/applicationContext.xml</param-valur>
</context-param>
<!-- 
	注册ServletContext监听器的实现类 ContextLoaderListener,
	用于创建 Spring容器及将创建好的 Spring容器对象放入到 ServletContext的作用域中 
-->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置中央调度器 -->
<servlet>
	<servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    	<param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/dispatcherServlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	<servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 注册字符集过滤器 -->
<filter>
	<filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <filter-param>
    	<param-name>encoding</param-name>
        <param-value>utf8</param-value>
    </filter-param>
</filter>
<filter-mapping>
	<filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

3)建表 Student

4)定义包、组织程序的结构

5)编写配置文件

# 编写 JDBC配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssmbuild?useSSL=false&useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=zhao
<!-- applicationContext.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注册组件扫描器 -->
    <context:component-scan base-package="com.kuang.controller"/>
    <!-- 引入属性配置文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!-- 配置Druid数据库连接池 -->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxActive" value="20"/>
    </bean>
	<!-- 注册 SqlSessionFactoryBean-->	
    <bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>
	<!-- 动态代理对象 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" id="configurer">
        <property name="basePackage" value="com.kuang.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>
</beans>
<!-- springmvc.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/mvc
                           https://www.springframework.org/schema/mvc/spring-mvc.xsd
                           http://www.springframework.org/schema/context
                           https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!-- 注册注解驱动 -->
    <mvc:annotation-driven/>
    <!-- 注册组件扫描器-->
    <context:component-scan base-package="com.kuang.controller"/>
    <!-- 注册静态资源处理器-->
    <mvc:default-servlet-handler/>
    <!-- 注册视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="resolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>
<!-- mybatis.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>
    <!-- 配置日志输出 -->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!-- 配置别名 -->
    <typeAliases>
        <package name="com.kuang.domain"/>
    </typeAliases>
    <!-- 配置映射器文件 -->
    <mappers>
        <package name="com.kuang.mapper"/>
    </mappers>
</configuration>

SSM整合注解开发

1)定义实体类

// 定义实体类
public class Student {
    private Integer id;
    private String name;
    private int age;
    // setters, getters, toString()
}

2)创建 Dao接口 和 SQL映射文件

// 创建 Dao接口
public interface StudentDao {
    int insertStudent(Student student);
    List<Student> selectAllStudents();
}
<!-- 配置映射文件 -->
<?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.kuang.dao.StudentDao">

    <insert id="insertStuent" parameterType="student">
        insert into student(name, age)
        values (#{name}, #{age})
    </insert>

    <select id="selectAllStudents" resultType="student">
        select id, name, age
        from student
        order by id desc
    </select>
</mapper>

3)创建 Service接口和实现类

// 创建 Service接口
public interface StudentServiceint addStudent(Student student);
	List<Student> findStudents();	
}
// 创建 Service实现类
@Service(value="sutdentService")
public class StudentServiceImpl implements StudentService {
    @Autowired
    private StudentDao stuDao;
    @Override
    public int addStudent(Student student) {
        return stuDao.insertSerudent(student);
    }
    
    @Override
    public List<Student> findStudents() {
        return stuDao.selectAllStudents();
    }
}

4)定义Controller处理器

// 定义处理器
@Controller
@RequestMapping("/student")
public class StudentController {
    @Autowired
    private StudentService studentService;
    
    @RequestMapping("/addStudent.do")
    public ModelAndView addStudent(Student student) {
        ModelAndView mv = new ModelAndView();
        // 调用 service处理业务,将结果放入到 ModelAndView中
        int rows = studentService.addStudent(student);
        if(rows > 0) {
            mv.addObject("msg", "注册成功!!");
        } else {
            mv.addObject("msg", "注册失败!!");
        }
        mv.setViewName("result")
    }
    
    @RequestMapping("/queryStudent.do")
    @ResponseBody
    public List<Student> queryStudent() {
        List<Student> students = studentService.queryStudent();
        return students;
    }
}

5)定义视图文件

<!-- index.jsp -->
<!-- 指定路径 -->
<%
	String basePath = request.getScheme() + "://" + request.getServerName() + ":"  + request.getServerPort() + request.getContextPath() + "/";
%>
<!-- 指定 bese 标签 -->
<head>
    <base href="<%=basePath%>s" />
    <title>title</title>
</head>

<body>
    <div align="center">
        <p>
            SSM整合开发——实现 Student表的操作
        </p>
        <img src="images/ssm.jsp" />
        <table cellpadding="0" cellspacing="0">
            <tr>
            	<td><a href="addStudent.jsp">注册学生</a></td>
            </tr>
            <tr>
            	<td>&nbsp;</td>
            </tr>
            <tr>
                <td><a href="listStudent.jsp">查询学生</a></td>
            </tr>
        </table>
    </div>
</body>
<!-- 注册学生页面 -->
<body>
    <div align="center">
        <p>
            学生注册页面
        </p>
        <form action="student/addStudent.do" method="post">
            <table>
                <tr>
                	<td>姓名:</td>
                    <td><input type="text" name="name" /></td>
                </tr>
                <tr>
                	<td>姓名:</td>
                    <td><input type="text" name="name" /></td>
                </tr>
                <tr>
                	<td>年龄:</td>
                    <td><input type="text" name="age" /></td>
                </tr>
                <tr>
                	<td>&nbsp;</td>
                    <td><input type="submit" value="注册" /></td>
                </tr>
            </table>
        </form>
    </div>
</body>
<!-- 浏览学生页面 -->
<%@ page contentType="text/html;charset=utf8" language="java" %>
<%
String basePath = request.getScheme() + "://" + request.getServerName() + ":"  + request.getServerPort() + request.getContextPath() + "/";
%>
<html>
    <head>
        <base href="<%=basePath%>s" />
        <title>title</title>
        <script src="js/jquery-1.11.1-min.js"></script>
        <script type="text/javascript">
        	$(function() {
                stuinfo();
            })
            
            function stuinfo() {
                $.ajax({
                    url:"student/queryStudent.do",
                    type="post",
                    dataType="json",
                    success:function(resp) {
                    $("#stubody").html("");
                    $.each(resp, function(i, n) {
                        $("#stubody").append("<tr>")
                            .append("<td>" + n.id + "</td>")
                            .append("<td>" + n.name + "</td>")
                            .append("<td>" + n.age + "</td>")
                            .append("</tr>")
                    })
                }
                })
            }
        </script>
    </head>

    <body>
        <div align="center">
            <p>
                学生查询页面
            </p>
            <table>
                <thead>
                    <tr>
                        <td>id</td>
                        <td>姓名</td>
                        <td>年龄</td>
                    </tr>
                </thead>
                <tbody id="stubody">

                </tbody>
            </table>
        </div>
    </body>
</html>
<!-- 注册结果页面 -->
<body>
    ${msg}
</body>

SpringMVC核心技术

请求重定向和转发

当处理器对请求处理完毕后,向其他资源进行跳转时,有两种跳转方式:

  • 请求转发
  • 重定向

根据所要跳转的资源类型,又可以分为两类:

  • 跳转到页面
  • 跳转到其他处理器

注意:对于请求转发的页面,可以是WEB-INF中的页面;而重定向的页面,是不能为 WEB-INF中的。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中资源的。

SpringMVC 框架把原来 Servlet中的请求转发和重定向操作进行了封装

  • forword:表示转发,实现 request.getRequestDispatcher(“xxx.jsp”).forword(resquest, response);
  • rediret:表示重定向,实现 response.sendRedirect(“xxx.jsp”)

请求转发

处理器方法返回 ModelAndView时,需要在 serViewName()方法中指定的视图前添加 forword: 视图完整路径:,且此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。视图页面必须写出相对于项目根目录的路径。

处理器方法返回 String时,需要在视图路径前面加入 forword: 视图完整路径

显式的forword 操作不需要视图解析器

// 请求转发 示例
@RequestMapping(value="/some.do")
public ModelAndView doSome(Integer age, String name) {
    System.out.println("doSome:name = " +name + " , age = " + age);
    ModelAndView mv = new ModelAndView();
    mv.addObject("myname", name);
    mv.addObject("myage", age);
    mv.setViewName("forword:/WEB-INF/view/show.jsp");
    return mv;
}

请求重定向

在处理器方法返回的视图字符串的前面添加 redirect: 视图路径,则可实现重定向跳转

// 请求重定向 示例
@RequestMapping(value="/doredirect.do")
public ModelAndView doRedirect(String name, Integer age) {
    System.out.println("执行了 Redirect重定向:name = " + name + ", age = " = age);
    ModelAndView mv = new ModelAndView();
    // 再次强调,重定向不能访问 /WEB-INF 下的资源
    mv.setViewName("redirect:/other.jsp");
    return mv;
}

异常处理

SpringMVC框架处理异常的常用方式:使用 @ExceptionHandler注解处理异常。

@ExceptionHandler注解

使用注解 @Exceptionhandler可以将一个方法指定为异常处理方法。该注解只有一个可选属性 value,为一个 Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。

而被注解的方法,其返回值可以是 ModelAndView, String, void,方法名随意,方法的参数可以是 Exception及其子类对象, HttpServletRequest, HttpServletResponse等。系统会自动为这些方法的参数进行赋值。

对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中

1)自定义异常类

定义三个异常类:NameException, AgeException, MyUserException。其中 MyUserException是父类。

// 定义异常类 MyUserException
public class MyUserException extends Exception {
	public MyUserException() {
        super();
    }   
    
    public MyUserException(String msg) {
        super(msg);
    }
}
// 定义异常类 NameException
public class NameException extends MyUserException {
    public NameException() {
        super();
    }
    
    public NameException(String msg) {
        super(msg);
    }
}
// 定义异常类 AgeException
public class AgeException extends AgeException {
    public AgeException() {
        super();
    }
    
    public AgeException(String msg) {
        super(msg);
    }
}

2)修改 Controller抛出异常

// 修改 Controller
@Controller
public class MyController {
    @RequestMapping("/some.do")
    public ModelAndView doSome(Integer age, String name) throws MyUserException {
        ModeAndView mv = new ModelAndView();
        if(!"zs".equals(name)) {
            throw new NameException("姓名不正确!!");
        }
        mv.addObject("myname", name);
        mv.addObject(*"myage", age);
        mv.setViewName("show");
        return mv;
    }
    
    @ExceptionHandler(value=NameException.class)
    public ModelAndView doNameException(Exception ex) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("tips", "处理NameException");
        mv.addObject("ex", ex);
        mv.setViewName("nameError");
        return mv;
    }
}

3)定义异常响应页面

<body>
    nameErrors page<br>
    <hr>
    ${ex.message}<br>
</body>

不过一般一般会选择将异常处理方法统一定义在一个类中,作为全局异常处理类。

需要注解 @ControllerAdvice,实现控制增强。使用 @ControllerAdvice修饰的类中的可以使用 @ExceptionHandler

当使用 @RequestMapping注解修饰的方法抛出异常时,就会执行 @ControllerAdvice修饰的类中的异常处理方法

4)定义全局异常处理类

// 定义全局异常处理类
@ControllerAdvice
public class GlobalExceptionResolver {
    @ExceptionHandler(value=NameException.class)
    public ModelAndView doNameException(Exception ex) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("tips", "@ControllerAdvice注解处理 NameException");
        mv.addObject("ex", ex);
        mv.setViewName("nameError");
        return mv;
    }
    
    @ExceptionHandler(value=AgeException.class)
    public ModelAndView doAgeException(Exception ex) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("tips", "@ControllerAdvice注解处理 AgeException");
        mv.addObject("ex", ex);
        mv.setViewName("ageError");
        return mv;
    } 
    
    // 处理其他异常
    @ExceptionHandler
    public ModelAndView doAgeException(Exception ex) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("tips", "@ControllerAdvice注解处理 OtherException");
        mv.addObject("ex", ex);
        mv.setViewName("defaultError");
        return mv;
    }
}

5)配置Spring配置文件

<!-- 修改Spring配置文件 -->
<context:component-scan base-package="com.bjpowernode.controllers" />

<context:component-scan base-package="com.bjpowernode.exception" />

<mvc:annotaion-driven />

拦截器

SpringMVC中的 Interceptor拦截器是十分重要和相当有用的。它的主要作用是拦截指定的用户请求,并进行相应的预处理和后处理。

其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。

拦截器的执行

自定义拦截器

// 自定义一个拦截器
public class MyInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行MyInterceptor-----preHandle()");
        return true;
    }
    
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception {
        System.out.println("执行了MyInterceptor----postHandle()");
    }
    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了MyInterceptor----afterCompletion()");
    }
}

自定义拦截器,需要实现 HandlerInterceptor接口,接口中包含三个方法:

  • preHandle(request, response, Object handler):该方法在处理器方法之前执行,返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
  • postHandle(request, response, Objhect handler, ModelAndView mv):该方法在处理器方法执行之后执行,处理器方法若最终为被执行则该方法不会执行,由于该方法是在处理器执行后完成的,且该方法参数中包含 ModelAndView,所以该方法可以秀修改处理器方法的处理结果数据,且可以修改跳转方向
  • afterCompletion(request, response, Object handler, Exception ex):当 preHandle()方法返回 true后,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在中央调度器渲染了响应页面之后执行的。一般用作清除资源。

例如:

// 定义控制器方法
@RequestMapping("/some.do")
public ModelAndView doSome(String name, Integer age, HttpSession session) {
    System.out.println("执行MyController中的处理器方法");
    ModelAndView mv = new ModelAndView();
    mv.addObject("myname", name);
    mv.addObject("myage", age);
    mv.setViewName("show");
    
    session.setAttribute("attr", "session对象");
    return mv;
}
// 配置拦截器方法
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
	System.out.println("执行了拦截器 MyInterceptor的 afterCompletion()方法");
    HttpSession session = request.getSession();
    Object attr = session.getAttribute("attr");
    System.out.println("attr删除之前 -> " + session.getAttribute("attr"));
    session.removeAttribute("attr");
    System.out.println("attr删除之后 -> " + session.getAttribute("attr"));
}

拦截器中方法执行的顺序:

注册拦截器

<context:component-scan base-package="com.bjpowernode.*" />

<mvc:interceptors>
	<mvc:interceptor>
    	<mvc:mapping path="/**" />
        <bean class="com.bjpowernode.interceptors.MyInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>

<mvc:mapping />指定拦截器可以拦截的请求路径,/**表示拦截所有请求

多个拦截器的执行

1)再定义一个拦截器

// 再定义一个拦截器
public class MyInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("执行MyInterceptor222222-----preHandle()");
        return true;
    }
    
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception {
        System.out.println("执行了MyInterceptor222222----postHandle()");
    }
    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了MyInterceptor222222----afterCompletion()");
    }
}

2)多个拦截器的注册与执行

<context:component-scan base-package="com.bjpowernode.*" />

<mvc:interceptors>
	<mvc:interceptor>
    	<mvc:mapping path="/**" />
        <bean class="com.bjpowernode.interceptors.MyInterceptor" />
    </mvc:interceptor>
    
    <mvc:interceptor>
    	<mvc:mapping path="/**" />
        <bean class="com.bjpowernode.interceptors.MyInterceptor2" />
    </mvc:interceptor>
</mvc:interceptors>

3)控制台的输出

当有多个拦截器时,形成拦截器链。拦截器链的执行顺序和注册顺序一致。

需要强调的是:当某一个拦截器的 preHandle()方法返回 true并被执行到时,会向一个专门的方法栈中放入该拦截器的 afterCompletion()方法。

多个拦截器中方法与处理器方法的执行顺序如下图:

从图中可以看出,只要有一个 preHandle()方法返回 false,则上部的执行链将被断开,其后续的处理器方法与 postHandle()方法将无法执行。但无论执行链情况怎样,只要方法栈中有方法,即执行连中只要有 preHandle()方法返回 true,就会执行它对应的 afterCompletion()方法。

1613805518503

权限拦截器举例

需求:只有经过登录的用户方可访问处理器,否则,将返回“无法访问”的提示。

本例的登录,由一个 jsp页面完成。即在该页面里将用户信息放入 session中,也就是说,只要访问过该页面,就说明登录了。没访问过,则未登录用户。

1)修改 index.jsp页面

<body>
    index.jsp
</body>

2)定义 Controller

// 定义 Controller控制器
@Controller
public class MyController {
    @RequestMapping(value="/system.do")
    public ModelAndView doSome() {
        System.out.println("欢迎进入系统");
        return new ModelAndView("/WEB-INF/view/welcome.jsp");
    }
}

3)定义 welcome页面

<body>
    欢迎进入系统
</body>

4)定义权限拦截器

当 preHandle()返回 false时,需要使用 request和 response对请求进行响应。

public class PermissionInterceptor implements HandlerInterceptor {
    public boolean preHandler(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.prinln("执行 MyInterceptor-----preHandle()");
        String user = (String) request.getSession().getAttribute("user");
        if(!"beijing".equals(user)) {
            request.getRequestDispatcher("/fail.jsp").forword(request, response);
            return false;
        }
        return true;
    }

     public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mv) throws Exception {
        System.out.println("执行了MyInterceptor----postHandle()");
    }
    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("执行了MyInterceptor----afterCompletion()");
    }
}
                            

5)定义 fail界面

<body>
    未登录,访问无效!!!
</body>

6)注册拦截器

<!-- 注册权限拦截器 -->
<mvc:interceptors>
	<mvc:interceptor>
    	<mvc:mapping path="/**" />
        <bean class="com.bjpowernode.interceptors.PermissionInterceptor"  />
    </mvc:interceptor>
</mvc:interceptors>

7)定义 login页面

<body>
    <%
    session.setAttribute("user", "beijing");
    %>
    登录成功!!!
</body>

8)定义 logout页面

<body>
    <%
    session.removeAttribute("user");
    %>
    已退出!!!
</body>
posted @   小么VinVin  阅读(121)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示