所需的外包,配置文件等 .项目名‘sshdemo’。
注意要说明的一个情况:
Spring 和 SpringMVC 都有一个 IOC 容器 ,并且 Controller 类的 bean 在 springmvc的 IOC 容器中,但是它可以引用 spring 的 ioc 容器中的 bean 如 service 和 dao 层的bean ,反之则不行,因为 spring ioc 容器和springmvc ioc 容器是父子关系 ,相当于全局变量和局部变量的关系!!
然后,在把Hibernate整合进来,上一门课Spring的最后的例子已经讲过了整合的方法!
- 本示例中有一个bundle插件,用于解夺导入的bundle包的。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.5</version>
<type>bundle</type>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.5</version>
<type>bundle</type>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.17.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
- 各核心配置文件
jdbc.properties,
jdbc.user=root
jdbc.password=xiong
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/springmvc
jdbc.acquireIncrement=5
jdbc.initialPoolSize=10
jdbc.minPoolSize=1
jdbc.maxPoolSize=100
jdbc.maxStatements=20
jdbc.maxStatementsPerConnection=5
log4j.properties:
log4j.rootLogger = debug,stdout, D
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.Threshold = INFO
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ./log4j.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d %p %m%n
hibernate.cfg.xml:
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
hibernate.cfg.xml
<hibernate-configuration>
<session-factory>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
spring.xml
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置C3P0数据连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="acquireIncrement" value="${jdbc.acquireIncrement}"></property>
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
<property name="minPoolSize" value="${jdbc.minPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
<property name="maxStatements" value="${jdbc.maxStatements}"></property>
<property name="maxStatementsPerConnection" value="${jdbc.maxStatementsPerConnection}"></property>
</bean>
<!-- 整合Hibernate,配置Hibernate的SessionFactory实例 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<property name="mappingLocations" value="classpath:cn/ybzy/shweb/model/*.hbm.xml"></property>
</bean>
<!-- 事务配置 -->
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!-- 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="load*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务的切入点 -->
<aop:config>
<aop:pointcut expression="execution(* cn.ybzy.springdemo.service.*.*(..))"
id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
- 在web.xml中配置contextLoaderListener,并且加入spring的配置文件spring.xml这样可以把service、dao、事务、缓存、以及和其它框架的整合放到spring的配置文件里面,再WEB服务监听之。
在web.xml中配置SpringMVC的Servlet和加入springmvc.xml
springmvc的核心的调度器DispatcherServlet.
解决后面整合hibernate的懒加载异常配置。
<!-- spring.xml配置数据库,服务,hibernate等整合在一起 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class><!--ctrl+/弹出一个对话框,选中ContextLoaderListener确定即可出现该段代码-->
</listener>
<!-- 使用spring mvc ,springmvc的核心的调度器,servlet中配置来 -->
<servlet>
<servlet-name>springDispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--指定springmvc的配置文件的所在的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 解决懒加载异常的过滤器 -->
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在web.xml中配置SpringMVC的Servlet调度器和加入spring的spring.xml整合文件, 这时,两个配置文件中扫描的包有重合的时候出现某些bean会被初始化2次的问题:
解决办法:
springmvc.xml:
在扫描包的子节点下配置 exclude-filter 和 include-filterSpringMVC只扫描 Controller 和 ControllerAdvice :
<!-- 下个配置很重要,不然的话注解复杂了不管用 -->
<mvc:annotation-driven/>
<context:component-scan base-package="cn.ybzy.sshdemo" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
spring.xml:
排除扫描 Controller 和 ControllerAdvice : :
<context:component-scan base-package="cn.ybzy.sshdemo">
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation"
expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
</context:component-scan>
测试:
控制层代码:
@Controller
public class UserController {
//测试spring,springmvc两个IOC容器没有配过滤扫描包的情况下,初始化后会调用两次该类创建对象
public UserController() {
System.out.println("UserController构造方法");
}
}
把sshdemo加入tomcat服务器中,运行 http://localhost/sshdemo 回车
在控制台中看看输出 ‘UserController构造方法’几次,没问题的情况下,只一次,因为上述代码中已设置了spring,springmvc分别要扫的包。spring是父容器,springmvc ioc是子容器-可以调用父容器的象属性。
springmvc 一般只负表控制层的逻辑。
RESTful风格获取用户信息(包括关联表部门信息)
/user/1 HTTP GET : 得到 id = 1 的 一条数据
/users HTTP GET : 得到所有数据
/user/1 HTTP DELETE: 删除 id = 1的 一条数据
/user/1 HTTP PUT: 更新id = 1的 一条数据
/user HTTP POST: 新增一条数据
- 现在的浏览器只支持GET和POST请求,不支持PUT、DELETE请求,所以要在web.xml上配置拦截器把POST请求转PUT、DELETE请求:
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
要让页面中显示springmvc表单格式,要在页面上加上:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<body>
<!-- 修改用户信息,springmvc的表单写法,应用restful风格很方便 -->
<form:form modelAttribute="user">
<form:hidden path="id"/> <br/><br/> <!--path是name和id的综合 -->
<form:input path="password"/> <br/><br/>
<form:select path="dpt.id" itemValue="id" itemLabel="dptName" items="${dpts}"></form:select> <!--多项选择--> <br/><br/>
<input type="submit" value="提交"/>
</form:form>
</body>
- 创建模型类 User,Department 代码见项目'sshdemo'.
- 创建dao接口,泛型dao接口实现类(提取公共代码)
为什么要接口:当我们修改类代码时,可以立规距,起到规范作用,提醒我们应守则。
BaseDao<T> BaseDaoInterface<T>,前者是类,后者是接口,有了这类,接口,我们写多个模型接口和实现类,会省很多代码(一个模型是看不出效果的),下面我以User用户模型来看它们是怎么用的。
-------------------------------------BaseDaoInterface<T>------------------------------------------------------------
public interface BaseDaoInterface<T> {
/**
* 新增记录到数据库中的对应的数据表格中去
* @param user
*/
public void add(T t );
/**
* 通过id值来删除对应的用记记录
* @param id
*/
public void delete(int id);
/**
* 传入一对象,来修改
* @param user
*/
public void update(T t);
/**
* 获取一条记录
* @param id
* @return
*/
public T getOne(int id);
/**
* 获取所有记录
* @return
*/
//public List<T> getAll();
}
-------------------------------------BaseDao<T>------------------------------------------------------------
import java.lang.reflect.ParameterizedType;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class BaseDao<T> implements BaseDaoInterface<T> {
@SuppressWarnings("unchecked")
public Class<T> getTClass(){
Class<T> tClass = (Class<T>)((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
return tClass;
}
@Autowired
private SessionFactory sessionFactory;
public Session getSession() {
return sessionFactory.getCurrentSession();
}
public void add(T t) {
getSession().save(t);
}
public void delete(int id) {
getSession().delete(getOne(id));
}
public void update(T t ) {
getSession().update(t);
}
public T getOne(int id) {
return getSession().load(getTClass(),id);
}
//@SuppressWarnings("unchecked") //这个我们不实现泛型,因获取所有记录的应用不多,我们可以在服务层作特色方法再去实现。
/*public List<T> getAll() {
String hql= "from T";
Session session = getSession();
Query<T> query = session.createQuery(hql);
return query.list();
}*/
}
-----------------------------------------------UserDao---------------------------------------
//继承了父接口BaseDaoInterface,可调用父接口的方法,这里可以不重写方法,当然了,加特色逻辑代码除外
public interface UserDao extends BaseDaoInterface<User> {
public List<User> getAll(); //自定义自已的特色方法
}
-----------------------------------------------UserDaoImpl---------------------------------------
import org.hibernate.Session;
import org.hibernate.query.Query;
import org.springframework.stereotype.Repository;
import cn.ybzy.sshdemo.model.User;
@Repository("userDao")//注解方式配bean放进IOC容器中,用时直接装配,也可以在spring的xml中配置文件中配置,但注解更方便
public class UserDaoImpl extends BaseDao<User> implements UserDao {
@SuppressWarnings("unchecked")
@Override
public List<User> getAll() { //实现//自定义自已的特色方法
String hql="from User";
Session session = getSession();
Query<User> query = session.createQuery(hql);
return query.list();
}
}
- 创建服务层。服务层也建泛型服务接口和实现类,实现提取共公代码。
BaseService<T> BaseServiceImpl<T>
-----------------------------------------------------------BaseService<T> ------------------------------------------------
public interface BaseService<T> {
/**
* 新增记录到数据库中的对应的数据表格中去
* @param user
*/
public void add(T t );
/**
* 通过id值来删除对应的用记记录
* @param id
*/
public void delete(int id);
/**
* 传入一对象,来修改
* @param user
*/
public void update(T t);
/**
* 获取一条记录
* @param id
* @return
*/
public T getOne(int id);
/**
* 获取所有记录
* @return
*/
/*public List<T> getAll();*/
}
-------------------------------------------------------BaseServiceImpl--------------------------------------
public class BaseServiceImpl<T> implements BaseService<T>{
@Autowired //自动装备baseDao对象到IOC--控制反转容器中。
public BaseDao<T> baseDao;
@Override
public void add(T t) {
baseDao.add(t);
}
@Override
public void delete(int id) {
baseDao.delete(id);
}
@Override
public void update(T t) {
baseDao.update(t);
}
@Override
public T getOne(int id) {
return baseDao.getOne(id);
}
//@Override
/*public List<T> getAll() {
// TODO Auto-generated method stub
return null;
}*/
}
---------------------------------------------------------UserService接口----------------------------------------
import java.util.List;
import cn.ybzy.sshdemo.model.User;
//继承了父接口BaseService,可调用父接口的方法,这里可以不重写方法,当然了,加特色逻辑代码除外
public interface UserService extends BaseService<User>{
public List<User> getAllUsers();//作为自已的特色方法
}
-------------------------------------------------------------UserServiceImpl-----------------------------------
@Service("userService")
public class UserServiceImpl extends BaseServiceImpl<User> implements UserService{
@Autowired
private UserDao userDao; //下面有一个特色方法,所以用到userDao,而泛型方法中没有定义
public List<User> getAllUsers() {
return userDao.getAll();
}
}
注意:
其它模型类,也是这样建dao,service层的。,本实例中还有个Department类--是User类的(多对一关系映射)一端。
- hibernate :User多对Department(建表t_departments,t_users)
---------------------------------------------------------User.hbm.xml---------------------------------------
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.ybzy.sshdemo.model.User" table="t_users">
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<property name="username" type="java.lang.String">
<column name="username" />
</property>
<property name="password" type="java.lang.String">
<column name="password" />
</property>
<many-to-one name="dpt" class="cn.ybzy.sshdemo.model.Department" fetch="join">
<column name="dpt_id" />
</many-to-one>
</class>
</hibernate-mapping>
-------------------------------------------------------------Department.hbm.xml----------------------------
<class name="cn.ybzy.sshdemo.model.Department" table="t_dpartments">
<id name="id" type="int">
<column name="id" />
<generator class="native" />
</id>
<property name="dptName" type="java.lang.String">
<column name="departname" />
</property>
</class>
- 显示层 /WEB-INF/jsp/user.jsp, updateUser.jsp
--------------------------------------------user.jsp----------------------------------------------------------------------
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<body>
<h4>显示所有用户的信息</h4>
<table>
<tr>
<td>用户编号</td>
<td>用户名</td>
<td>用户密码</td>
<td>用户部门</td>
</tr>
<c:forEach var="user" items="${users}">
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<td>${user.dpt.dptName}</td>
<td><a href="${pageContext.request.contextPath}/user/${user.id}">修改</a> <a href="#">删除</a></td>
</tr>
</c:forEach>
</table>
</body>
-------------------------------------------------updateUser.jsp---------------------------------------------------------
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<body>
<!-- 修改用户信息,springmvc的表单写法,应用restful风格很方便,不用写隐藏字段 -->
<form:form modelAttribute="user">
<form:hidden path="id"/> <br/><br/> <!--path是name和id的综合 -->
<form:input path="password"/> <br/><br/>
<form:select path="dpt.id" itemValue="id" itemLabel="dptName" items="${dpts}"></form:select> <br/><br/>
<input type="submit" value="提交"/>
</form:form>
</body>
- 控制类
下面两个方法http://localhost:8081/sshdemo/users显示所有用户信息 http://localhost:8081/sshdemo/user/1用于修改指定用户(id)信息
@Controller
public class UserController {
//测试spring,springmvc两个IOC容器没有配过滤扫描包的情况下,初始化后会调用两次该类创建对象
/*public UserController() {
System.out.println("UserController构造方法");
}*/
@Autowired
private UserService userService;
@Autowired
private DepartmentService departmentService;
/**
* RESTful风格:
* url: xxxxx/users get 获取所有users
* @return
*/
@RequestMapping(value="/users",method=RequestMethod.GET)
public String getAllUsers(Model model) {
List<User> users = userService.getAllUsers();
model.addAttribute("users",users); //会出懒加载异常,因为这里session会随时会关闭,不好控制,在web.xml中配过滤器
return "user";
}
}
RESTful风格/user/n 获取某一条记录
/user HTTP put方式修改一条记录。。
获取一条记录方法一般配合修改,添加记录使用。
updateUser.jsp 修改页面。
@RequestMapping(value="/user/{id}",method=RequestMethod.GET)
public String updateUser(@PathVariable("id") Integer id,Model model) { //获取表单提交的参数id
if(id!=null) {
User user = userService.getOne(id);
model.addAttribute("user",user);
}
List<Department> dpts=departmentService.getAllDepartments();
model.addAttribute("dpts",dpts);
return "updateUser";
}
@ModelAttribute //遇到该注解的方法,只要任何请求进入该类就最先执行之,这里我们初始化一个要修改的对象,再注入入到quest,session域空间中
public void getUser(@RequestParam(value="id",required=false) Integer id,
@RequestParam(value="dpt.id",required=false) Integer dptid,Model model ) {
if(id!=null&&dptid!=null) {
User user= userService.getOne(id);
Department department =departmentService.getOne(dptid);
user.setDpt(department);
model.addAttribute("user",user);
}
}
@RequestMapping(value="/user",method=RequestMethod.PUT)
public String updateUser(User user){
System.out.println("user===="+user);
//不能这样userService.update()直接修改,修改好后,没有传的字段为null,这肯定不行的
//1. 传统处理方法
/*User user2 = userService.getOne(user.getId());
Department dpt2 = departmentService.getOne(user.getDpt().getId());
user2.setPassword(user.getPassword());
user2.setDpt(dpt2);
userService.update(user2); */
//springmvc方法修改一条记录
userService.update(user);
System.out.println(user);
return "redirect:/users"; //跳转到一映射方法上,再由那方法转送到user.jsp中。
}
-------------------------------------------------updateUserView.jsp------------------------
<!--put方式 ,修改一条记录 -->
<form:form action="${pageContext.request.contextPath}/user" method="put" modelAttribute="user"> <!-- 没有modelAttribute="user"的话,那 控制层mode.addAttribute("command",user);写成这样-->
id: <form:hidden path="id"/> <br/><br/> <!--path是表单name和id的综合 -->
password: <form:input path="password"/> <br/><br/>
department: <form:select path="dpt.id" itemValue="id" itemLabel="dptName" items="${dpts}"></form:select> <br/><br/>
<input type="submit" value="提交"/>
</form:form>
</body>
RESTful风格/user HTTP post新增一条记录
/**
* 添加用户,
*/
@RequestMapping(value="/user",method=RequestMethod.GET)
public String addUser(Model model) {
model.addAttribute("command",new User());
model.addAttribute("dpts",departmentService.getAllDepartments());
return "addUserView";
}
//符合rest风格: /user post 新增一条user记录
@RequestMapping(value="/user",method=RequestMethod.POST)
public String addUser(User user) {
userService.add(user);
return "redirect:/users";
}
--------------------------------------------------------addUserView.jsp--------------------------------------
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h4>添加用户</h4>
<form:form action="${pageContext.request.contextPath}/user" method="post" > <!-- 没有modelAttribute="user"的话,那 控制层mode.addAttribute("command",user);写成这样-->
username: <form:input path="username"/> <br/><br/> <!--path是表单name和id的综合 -->
password: <form:input path="password"/> <br/><br/>
department: <form:select path="dpt.id" itemValue="id" itemLabel="dptName" items="${dpts}"></form:select> <br/><br/>
<input type="submit" value="添加用户"/>
</form:form>
</body>
RESTful风格/user/1 HTTP DELETE: 删除 id = 1的 一条数据
/user/1 HTTP DELETE: 删除 id = 1的 一条数据
1、编写Controller,实现删除动作的方法。
2、编写jsp页面文件
页面上有几个小麻烦:
(1)浏览器中<a>标签默认的是GET请求,没的DELETE的请求,解决方法:用js来解决,需
要导入jquery的js文件,这里迁出麻烦(2)!
---------------------------------------------------------users.jsp------------------------------------
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jq/jquery-1.11.3.min.js"></script>
<script type="text/javascript">/* 注:springmvc不自动识别静态资源如:js,css,图片,要在springmvc.xml配<mvc:default-servlet-handler/> */
$(function(){
$(".deleteUser").click(function(){
var href=this.href; //$(this).attr("href");
alert(href);
$("#delform").attr("action",href).submit();
return false;//让<a>标签原料来的功能失效
});
});
</script>
</head>
<body>
<!--为删除链接设置HTTP POST,因为超链接只有get方式,前面讲过,这里用表单迁移-->
<form id="delform" action="" method="post">
<input type="hidden" name="_method" value="delete">
</form>
<h4>显示所有用户的信息</h4>
<table>
<tr>
<td>用户编号</td>
<td>用户名</td>
<td>用户密码</td>
<td>用户部门</td>
</tr>
<c:forEach var="user" items="${users}">
<tr>
<td>${user.id}</td>
<td>${user.username}</td>
<td>${user.password}</td>
<td>${user.dpt.dptName}</td>
<td>
<a href="${pageContext.request.contextPath}/user/${user.id}">修改</a>
<a class="deleteUser" href="${pageContext.request.contextPath}/user/${user.id}">删除</a></td>
</tr>
</c:forEach>
</table>
</body>
jquery.min.js的导入:
(2)导入js、css、图片...静态资源文件,不让springmvc的解析器解析他们,解决办法
加两个配置标签!
测试删除:
http://localhost:8081/sshdemo/users 点‘删除’按钮。
InitBinder注解来绑定表单数据
在使用 SpingMVC 框架的项目中,经常会遇到页面日期时间类型的数据要绑定到控制器的实体,或者控制器需要接受这些数据,如果这类数据类型不做处理的话将无法绑定。这里我们可以使用注解 @InitBinder 来解决这些问题,这样 SpingMVC 在绑定表单之前,都会先注册这些编辑器。一般会将这些方法些在 BaseController 中,需要进行这类转换的控制器只需继承BaseController 即可。 Spring 自己提供了大量的编辑器实现类,诸如 CustomDateEditor, CustomBooleanEditor , CustomNumberEditor 等许多,基本上够用。
例:我们http://localhost:8081/sshdemo/user/1进入个修改页面时,修改日期(没有绑定它之前)会出现如下图片中的错误 。
注:基本数据类型修改不出问题
要使日期修改有用,处理方法:
11.编写 BaseController,让所有其它控制类都可继承它用之。
在 BaseController 中使用 SpringMVC 的注解 @initbinder 和 Spring 自带的 WebDateBinder 类来操作。
WebDataBinder 是用来绑定请求参数到指定的属性编辑器 . 由于前台传到 controller 里的值是 String 类型的,当往 Model 里 Set 这个值的时候,如果 set 的这个属性是个对象, Spring 就会去找到对应的 editor 进
行转换,然后再 Set 进去。
package cn.ybzy.sshdemo.controller;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
public class BaseController {
//InitBinder注解来绑定表单数据
//在使用 SpingMVC 框架的项目中,经常会遇到页面日期时间类型的数据要绑定到控制器的实体,
//或者控制器需要接受这些数据,如果这类数据类型不做处理的话将无法绑定
@InitBinder //这里一定要引一个java.util.Date类
public void initBinder(WebDataBinder binder) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false); //表示日期格式遵守严格的格式,如:2021-10-33不是严格的格式,将其转为2021-11-2
binder.registerCustomEditor(Date.class,new CustomDateEditor(sdf, true));
}
}
12.具体操作日期控制类继承它。
public class UserController extends BaseController {
@RequestMapping(value="/user/{id}",method=RequestMethod.GET)
public String updateUser(@PathVariable("id") Integer id,Model model) { //获取表单提交的参数id
if(id!=null) {
User user = userService.getOne(id);
model.addAttribute("user",user);
}
List<Department> dpts=departmentService.getAllDepartments();
model.addAttribute("dpts",dpts);
return "updateUserView";
}
@ModelAttribute //遇到该注解的方法,只要任何请求进入该类就最先执行之,这里我们初始化一个要修改的对象,再注入入到quest,session域空间中
public void getUser(@RequestParam(value="id",required=false) Integer id,
@RequestParam(value="dpt.id",required=false) Integer dptid,Model model ) {
if(id!=null&&dptid!=null) {
User user= userService.getOne(id);
Department department =departmentService.getOne(dptid);
user.setDpt(department);
model.addAttribute("user",user);
}
}
}
@RequestMapping(value="/user",method=RequestMethod.PUT)
public String updateUser(User user){
userService.update(user);
System.out.println(user);
return "redirect:/users"; //跳转到一映射方法上,再由那方法转送到user.jsp(显示所有记录信息页)中。
}
13.需要在 SpringMVC 的配置文件加上
<mvc:annotation-driven/>
-
SpringMVC支持用注解代替@initBinder注解及方法
image.png
image.png -
自定义springmvc 编辑器形式。
image.png
image.png
使用JSR-303 Validation进行验证
JSR-303 Validation简介
JSR-303简介
------JSR-303只是一个规范,而Spring也没有对这一规范进行实现,那么当我们在SpringMVC中使用JSR-303的时候就需要我们提供一个对JSR-303规范的实现,Hibernate Validator就是实现了这一规范的具体框架。(这个和Hibernate ORM框架没关系啊!)JSR-303的校验是基于注解的,它内部已经定义好了一系列的限制注解,我们只需要把这些注解标记在需要验证的实体类的属性上或是其对应的get方法上。
JSR 303 基本的校验规则
- 空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim后的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY. - Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false - 长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included. - 日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前,验证成立的话被注释的元素一定是一个过去的日期
@Future 验证 Date 和 Calendar 对象是否在当前时间之后 ,验证成立的话被注释的元素一定是一个将来的日期
@Pattern 验证 String 对象是否符合正则表达式的规则,被注释的元素符合制定的正则表达式,regexp:正则表达式 flags: 指定 Pattern.Flag 的数组,表示正则表达式的相关选项。 - 数值检查
建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为”“,Integer为null
@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最
大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最 - 小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) 被指定的元素必须在合适的范围内
@Range(min=10000,max=50000,message=”range.bean.wage”)
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)
JSR-303 Validation具体操作。
1 系统默认的校验信息
1)导入需要的jar包:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.10.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-annotation-processor</artifactId>
<version>6.0.10.Final</version>
</dependency>
2)SpringMVC的配种文件中需要mvc:annotation-driven这个标签,自动加载需要的支持类。
3)在JavaBean的属性上加验证注解。
public class User {
private int id;
@NotEmpty //验证,不能这“”
private String username;
@NotEmpty
private String password;
@NotNull
private Department dpt;
@Past //限定时间是过去,不能为以后
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthday;
@DecimalMin(value="1.0")
@DecimalMax(value="2.4")
private double height;
@Email //验证是否符合邮箱格式
private String email;
4)在Controller的处理方法上的参数前,加@Valid注解,增加出错后,错误信息放置的对象参数Errors/BindingResult errorrs,我们这里是用在添加用户上。
@RequestMapping(value="/user",method=RequestMethod.POST)
public String addUser(@Valid User user,BindingResult err/*Errors err 一定注意,这两个参数紧掺着,中间不可有其它参数*/,Model model) {
if(err.getErrorCount()>0) {
System.out.println("某些数据不符合数据有效性校验!");
for(FieldError fe:err.getFieldErrors()) {
System.out.println("===="+fe.getField()+":"+fe.getDefaultMessage());
}
model.addAttribute("user",new User()); //这里不能用addAttribute("command",new User());那么添加用户页上form上加modelAttribute="user"如:<form:form action="${pageContext.request.contextPath}/user" method="post" modelAttribute="user" >
model.addAttribute("dpts",departmentService.getAllDepartments());
return "addUserView";
}
userService.add(user);
return "redirect:/users";
}
5) jsp页面上应用(addUserView.jsp)
<h4>添加用户</h4>
<form:form action="${pageContext.request.contextPath}/user" method="post" modelAttribute="user" > <!-- 没有modelAttribute="user"的话,那 控制层mode.addAttribute("command",user);写成这样-->
username: <form:input path="username"/><form:errors path="username"/> <br/><br/> <!--path是表单name和id的综合 -->
password: <form:input path="password"/> <form:errors path="password"/><br/><br/>
birthday: <form:input path="birthday"/><form:errors path="birthday"/><br><br>
height: <form:input path="height"/><form:errors path="height"/><br><br>
email: <form:input path="email"/><form:errors path="email"/><br><br>
department: <form:select path="dpt.id" itemValue="id" itemLabel="dptName" items="${dpts}"></form:select> <br/><br/>
<input type="submit" value="添加用户"/>
</form:form>
</body>
6)测试
localhost:8081/sshdemo/user
试着各种情部况的添加用户信息,再到控制台看输出。
我这里一个数据都不加,控制台输出
某些数据不符合数据有效性校验!
====height:必须大于或等于1.0
====password:不能为空
====username:不能为空
2 自定信校验信息
有些系统默认的错误信息不好用,我们一般都自己写错误提示信息
①用注解,在注解中带message参数
②用国际化配置文件:i18n.properties,注意这个你的自定义的错误信息会覆盖注解上自定义的错误信息.
SpringMVC配置文件中配置国际化资源文件:
SpringMVC返回json数据的两种常用方式
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
JSON是一个序列化的对象或数组。JSON是一个标记符的序列。这套标记符包含六个构造字符、字符串、数字和三个字面名。
json值可以是对象、数组、数字、字符串或者三个字面值(false、null、true)中的一个。值中的字面值中的英文必须使用小写。
{"name": "John Doe", "age": 18, "address": {"country" : "china", "zip-code": "10000"}}
{"a": 1, "b": [1, 2, 3]}
1
[1, 2, "3", {"a": 4}]
1
3.14
1
"plain_text"
JSON 与 JS 对象的关系
很多人搞不清楚 JSON 和 JS 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:
JSON 是 JS 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
如:
var obj = {a: 'Hello', b: 'World'}; //这是一个对象,注意键名也是可以使用引号包裹
var json = '{"a": "Hello", "b": "World"}'; //这是一个 JSON 字符串,本质是一个字符串
JSON 和 JS 对象互转
要实现从JSON字符串转换为JS对象,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //结果是 {a: 'Hello', b: 'World'}
要实现从JS对象转换为JSON字符串,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'}); //结果是 '{"a": "Hello", "b": "World"}'
方式一:
使用JSON工具将对象序列化成json,常用工具Jackson,fastjson,gson。然后利用HttpServletResponse,然后获取response.getOutputStream()或response.getWriter(),直接输出。
- 导入Jackson工具jar包
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
- 写jsonTestjsp页面
<title>Insert title here</title>
<script type="text/javascript" src="${pageContext.request.contextPath}/resources/js/jq/jquery-1.11.3.min.js"></script>
<script type="text/javascript">
$(function(){
$("#jsonid").click(function(){
var href= this.href;
var args = {};
$.post(href,args,function(data){
for(var i=0;i<data.length;i++){
alert(data[i].username);
}
});
return false;
});
});
</script>
</head>
<body>
<a id="jsonid" href="${pageContext.request.contextPath}/testjson1/2">json的数据格式,来获取所有用户信息[object,object,object,]</a>
</body>
- 编写控制类
**
* 响应json格式数据到页面中,方法1,很土的方法
* @param resp
* @param out
* @throws JsonProcessingException
*/
@RequestMapping(value="/testjson",method=RequestMethod.POST)
public void testjson(HttpServletResponse resp,PrintWriter out) throws JsonProcessingException {//out输出流,拿到输出流的方法很多很多
resp.setContentType("application/json;charset=utf-8");
//下面两行代码:关闭浏览器缓存,让页面无刷新更新数据,如:实时股票价格
resp.setHeader("pragma", "no-cache");
resp.setHeader("cache-control", "no-cache");
List<User> users = userService.getAllUsers();
ObjectMapper mapper = new ObjectMapper();
String josnStr=mapper.writeValueAsString(users);
System.out.println("====JSON格式的所有用户信息: "+josnStr);
out.print(josnStr); //向浏览器响应json格式数据
}
/**
* 响应json格式数据到页面中,方法2
* @return
*/
@ResponseBody
@RequestMapping(value="/testjson2",method=RequestMethod.POST)
public List<User> testjson2(){
List<User> users = userService.getAllUsers();
return users;
}
@RequestMapping("/jsontestview") //用于测试时的映射的页面jsonTestView.jsp
public String jsonTestView() {
return "jsonTestView";
}
以上两种方法效果是一样的,显然后一种 方法是方便的。
- 测试:
这里还要进一个注意知识点:
我们用的是HibernateORM框架访问的数据库,那么默认情况下,我们都启用的是懒加载,以提高访问数据看的效率,但是,记住如果是返回Json格式的数据,而又在POJO类里有属性是关联对象的时候:比如这里的,User类里有Department部门类型属性dpt,那么我们这里用的Jackson工具,在将User对象转json格式字符串的时候会出错!
解决办法两个:
1、关掉懒加载:
image.png
2、在实体类上用注解把懒加载的依赖对象忽略掉
image.png
测试::::
http://localhost:8080/sshdemo/jsontestview 回车 点击链接的超链接。
image.png
image.png
springMVC中框架对HttpMessageConverter的选择原理
-------在SpringMVC的 Controller层经常会用到@RequestBody和@ResponseBody,通过这两个注解,可以在Controller中直接使用Java对象作为请求参数和返回内容(这里直接将返回内容直接写入到Response对象的body数据区,从而绕过来前面我们讲的视图解析器,不通过视图解析器直接将数据响应给浏览器),而在写入之前必须要转换响应的数据格式,完成这之间转换作用的便是HttpMessageConverter。
HttpMessageConverter接口定义了四个方法,分别是读取数据时的 canRead(), read()和 写入数据时的 canWrite(), write() 方法。在使用 <mvc:annotation-driven />标签配置时,默认配置了RequestMappingHandlerAdapter,并为他配置了一下默认的HttpMessageConverter,默认HttpMessageConverter会加载7个现实的转换器,导入Jackson包后,自动加载第8个现实类:
当使用 @RequestBody 和 @ResponseBody 注解时, RequestMappingHandlerAdapter就使用它们来进行读取或者写入相应格式的数据。
SpringMVC怎么知道应该调用哪个转换器来工作的?
@RequestBody注解时: 根据Request对象header部分的Content-Type类型,逐一匹配合适的HttpMessageConverter来读取数据;
请求进来的时候的Access属性:
@ResponseBody 注解时: 根据 Request 对象 header 部分的 Accept 属性,逐一按accept 中的类型,去遍历找到能处理的 HttpMessageConverter 实现类;
常用实现类功能说明如下:
① StringHttpMessageConverter:支持的MediaType:text/plain,/,将请求信息转换为字符串;
② ByteArrayHttpMessageConverter:支持的MediaType:application/octet-stream,/读写二进制数据;
③ SourceHttpMessageConverter:支持的
MediaType:application/xml,text/xml,application/+xml,读写javax.xml.transform.Source类型的数据;
④ FormHttpMessageConverter :支持的MediaType:application/x-www-form-urlencoded,multipart/form-data将表单数据读取到MultiValueMap中;
⑤ ResourceHttpMessageConverter:支持的MediaType:/,读写org.springframework.core.io.Resource对象;
⑥ BufferedImageHttpMessageConverter:读写BufferedImage对象;
⑦ MappingJackson2HttpMessageConverter:支持的
MediaType:application/json,application/+json,利用jackson开源包的objectMapper读写JSON数据。
⑧ Jaxb2RootElementHttpMessageConverte:支持的MediaType:application/xml,text/xml,application/*+xml
我们在来玩一个例子,实现进来是文件形式,但使用字符串来接收:
例:
- user. jsp
加一个请求表单,该表单选择一个aaa.txt文本文件,再上传,提交后,会把代码中的字符串直接响应给浏览器,不用通过视图解析器处理,以前我们都是通过(return "xxxx")xxx视图来处理的。
注意:上传文件必定是post方式,且enctype为二进制流
<form action="${pageContext.request.contextPath}/testconvert" method="post" enctype="multipart/form-data">
文件说明:<input type="text" name="mark">
上传文件:<input type="file" name="file">
<input type="submit" value="上传">
</form>
- UserController.java
@ResponseBody
@RequestMapping(value="testconvert",method=RequestMethod.POST)
public String testConvert(@RequestBody String text) {
System.out.println(text);
return "xiongshaowen"+new Date();//跟视图没有关系,我要把这些字符串直接响应给浏览器,不经过视图
}
- 测试:
http://localhost:8080/sshdemo/users 回车,随便选一个文本文件,再点上传,可以看到
http://localhost:8081/sshdemo/testconvert(地址栏是这样的,没有视图)显示:
image.png
springMVC实现文件的上传与下载
------文件上传是项目开发中最常见的功能。为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。
一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的。Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件。
操作步骤:
1、导入jar包:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
2、编辑上传文件的user.jsp页面(注意:post,form-data)
<h4 style="color:red">上传文件</h4>
<form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
文件说明:<input type="text" name="mark">
上传文件:<input type="file" name="file">
<input type="submit" value="上传">
</form><br><br>
- 控制类
/**
* springMVC 上传文件 http://localhost:8080/sshdemo/users 有一个表单,点击上传
* @param mark 文件描述
* @param file
* @param request
* @return
* @throws IllegalStateException
* @throws IOException
*/
@RequestMapping(value="/upload",method=RequestMethod.POST)
public String UploadContext(@RequestParam("mark") String mark,@RequestParam("file") MultipartFile file,HttpServletRequest request) throws IllegalStateException, IOException{
System.out.println("====mark:"+mark);
if(!file.isEmpty()) {
String path= request.getServletContext().getRealPath("/resources/uploadfiles");
System.out.println("上传的路径为: "+path);
String fileName = file.getOriginalFilename(); //得到原文件名
File file2 = new File(path,fileName); //建一个文件,此时它是空的
if(!file2.getParentFile().exists()) {
file2.getParentFile().mkdirs();
} //确保uploadfiles目录一定存在
file.transferTo(file2);
}
return "redirect:/users"; //上传文件表单写在users方法返回的页面中(user.jsp)
}
/**
* 下载:示例 http://localhost:8080/sshdemo/download?filename=aaaa.txt
* @param filename
* @param request
* @return
* @throws IOException
*/
@RequestMapping("/download")
public ResponseEntity<byte[]> download(@RequestParam("filename") String filename,HttpServletRequest request) throws IOException{
//request拿到保存文件的绝对路径
String path = request.getServletContext().getRealPath("/resources/uploadfiles/");
File file = new File(path+File.separator+filename);
HttpHeaders headers =new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment", new String(filename.getBytes("UTF-8"),"iso-8859-1"));
return new ResponseEntity<byte[]>(FileUtils.readFileToByteArray(file),headers,HttpStatus.CREATED);
}
- springmvc.xml
<!-- 配置文件上传的解析器,上传文件大小上限,单位为字节(10MB) -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize">
<value>10485760</value>
</property>
<!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,默认为ISO-8859-1 -->
<property name="defaultEncoding">
<value>UTF-8</value>
</property>
</bean>
- 测试文件的上传与下载:
上传:http://localhost/sshdemo/users 有上传文件的表单,选择文件上传即可。
下载:http://localhost/sshdemo/download?filename=xxxx.xxx 注意:该下载是已经上传了的文件。
SpringMVC的拦截器
SpringMVC的处理器拦截器 ,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。
( 1 )过滤器:
依赖于 servlet 容器。在实现上基于函数回调,可以对几乎所有请求进行过滤,但是缺点是一个过滤器实例只能在容器初始化时调用一次。使用过滤器的目的是用来做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest 的一些参数,包括:过滤低俗文字、危险字符等。
( 2 )拦截器:
依赖于 web 框架,在实现上基于 Java 的反射机制,属于面向切面编程( AOP )的一种运用。由于拦截器是基于 web 框架的调用,因此可以使用 Spring 的依赖注入( DI )进行一些业务操作,同时一个拦截器实例在一个 controller 生命周期之内可以多次调用。
拦截器常见应用场景
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(PageView)等。
2、权限检查:如登录检测,进入处理器检测是否登录,如果没有直接返回到登录页面;
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个Controller中的处理方法都需要的,我们就可以使用拦截器实现。
5、OpenSessionInView:如Hibernate,在进入处理器打开Session,在完成后关闭Session。
…………本质也是AOP(面向切面编程),也就是说符合横切关注点的所有功能都可以放入
拦截器实现。
拦截器的应用实操。
- SpringMVC提供的拦截器接口与拦截器适配器
图片.png
拦截器一个有3个回调方法,而一般的过滤器Filter才两个:
preHandle:预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
postHandle:后处理回调方法,实现处理器的后处理(但在渲染视图之前),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
afterCompletion:整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中 preHandle返回true的拦截器才会执行afterCompletion。
拦截器适配器:
有时候我们可能只需要实现三个回调方法中的某一个,如果实现HandlerInterceptor接口的话,三个方法必须实现,不管你需不需要,此时spring提供了一个HandlerInterceptorAdapter适配器(一种适配器设计模式的实现),允许我们只实现需要的回调方法。
图片.png
运行流程图
图片.png
图片.png - 实操: 两个拦截器:HandlerInterceptor1和HandlerInterceptor2:
图片.png
SpringMVC配置文件里配置:
图片.png
测试正常和中断的执行情况!
1.正常情况:输入如下图所示的地址:其实任何映射方法进入即都可以
preHandle()中,return true;
图片.png
控制台显示:
====HandlerInterceptor1,preHandle
====HandlerInterceptor2,preHandle
=====HandlerInterceptor2,postHandle
=====HandlerInterceptor1,postHandle
=====HandlerInterceptor2,afterCompletion
=====HandlerInterceptor1,afterCompletion
2.中断情况下: return false;
控制台显示:
====HandlerInterceptor1,preHandle
- 精准拦截。
我们只在springmvc.xml只配拦截器1,只拦截"/users"映射方法(显示所有用户信息)
<!--配置精准拦截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/users"/>
<bean class="cn.ybzy.sshdemo.web.HandlerInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
http://localhost/sshdemo/users 回车后,
控制台显示:
=====HandlerInterceptor1,postHandle
=====HandlerInterceptor1,afterCompletion
- 通配符与多层结构拦截。
1.一层结构,只一个 .
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/>
<bean class="cn.ybzy.sshdemo.web.HandlerInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
拦截所有的控制类方法,只是一层。测试如下:
http://localhost:8080/sshdemo/users
2.两层结构或多层结构
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="cn.ybzy.sshdemo.web.HandlerInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
测试如下:
http://localhost/sshdemo/uuu/users
3.排除拦截,如下图,不拦截 '/users'
SpringMVC异常处理
Spring MVC中异常处理的类体系结构:
在Spring MVC中,所有用于处理在请求映射和请求处理过程中抛出的异常的类,都要实现 HandlerExceptionResolver接口。一个基于Spring MVC的Web应用程序中,可以存在多个实现了HandlerExceptionResolver的异常处理类,他们的执行顺序,由其order属性决定, order值越小,越是优先执行, 在执行到第一个返回不是null的ModelAndView的Resolver时,不再执行后续的尚未执行的Resolver的异常处理方法。平常开发的时候,我一般都会配置:
<mvc:annotation-driven />,使用了这个配置标签,SpringMVC会默认配置:
- 注解@ExceptionHandler实现HandlerExceptionResolver接口的方式。
1.具体个类中有效:
UserController.java控制类中写入口方法和异常处理方法
/**
* 异常处理,测试入口方法
* @param i
* @return
*/
@RequestMapping("/testexception")
public String testException(@RequestParam("i") int i) {
int a = 10/i; //测试异常,得下请求发一个i=0来测试 http://localhost/sshdemo/testexception?i=0
System.out.println("异常测试中:10/i===="+a);
return "redirect:/users";
}
/**
* 定义了数学异常处理方法,如上:testexception?i=0,会跳到error.jsp中,这样可以把异常信息注入到视图页中了
* 页面接收 ${ex}
* @param ex
* @return
*/
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handlerEx(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.setViewName("error"); //设置含有的视图页面error.jsp
mv.addObject("ex",ex.getMessage());
return mv;
}
error.jsp
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h3>这里是error页面</h3>
异常信息:${ex}
</body>
</html>
测试:
http://localhost/sshdemo/testexception?i=0 回车,跳转到error.jsp显示如下:
这里是error页面
异常信息:java.lang.ArithmeticException: / by zero
2.对所有控制类有效方法
定义一个异常控制类,该类加注解@ControllerAdvice
@ControllerAdvice
public class ExController {
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handlerEx(Exception ex) {
ModelAndView mv = new ModelAndView();
mv.setViewName("error");
mv.addObject("ex",ex.getMessage());
return mv;
}
}
- SimpleMappingExceptionResolver处理异常方式。
这种方式用得很多,不用自己写java类处理异常,直接配置就可以了:
图片.png
发现页面中的${exception}没有失效, SimpleMappingExceptionResolver默认就会把异常信息注入到jsp页面,默认的异常信息的引用名: exception ,这个默认名字也是可以修改的:
图片.png
测试,入口方法与1 一样。