SpringMvc
1、理解MVC设计模式
视图View——jsp和html,显示页面(el和jstl)
控制器controller——servlet
模型model——JavaBean
1.1、jsp model1
1.1、jsp model2(把servlet请求响应的控制分离出来)
JSP:负责生成动态网页
Servlet:负责流程控制
JavaBean:负责业务逻辑处理
1.3、MVC的处理过程
重点在于控制器
1.4、优缺点
1.4.1、优点
多视图共享一个模型,大大提高代码的可重用性
MVC三个模块相互独立,松耦合架构
控制器提高了应用程序的灵活性和可配置性
有利于软件工程化管理
完美的系统架构 = 松耦合+高重用性+高扩展性
1.4.2、 缺点
原理复杂
增加了系统结构和实现的复杂性
视图对模型数据的低效率访问
2、了解spring MVC的架构以及请求流程
2.1、spring MVC请求处理流程
1、用户发送请求给前端控制器DispatcherServlet,这个控制器决定该去找哪个具体控制器,根据URL决定调用哪个方法
2、页面控制器/处理器调用业务对象返回Model AND View给前端控制器,前端控制器根据Model AND View决定找哪个视图
3、然后把model的数据显示在视图中
前端控制器出来所有的请求
2.2、spring MVC的架构体系
结构最清晰的MVC Model2实现
Controller、ModelAndView
结构体系
DispatcherServlet(前端控制器)
- Spring MVC最核心的类
- web.xml中配置
Handler(处理器):对应MVC中C(Controller层)
- 类型:Object
- 作用:实际处理请求
- 标注了@RequestMapping的所有方法都可以看作是一个Handler
ModelAndView:逻辑视图名、模型对象
核心组件
HandlerMapping(处理器映射)
- BeanNameUrlHandlerMapping(默认)将请求URL映射到同名的控制器Bean上
- DefaultAnnotationHandlerMapping将请求映射到标注@RequestMapping注解的控制器和处理方法上
RequestMappingHandlerMapping
HandlerAdapter(适配器)
- AnnotationMethodHandlerAdapter
- RequestMappingHandlerAdapter
ViewResolver(视图解析器)
- InternalResourceView
框架特点
- 清晰地角色划分
- 灵活的配置功能
- 提供了大量的控制器接口和实现类
- 真正做到与View层的实现无关(JSP、Velocity、Xslt等)
- 国际化支持
- 面向接口编程
- Spring提供了Web应用开发的一整套流程,不仅仅是MVC,他们之间可以很方便的结合一起
3、springmvc框架开发环境搭建步骤
3.1、使用BeanNameUrlHandlerMapping方式
通过BeanNameUrlHandlerMapping的方式完成请求与Controller之间的映射关系
jar包
1、加入jar包
spring-web-3.2.13.RELEASE.jar
spring-webmvc-3.2.13.RELEASE.jar
配置文件
2、web.xml配置文件,接管了所有的web的请求
Tomcat的启动要配置文件
因为现在没有上下文对象了
static void start() {
userService=(UserService) (new ClassPathXmlApplicationContext("applicationContext.xml").getBean("userService"));
}
所以要配置一个监听器去启动,监听表示会监听启动、关闭、会话
DispatcherServlet前端控制器
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<!-- 配置SpringMVC前端控制器 -->
<servlet>
<!-- name可以随便起,建议springmvc -->
<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-servlet.xml</param-value>
</init-param>
<!-- Tomcat启动时启动,1是启动true,0不启动false -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 根目录所有的请求都提交给springmvc -->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3、springmvc-servlet.xml配置文件,放入资源文件夹中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="cn.bdqn.smbms.web.cotroller.IndexControllerDemo" name="/index"></bean>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 路径的前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 路径的后缀 -->
<property name="suffix" value=".jsp"/>
</bean>
控制器
4、创建indexController类继承AbstractController
package cn.bdqn.smbms.web.cotroller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;
public class IndexControllerDemo extends AbstractController {
//通过这个方法响应客户端请求,返回视图和模型,模型里面是数据
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv=new ModelAndView();
//前缀和后缀在配置文件配置好了
mv.setViewName("index");
return mv;
}
}
视图
5、新建一个index.jsp文件
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<p>xxxxxx</p>
</body>
</html>
部署运行
6、添加项目到Servers,启动服务器,在浏览器运行
3.2、注解驱动控制器
但是有多个请求时,要配置多个映射关系,并建立多个Controller来进行请求处理,步骤繁琐
此时可以用注解去生成bean
@Controller:标注一个普通的JavaBean成为可以处理请求的控制器
@RequestMapping:通过请求URL进行映射
3、springmvc-servlet.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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"
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/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注解的驱动 ,启动注解-->
<mvc:annotation-driven />
<!-- 扫描包中的注解 -->
<context:component-scan base-package="cn.bdqn.smbms.web.cotroller" />
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
4、创建一个indexController类,但是需要添加上注解
package cn.bdqn.smbms.web.cotroller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import cn.bdqn.smbms.pojo.User;
@Controller
public class indexController {
//value是URL,表示这两个值都可以在浏览器访问
@RequestMapping(value= {"/index","/"},method=RequestMethod.GET)
public String Index(Model model) {
User user=new User();
user.setUserName("mvc");
model.addAttribute(user);
model.addAttribute("message", "我的MVC");
return "index";
}
}
5、
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>${message }</h1>
<p>用户名:${user.userName}</p>
</body>
</html>
这种方式可以灵活的添加方法
在indexController类添加方法
@RequestMapping(value= {"/xxx","/"},method=RequestMethod.GET)
public String xxx(Model model) {
return "xxx";
}
新建xxx.jsp页面
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>我的xxxxxxx</h1>
</body>
</html>
4、掌握Controller和View之间的映射
5、掌握参数的传递(View-Controller)
5.1、从控制器传到视图
- ModelAndView
ModelAndView model=new ModelAndView();
model.addAttribute("message", "我的MVC");
页面用el表达式接受参数
<h1>${message }</h1>
- 在方法里传递参数Model
@RequestMapping( {"/index","/"})
public String Index(Model model) {
model.addAttribute("message", "我的MVC");
return "index";
}
5.2、从视图传参到控制器(加参数)
在方法里添加同名参数,数据名一样即可绑定数据
@Controller
public class indexController {
@RequestMapping({"/xxx"})
public String login(String username,String password) {
if("admin".equals(username) && "123".equals(password)) {
return "welcome";
}
//重定向到/的URL
return "redirect:/";
}
action提交到URL
新建index.jsp
<body>
<form action="xxx" method="post">
用户名:<input name="username">
密码:<input name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
新建welcome.jsp页面
<body>
<h1>欢迎光临</h1>
</body>
使用实体类对象传递数据
@RequestMapping( {"/xxx"})
public String login(User user) {
if("admin".equals(user.getUserName()) && "123".equals(user.getUserPassword())) {
return "welcome";
}
//重定向到/的URL
return "redirect:/";
}
这里的userName和userPassword要和实体类参数一样
<body>
<form action="xxx" method="post">
用户名:<input name="userName">
密码:<input type="password" name="userPassword">
<input type="submit" value="登陆">
</form>
</body>
在会话保存数据
在方法的参数里加入HttpSession
@RequestMapping( {"/xxx"})
public String login(User user,HttpSession session) {
if("admin".equals(user.getUserName()) && "123".equals(user.getUserPassword())) {
//把用户对象添加到session里
session.setAttribute("LoginUser", user);
return "welcome";
}
//重定向到/的URL
return "redirect:/";
}
在欢迎页面
<body>
<h1>欢迎${ sessionScope.LoginUser.userName }登陆</h1>
</body>
映射规则
方法名可以一样,参数不一样(重载)
但是映射不能一样,要保证bean全局唯一@RequestMapping(value= {"/xxx"})
如果想要区别,不同方法请求获得的方法也不一样
@RequestMapping(value= {"/xxx"},method=RequestMethod.GET)
@RequestMapping(value= {"/xxx"},method=RequestMethod.POST)
可以不写表单的action="xxx"
,默认请求post方式的URL
<form method="post">
用户名:<input name="userName">
密码:<input type="password" name="userPassword">
<input type="submit" value="登陆">
</form>
如果在方法上加上参数,但是没有传递过去,会报错,可以用?=-1传递过去
也可以加上注释,表示这个参数必须要public String Index(Model model,@RequestParam int id) {}
如果参数是可选的,可加public String Index(Model model,@RequestParam(name="id" ,required=false) Integer id) {}
,不传,id默认参数为null
Controller方法中参数前加@RequestParam进行直接入参
6、掌握并理解单例模式
BaseDao:操作数据库的基类
init()方法
每个线程对系统操作都需new一个BaseDao实例
I/O操作消耗系统资源,影响系统性能
单例模式:系统运行期间,有且仅有一个实例
一个类只有一个实例——最基本的
只提供私有构造器
它必须自行创建这个实例
定义了静态的该类私有对象
它必须自行向整个系统提供这个实例
提供一个静态的公有方法,返回创建或者获取本身的静态私有对象
6.1、懒汉模式-延迟加载
在类加载时不创建实例,采用延迟加载的方式,在运行调用时创建实例
package MySingleton;
public class MySingleton {
//1将构造私有化,不允许外部实例化对象
private MySingleton() {}
//2在类中定义一个本类型的私有静态属性
private static MySingleton instance;
//3定义公有的静态方法,返回该类的对象
public static MySingleton getInstance() {
//4判断是否已经实例化,这样可以只实例化一次
if(null==instance) {
instance=new MySingleton();
}
return instance;
}
}
用类名调用方法
MySingleton ms=MySingleton.getInstance();
线程安全问题
多个线程同一个时间调用方法,就会出现线程安全问题
特点:在类加载时不创建实例,采用延迟加载的方式,在运行调用时创建实例,延迟加载(lazy Loading)
解决:同步(synchronized)(不建议用)
6.2、饿汉模式-先实例化
package MySingleton;
public class MySingleton {
//将构造私有化,不允许外部实例化对象
private MySingleton() {}
//在类中定义一个本类型的私有静态属性,并且实例化
private static MySingleton instance=new MySingleton();
//定义公有的静态方法,返回该类的对象
public static MySingleton getInstance() {
return instance;
}
}
问题
在类加载的时候,就完成初始化
特点:线程安全,不具备延迟加载特性
6.3、结合懒汉和饿汉-静态内部类
避免了线程安全问题同时要具备延迟加载的特性
只有当加载静态内部类时才会实例化对象,实例化是在类加载的时候才会实例化
package MySingleton;
public class MySingleton {
//静态内部类
private static class InnerSingleton{
static MySingleton instance=new MySingleton();
}
//将构造私有化,不允许外部实例化对象
private MySingleton() {}
//定义公有的静态方法,返回该类的对象
public static MySingleton getInstance() {
return InnerSingleton.instance;
}
}
7、Spring MVC框架1
搭建Spring MVC+Spring+JDBC框架,并掌握在此框架上进行项目开发
指示符—“redirect:”重定向
指示符—“forward:”转发
7.1、掌握SpringMVC访问静态文件
静态资源放到单独的statics文件夹内
在apringmvc-servlet.xml配置文件配置静态资源
<!-- 配置静态资源,这样放过一些资源的访问,可以直接访问/statics/根目录下的资源 -->
<!-- 具体的文件夹的位置location="/statics/" ,mapping="/statics/**"浏览器访问地址 -->
<mvc:resources location="/statics/" mapping="/statics/**" />
访问statics文件夹里的images文件夹里的login_img.png图片
http://localhost:8080/smbms/statics/images/login_img.png
mapping是可以改的
<mvc:resources location="/statics/" mapping="/xxx/**" />
用这个地址访问
http://localhost:8080/smbms/xxx/images/login_img.png
Spring MVC-Controller的单例管理
Spring MVC—Controller:默认scope为singleton、速度和性能优越
Controller是单例的,其内部的成员变量都是公用的,开发时需要注意资源使用的问题
一般情况下,Controller内的成员变量就只有service对象
单例<bean id="zg" class="springdemo.Person" scope="singleton"/>
多例<bean id="zg" class="springdemo.Person" scope="prototype"/>
注解
在控制器类加上注解
@Scope(value="prototype")
7.2、掌握ServletAPI作为入参
Servlet API对象作为处理方法的入参
HttpSession
HttpServletRequest
页面上使用EL表达式显示相应信息
7.3、掌握Spring MVC异常(局部、全局)处理
局部异常HandlerExceptionResolver
仅能处理指定Controller中的异常
@ExceptionHandler
任何异常都会转动error页面
@ExceptionHandler
public String handlerException(RuntimeException exception,Model model) {
model.addAttribute("error", exception);
return "error";
}
error是键
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>出错了!${error.message }</h1>
<a href="${pageContext.request.contextPath }">返回</a>
</body>
</html>
全局异常异常处理
对所有异常进行统一处理
配置SimpleMappingExceptionResolver
发生异常时使用对应的视图报告异常
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings"/>
<props>
<prop key="java.lang.Exception">error</prop>
</props>
</bean>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>出错了!${exception .message }</h1>
<a href="${pageContext.request.contextPath }">返回</a>
</body>
</html>
默认${exception .message }
的exception是键
8、Spring MVC框架2
8.1、 格式化注解(@DateTimeFormat)
Spring MVC框架中时间类型的数据无法自动绑定,导致user对象的birthday属性(java.util.Date类型)绑定失败
结果:输入新增用户信息,点击保存之后系统报错
400状态码(请求错误)、控制台(BindException异常)
@DateTimeFormat(pattern = "yyyy-mm-dd")
private Date birthday;
转换日期显示格式
<p><strong>出生日期:</strong><span><fmt:formatDate value="${user.birthday }" pattern="yyyy-MM-dd"/></span></p>
8.2、REST风格(Representational State Transfer)
表述性状态转移,是一种软件架构风格
传统URL | REST风格的URL |
---|---|
/userview.html?id=12 | /user/view/12 |
请求方式:GET、POST、DELETE、PUT
/view/{id}
URL中的{xxx}占位符参数
@PathVariable String id
接收REST风格URL中的参数
@RequestMapping(value="/admin/user/view/{id}")
public String view(@PathVariable Integer id,Mode l model) {
model.addAttribute("user", userService.get(id));
return "/user/userview";
}
修改userlist.jsp页面href="${pageContext.request.contextPath }/admin/user/view/${user.id}"
<span><a class="viewUser" href="${pageContext.request.contextPath }/admin/user/view/${user.id}" ><img src="${pageContext.request.contextPath }/statics/images/read.png" alt="查看" title="查看"/></a></span>
@PathVariable(“id”) Integer id | @PathVariable Integer id |
---|---|
变量名不一样,需要写出来 | 不写参数,默认和变量名一样 |
@RequestMapping(value="/admin/user/view/{xxx}")
public String view(@PathVariable("xxx") Integer id,Model model) {
model.addAttribute("user", userService.get(id));
return "/user/userview";
}
8.3、掌握Spring表单标签
Spring提供的轻量级标签库,可在JSP页面中渲染HTML元素的标签
1、引入标签声明之后就可使用Spring表单标签
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="fm" %>
2、标签
fm:form标签
- modelAttribute指定绑定的模型属性,默认为command(建议指定)
- action指定表单提交的目标URL,可不指定,则自动提交到获取表单页面的URL
- method:GET、POST
表单组件标签也拥有HTML标签的各种属性,比如:id、onclick等等,都可以根据需要,灵活使用
fm:input/ | 属性 |
---|---|
path | 属性路径,表示表单对象属性,如userName、userCode等 |
cssClass | 表单组件对应的CSS样式类名 |
cssErrorClass | 当提交表单后报错(服务端错误),采用的CSS样式类 |
cssStyle | 表单组件对应的CSS样式 |
htmlEscape | 绑定的表单属性值是否要对HTML特殊字符进行转换,默认为true |
常用标签 | 作用 |
---|---|
fm:form/ | 渲染表单元素 |
fm:input/ | 输入框组件标签 |
fm:password/ | 密码框组件标签 |
fm:hidden/ | 隐藏框组件标签 |
fm:textarea/ | 多行输入框组件标签 |
fm:radiobutton/ | 单选框组件标签 |
fm:checkbox/ | 复选框组件标签 |
fm:select/ | 下拉列表组件标签 |
fm:error/ | 显示表单数据校验所对应的错误信息 |
8.4、掌握数据验证框架-JSR 303
- 服务器端的数据校验
- 利用Spring自带的验证框架
- 利用JSR 303实现
- Spring本身没有提供JSR 303的实现(利用Hibernate Validator实现)
- JSR 303
- Java为Bean数据合法性校验所提供的标准框架
- Spring MVC支持JSR 303标准的校验框架
- JSR 303通过在Bean属性上标注校验注解指定校验规则,并通过标准的验证接口对Bean进行验证
约束 | 说明 |
---|---|
@Null | 被注释的元素必须为null |
@NotNull | 被注释的元素必须不为null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
步骤
1、添加jar包
hibernate-validator-4.3.2.Final.jar
jboss-logging-3.1.0.CR2.jar
validation-api-1.0.0.GA.jar
2、修改User.java,给需要验证的属性增加相应的校验注解
实体类
private Integer id;
@NotEmpty(message = "用户编码不能为空")
private String userCode;
@NotEmpty(message = "用户名称不能为空")
private String userName;
@Length(message = "密码长度不得小于6且不得大于20",min=6,max=20)
private String userPassword;
private Integer gender;
@DateTimeFormat(pattern = "yyyy-mm-dd")
@Past(message = "日期不能是过去的日期")
private Date birthday;
@Pattern(regexp = "^1[0-9]{10}$" , message = "手机号码要是1开头的11位数字")
private String phone;
jsp页面
<div>
<label for="userCode">用户编码:</label>
<fm:input path="userCode" />
<!-- 放置提示信息 -->
<font color="red"><fm:errors path="userCode"/></font>
</div>
控制类,参数加上@Valid User user,BindingResult result
这两个要紧接着
@RequestMapping(value="/admin/user/add",method=RequestMethod.POST)
public String add(@Valid User user,BindingResult result,Model model) {
model.addAttribute("roleList", roleService.listAll());
//判断验证是否通过
if(result.hasErrors()) {
return "user/useradd";
}
if(userService.add(user)) {
return "redirect:/admin/user/list";
}
8.5、掌握文件上传
8.5.1、使用Spring MVC实现单文件上传
MultipartResolver接口
用于处理上传请求,将上传请求包装成可以直接获取文件的数据,方便操作
两个实现类
- StandardServletMultipartResolver使用了Servlet3.0标准的上传方式
- CommonsMultipartResolver使用了Apache的commons-fileupload来完成具体的上传操作
实现
1、导入jar文件
commons-io-2.4.jar
commons-fileupload-1.2.2.jar
2、配置MultipartResolver(springmvc-servlet.xml)
使用CommonsMultipartResolver配置一个MultipartResolver解析器
<bean id="multipartResolver" class=" org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="5000000" />
<property name="defaultEncoding" value="utf-8" />
</bean>
3、编写文件上传表单页useradd.jsp
在method="POST"指定表单内容类型,支持文件上传<fm:form method="post" modelAttribute="user" enctype="multipart/form-data">
用来上传文件的file组件
<div>
<label for="idPic">头像上传:</label>
<!-- 不要和user实体类里属性一样,会映射到User里面去(参数) -->
<input type="file" name="idPic" id="idPic"/>
</div>
4、MultipartFile对象
Spring MVC会将上传文件绑定到MultipartFile对象中,并通过该对象完成文件的上传操作
控制类添加参数MultipartFile idPic
@RequestMapping(value="/admin/user/add",method=RequestMethod.POST)
public String add(@Valid User user,BindingResult result,Model model,HttpSession session,MultipartFile idPic) {
//得到真实的路径
String filename=session.getServletContext().getRealPath("/statics/upload/1.jpg");
try {
idPic.transferTo(new File(filename));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
}
添加成功后,到Tomcat的服务器的项目里的/statics/upload/位置查看图片是否保存成功,里面的文件名为1.jpg
也可以写固定磁盘路径,但是可能无权操作c盘,或者没有d盘
//String filename=session.getServletContext().getRealPath("/statics/upload/1.jpg");
try {
idPic.transferTo(new File("c:\\xxx.jpg"));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
如果用本身文件名容易重名,可以使用随机文件名并上传到数据库
对上传文件进行重命名
当前系统时间+随机数+“_Personal.jpg”,文件上传成功后,更新数据库字段以及存储路径
//得到扩展名
String ext=FilenameUtils.getExtension(idPic.getOriginalFilename());
//后缀,.currentTimeMillis()当前毫秒数
String randName=""+System.currentTimeMillis()+(new Random().nextInt(999999))+"_idPic";
//完整的路径
String filename=session.getServletContext().getRealPath("/statics/upload/"+randName+"."+ext);
try {
idPic.transferTo(new File(filename));
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
把文件名写到数据库里
@RequestMapping(value="/admin/user/add",method=RequestMethod.POST)
public String add(@Valid User user,BindingResult result,Model model,HttpSession session,MultipartFile idPic) {
model.addAttribute("roleList", roleService.listAll());
//如果有错误,就返回控制类
if(result.hasErrors()) {
return "user/useradd";
}
//判断文件域是否为空
if(!idPic.isEmpty()) {
//得到扩展名
String ext=FilenameUtils.getExtension(idPic.getOriginalFilename());
//判断扩展名是否是jpg和png
if("jpg".equals(ext) && "png".equals(ext)) {
model.addAttribute("fileerror", "文件类型只能是jpg或者png");
return "user/useradd";
}
//1024*1024等于1兆
if(idPic.getSize()>1024*1024) {
model.addAttribute("fileerror", "文件大小不能超过2Mm");
return "user/useradd";
}
//后缀,.currentTimeMillis()当前毫秒数
String randName=""+System.currentTimeMillis()+(new Random().nextInt(999999))+"_idPic";
//完整的路径
String filename=session.getServletContext().getRealPath("/statics/upload/"+randName+"."+ext);
try {
idPic.transferTo(new File(filename));
//把文件名上传到数据库
user.setIdPicPath(randName + "." + ext);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
model.addAttribute("fileerror", e.getMessage());
return "user/useradd";
}
}
//有时候保存的创建者为空,因为会话失时效了就会无法添加进去,所以单独放一条
user.setCreatedBy(((User)session.getAttribute(CommonData.LOGIN_USER)).getId());
if(userService.add(user)) {
return "redirect:/admin/user/list";
}
/*添加不成功,把user里的数据写到model里面,然后jsp页面需要写el表达式接收数据(可用表单标签)
* 可以在参数里添加@ModelAttribute User user,则不用写这行代码,放入get方法里即可
* model.addAttribute("user", user);
*/
return "user/useradd";
}
9、
掌握JSON对象的处理
json对象的处理-使用Ajax异步判断实现同名验证
客户端响应的是一个ajax方法,直接把return的结果返回到客户端而不返回视图
步骤:
1、添加jar包
fastjson-1.2.13.jar
2、改造Controller层—UserController.java
@ResponseBody | RestController |
---|---|
/ | @RestController=@ResponseBody + @Controller |
则每一个方法要单独写, 将标注该注解的处理方法的返回结果直接写入HTTP Response Body中 | 则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容 |
@ResponseBody
@RequestMapping(value="/admin/user/codeExists/{userCode}")
public String CodeIsExit(@PathVariable String userCode) {
return "" + userService.userCodeExists(userCode);
}
userCodeIsExit()方法:同名验证
入参:userCode
验证结果:HashMap
返回值:Object(JSON对象),return JSONArray.toJSONString(HashMap);
3、改造View层
useradd.js
$(function(){
$("#userCode").on("keyup", function(){
$.ajax({
url: $("#path").val() + "/admin/user/codeExists/" + $("#userCode").val(),
success: function(result){
if("true" == result){
$("#userCode").next().text("该编号已被使用");
} else {
//不写else,就会一直符合if条件输出
$("#userCode").next().text("可使用");
}
}
})
})
})
4、把js资源添加到useradd.jsp
<script type="text/javascript" src="${pageContext.request.contextPath }/statics/js/useradd.js"></script>
其他js示例
$(function(){
$("#userCode").on("keyup", function(){
$.ajax({
url: $("#path").val() + "/admin/user/codeExists/" + $("#userCode").val(),
success: function(result){
if("true" == result){
$("#userCode").next().text("该用户编号已经被使用");
} else {
$("#userCode").next().text("");
}
}
})
})
$("form").on("submit", function(){
var pass = true;
if($("#userCode").val()==""){
$("#userCode").next().text("用户编号不能为空");
pass = false;
} else {
if($("#userCode").next().text() != "该用户编号已经被使用"){
$("#userCode").next().text("");
}
}
if($("#userName").val()==""){
$("#userName").next().text("用户名称不能为空");
pass = false;
} else {
$("#userName").next().text("");
}
if($("#userPassword").val().length<6 || $("#userPassword").val().length>20){
$("#userPassword").next().text("用户密码不能为空,且必须是6-20个字符");
pass = false;
} else {
$("#userPassword").next().text("");
}
if($("#ruserPassword").val() != $("#userPassword").val()){
$("#ruserPassword").next().text("两次输入密码不一致");
pass = false;
} else {
$("#ruserPassword").next().text("");
}
if($("#birthday").val()==""){
$("#birthday").next().text("必须选择出生日期");
pass = false;
} else {
$("#birthday").next().text("");
}
if($("#userCode").next().text()!=""){
pass = false;
}
return pass;
})
})
JSON数据的传递处理
通过Ajax异步调用来获取用户信息
控制器的处理方法返回bean对象转换成JSON对象输出
改造Controller层—UserController.java
返回值:Object(JSON对象)return JSON.toJSONString(user)
@ResponseBody
@RequestMapping(value="/admin/user/get/{id}")
public String get(@PathVariable Integer id) {
return JSON.toJSONString(userService.get(id));
}
访问http://localhost:8080/smbmsDemo/admin/user/get/1
页面返回了json格式的字符串
{"address":"?????????207?","birthday":434563200000,"createdBy":1,"creationDate":1363855927000,"gender":1,"id":1,"phone":"13688889999","userCode":"admin","userName":"?????","userPassword":"1234567","userRole":1,"userRoleName":"?????"}
改造view层
userlist.jsp
增加一个div区域用于用户明细信息的展示
<div class="providerView">
<p><strong>用户编号:</strong><span></span></p>
<p><strong>用户名称:</strong><span></span></p>
<p><strong>用户性别:</strong><span></span></p>
<p><strong>出生日期:</strong><span></span></p>
<p><strong>用户电话:</strong><span></span></p>
<p><strong>用户地址:</strong><span></span></p>
<p><strong>用户角色:</strong><span></span></p>
<br/>
</div>
绑定table标签<tr id="${user.id }">.....</tr>
userlist.js
给页面元素进行赋值操作
$(function(){
$("table.providerTable tr:gt(0)").on("mouseover", function(){
var id = $(this).attr("id");
$.ajax({
url: $("#path").val() + "/admin/user/get/" + id,
dataType: "json",
success: function(user){
$(".providerView span").eq(0).text(user.userCode);
$(".providerView span").eq(1).text(user.userName);
$(".providerView span").eq(2).text(user.gender==1?"男":"女");
$(".providerView span").eq(3).text(user.birthday);
$(".providerView span").eq(4).text(user.phone);
$(".providerView span").eq(5).text(user.address);
$(".providerView span").eq(6).text(user.userRoleName);
}
})
})
})
鼠标停留在当前用户时,页面显示信息
JSON数据传递的中文乱码
消息转换器(StringHttpMessageConverter)中固定了转换字符编码为“ISO-8859-1”
1、在方法的参数里添加produces = {"application/json;charset=utf-8"}
,指定返回的内容类型为json格式数据,并且字符串的转换编码为 “UTF-8”
@ResponseBody
@RequestMapping(value="/admin/user/get/{id}",produces = {"application/json;charset=utf-8"})
public String get(@PathVariable Integer id) {
return JSON.toJSONString(userService.get(id));
}
缺点:每个方法都要添加参数
2、装配消息转换器StringHttpMessageConverter
在springmvc的配置文件中添加,value的值可以设定不同的对象是什么编码格式(注意是写在注解的驱动中)
<!-- 注解的驱动 ,启动注解-->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=utf-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
bean对象中含有日期类型数据如何处理
那么在Spring MVC输出JSON数据时,日期格式是否需要处理?
在实体类加上注解@JSONField(format="yyyy-MM-dd")
@DateTimeFormat(pattern = "yyyy-mm-dd")
@Past(message = "日期不能是过去的日期")
@JSONField(format="yyyy-MM-dd")
private Date birthday;
@DateTimeFormat(pattern = “yyyy-mm-dd”) | 表单的字符串转换到data类型,映射到URL的类型客户端提交到服务器的格式转换 |
---|---|
@Past(message = “日期不能是过去的日期”) | 填写时的格式要求 |
@JSONField(format=“yyyy-MM-dd”) | 服务器的日期发到客户端的格式的转换 |
缺点:也要每个都要加注解
配置文件统一修改
配置FastJson的消息转换器-FastJsonHttpMessageConverter
设置features属性:指定输出时的日期转换器为WriteDateUseDateFormat
缺点:格式固定的
FastJson规定了默认的返回日期类型DEFFAULT_DATE_FORMAT:yyyy-MM-dd HH:mm:ss故对于特殊类型字段,可使用@JSONField来控制
<!-- 启用注解 -->
<mvc:annotation-driven>
<mvc:message-converters>
<!-- 定义FastJson的消息转换器 -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>application/json;charset=utf-8</value>
</list>
</property>
<!-- 格式化日期 -->
<property name="features">
<list>
<value>WriteDateUseDateFormat</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
控制类
要返回json格式,不然会无法接受数据
@ResponseBody
@RequestMapping(value="/admin/user/get/{id}")
public Object get(@PathVariable Integer id) {
return userService.get(id);
}
JSON、Ajax异步调用删除用户
在userlist.js添加
var id;
$(".deleteUser").on("click",function(){
id = $(this).attr("id");
$(".zhezhao").show();
$("#removeUser").show();
})
$("#no").on("click",function(){
$(".zhezhao").hide();
$("#removeUser").hide();
})
$("#yes").on("click",function(){
$.ajax({
url: $("#path").val() + "/admin/user/remove/" + id,
success:function(result){
if("true"==result){
// var x=$("#"+id).parents("tr");
// alert(x.find("td").eq(1).text())
$("#"+id).parents("tr").remove();
}
}
})
$(".zhezhao").hide();
$("#removeUser").hide();
})
userlist.jsp关键代码
<span><a class="deleteUser" id="${user.id }" href="javascript:void(0)"><img src="${pageContext.request.contextPath }/statics/images/schu.png" alt="删除" title="删除"/></a></span>
<!--点击删除按钮后弹出的页面-->
<div class="zhezhao"></div>
<div class="remove" id="removeUser">
<div class="removerChid">
<h2>提示</h2>
<div class="removeMain">
<p>你确定要删除该用户吗?</p>
<a href="javascript:void(0)" id="yes">确定</a>
<a href="javascript:void(0)" id="no">取消</a>
</div>
</div>
</div>
控制类修改删除代码
/**
* 删除用户
*/
@ResponseBody
@RequestMapping(value="/admin/user/remove/{id}")
public String remove(@PathVariable Integer id,Model model,HttpSession session) {
model.addAttribute("user", userService.get(id));
//如有已上传照片,则删除
String oldPicPath = userService.get(id).getIdPicPath();
if(null!=oldPicPath) {
File f= new File(session.getServletContext().getRealPath(CommonData.UPLOAD_PATH)+oldPicPath);
f.delete();
}
//然后再删除用户
//这是使用json页面删除用户的返回一个字符串
return "" + userService.remove(id);
// //重定向list的URL
// return "redirect:/admin/user/list";
}
理解数据转换和格式化
数据绑定流程
- DataBinder:数据转换器,数据绑定的核心部件,核心调度
- ConversionService:Spring类型转换体系的核心接口,解决前台form表单中时间字符串到后台Date数据类型的转换问题(转换服务-类型转换)把表单的数据类型转换为实体类型
- ValiDator进行验证,用实体类的注解去验证,是否为空是否超过当前日期
- BindingResult验证结果绑定到result里
配置了mvc:annotation-driven/标签,并没有配置ConversionService,也能通过格式化注解来解决日期的转换问题
mvc:annotation-driven/标签
DefaultAnnotationHandlerMapping
AnnotationMethodHandlerAdapter
FormattingConversionServiceFactoryBean(默认加载,无法转换日期)
日期格式的转换
不需要单独注解,就可以给全部关于日期的转换格式
使用配置转换格式
编写自定义转换器,自定义转换的规则,实现日期格式的转换
自定义转换器(StringToDateConverter.java)继承接口Converter,参数是<String, Date>
实现convert()方法:完成字符串到java.util.Date类型指定格式的转换
package cn.bdqn.smbms.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.core.convert.converter.Converter;
/**
* 字符串和日期的转换器
*/
public class StringToDateConvserter implements Converter<String, Date> {
//如果可以灵活传递参数,实例化时,可以通过构造传递参数
//日期格式
private String pattern;
public StringToDateConvserter(String pattern) {
this.pattern=pattern;
}
@Override
public Date convert(String dateStr) {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date=sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
装配自定义的ConversionService
在springmvc的配置文件添加
要注意添加conversion-service="myConversionService"
<mvc:annotation-driven conversion-service="myConversionService">
<mvc:message-converters>
略
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 日期转换 -->
<bean id="myConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="cn.bdqn.smbms.util.StringToDateConvserter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
</bean>
</list>
</property>
</bean>
使用控制器转换格式
通过自定义的编辑器实现日期格式的转换
使用@InitBinder装配自定义编辑器
注意:标注了@InitBinder注解的方法会在控制器初始化时调用
在userController类加上,主要不要导错包
/**
* 编辑自定义日期类型编译器
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
}
删掉配置文件的conversion-service="myConversionService"
如果不想每个类都写一个方法
可以新建一个类实现自定义编辑器
然后控制类继承此类
了解本地化
掌握Spring MVC+Spring+MyBatis的框架整合
applicationContext-mybatis.xml
数据源相关配置
配置sql心跳包
在校验连接的同时,解决数据库重新连接的问题,从而确保连接池中连接是真实有效的连接
initialSize
maxActive
maxIdle
minIdle
maxWait
removeAbandoned
removeAbandonedTimeout
事务管理
配置SqlSessionFactoryBean
配置MapperScannerConfigurer
springmvc-servlet.xml
配置mvc:annotation-driven/标签(包括消息转换器配置)
通过mvc:resources/标签配置静态文件访问
配置支持文件上传- multipartResolver
配置多视图解析器-ContentNegotiatingViewResolver
配置拦截器-interceptors
拦截器interceptors
基于HandlerMapping,对请求实施拦截,根据业务需求,基于不同的HandlerMapping定义多个拦截器
HandlerInterceptor接口,实现类Adapter,方法如下
- preHandle()之前
- postHandle()之后
- afterCompletion()全部完成之后
自定义拦截器的配置-SysInterceptor
新建AuthInterceptor.java
package cn.bdqn.smbms.web.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import cn.bdqn.smbms.util.CommonData;
public class AuthInterceptor extends HandlerInterceptorAdapter {
//true不拦截,false拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if(request.getSession().getAttribute(CommonData.LOGIN_USER)==null) {
response.sendRedirect(request.getContextPath());
return false;
}
return true;
}
}
springmvc配置文件
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/admin/**"/>
<bean class="cn.bdqn.smbms.web.interceptor.AuthInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
此时访问http://localhost:8080/smbms/admin/user/list
也会显示登陆页面,因为不登陆无法访问