SpringMVC
1. Springmvc入门案例
1.1 SpringMVC入门程序开发总结
- 一次性工作
- 创建工程,设置服务器,加载工程
- 导入坐标
- 创建web容器启动类,加载SpringMVC配置,并设置SpringMVC请求拦截路径
- SpringMVC核心配置类(设置配置类,扫描controller包,加载Controller控制器bean)
- 多次工作
- 定义处理请求的控制器类
- 定义处理请求的控制器方法,并配置映射路径(@RequestMapping)与返回json数据(@ResponseBody)
1.2 入门案例工作流程分析
- 启动服务器初始化过程
- 服务器启动,执行ServletContainersInitConfig类,初始化web容器
- 执行createServletApplicationContext方法,创建了WebApplicationContext对象
- 加载SpringMvcConfig
- 执行@ComponentScan加载对应的bean
- 加载UserController,每个@RequestMapping的名称对应一个具体的方法
- 执行getServletMappings方法,定义所有的请求都通过SpringMVC
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected WebApplicationContext createRootApplicationContext() {
return null;
}
}
@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig{}
@Controller
public class UserController{
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save");
return "{'info':'springmvc'}"
}
}
- 单次请求过程
- 发送请求localhost/save
- web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
- 解析请求路径/save
- 由/save匹配执行对应的方法save()
- 执行save()
- 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方
1.3 Controller加载控制与业务bean加载控制
-
SpringMVC相关bean(表现层bean)
-
Spring控制的bean
- 业务bean(Service)
- 功能bean(DataSource等)
-
SpringMVC相关bean加载控制
- SpringMVC加载的bean对应的包均在com.itheima.controller包内
-
Spring相关bean加载控制
- 方式一:Spring加载的bean设定扫描范围为com.itheim,排除掉controller包内的bean
- 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包,dao包等
- 方式三:不区分Spring和SpringMVC环境,加载到同一个环境中
-
名称:@ComponentScan
-
类型:类注解
-
范例:
@Configuration
@ComponentScan(value="com.itheima",excludeFilters=@ComponentScan.Filter(type=FilterType.ANNOTATION,classes=Controller.class))
public class SpringConfig{}
- 属性
- excludeFilters:排除扫描路径中的bean,需要指定类别(type)与具体项(classes)
- includeFilters:加载指定的bean,需要指定类别type与具体项(classes)
- excludeFilters:排除扫描路径中的bean,需要指定类别(type)与具体项(classes)
- bean加载格式
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
protected WebApplicationContext createRootApplicationContext() {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
- 简化开发
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}
}
1.4 PostMan简介
- PostMan是一款功能强大的网页调试与发送网页Http请求的Chrome插件
- 作用:常用于进行接口测试
2 请求与响应
2.1 设置请求映射路径
设置当前控制器方法请求访问路径,如果设置在类上统一设置当前控制器方法请求访问路径前缀
@Controller
@RequestMapping("/user")//请求映射路径。路径前缀
public class UserController{
@RequestMapping("/save")
@ResponseBody
public String save(){
System.out.println("user save");
return "{'info':'user save'}"
}
@RequestMapping("/delete")
@ResponseBody
public String delete(Integer id){
System.out.println("user delete");
return "{'info':'user save'}"
}
}
2.2请求方式
- Get请求:http://localhost/commonParam?name=itcast&age=15
相同名称可以映射,或者用@RequestParam
@RequestMapping("/commonParam")
@ResponseBody
public string commonParam(String name int age){
System.out.println("普通参数传递 name ==>"+name);
System.out.println("普通参数传递 age ==>"+age);
return "{'module':'common param'}"
}
//输出为:普通参数传递 name ==>itcast
// 普通参数传递 name ==>15
不同名称的映射
@RequestMapping("/commonParam")
@ResponseBody
public string commonParam(@RequestParam("name") String name1 int age){
System.out.println("普通参数传递 name ==>"+name1);
System.out.println("普通参数传递 age ==>"+age);
return "{'module':'common param'}"
}
//输出为:普通参数传递 name ==>itcast
// 普通参数传递 name ==>15
- Post请求:
请求与Get类似,在Body里的x-www-form-urlencoded里面添加参数
为web容器添加过滤器并指定字符集,Spring-web包中提供了专用的字符过滤器
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{SpringConfig.class};
}
//中文乱码处理
@0verride
protected Filter[]getservletFilters(){
CharacterEncodingFilter filter = new characterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
2.3 请求参数
- 普通参数:url地址传参,地址参数名与形参形参变量名相同,定义形参即可接收参数
@RequestMapping("/commonParam")
@ResponseBody
public string commonParam(String name int age){
System.out.println("普通参数传递 name ==>"+name);
System.out.println("普通参数传递 age ==>"+age);
return "{'module':'common param'}"
}
- POJO参数:请求参数名和形参对象属性名相同,定义POJO类型形参即可接收参数
@RequestMapping("/pojoParam")
@ResponseBody
public string pojoParam(User user){
System.out.println("pojo参数传递 user ==>"+user);
return "{'module':'pojo param'}"
}
- 嵌套POJO参数:POJO对象中包含对象
public class User{
private String name;
private int age;
private Address address;
}
public class Addresss{
private String province;
private String city
}
@RequestMapping("/listpojoParam")
@ResponseBody
public string listpojoParam(List<User> list){
System.out.println("listpojo参数传递 list ==>"+list);
return "{'module':'listpojo param'}"
}
- 数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型形参即可接收参数
@RequestMapping("/arrayParam")
@ResponseBody
public string arrayParam(String[] likes){
System.out.println("数组参数传递 likes ==>"+Arrays.toString(likes));
return "{'module':'array param'}"
}
- 集合保存普通参数:请求参数名与形参集合对象名相同且请求参数为多个,@RequestParam绑定参数关系
@RequestMapping("/listParam")
@ResponseBody
public string listParam(@RequestParm List<String> likes){
System.out.println("集合参数传递 likes ==>"+likes;
return "{'module':'list param'}"
}
-
Json数据
- 添加坐标
- 设置发送的json的数据(请求body中添加json)
- 开启SpringMVC多项辅助功能
@EnableWebMVC:开启json转化成对象的功能
@Configuration @ComponentScan("com.itheima.controller") @EnableWebMVC public class SpringMvcConfig{}
- 设置接受json数据
@RequestBody:将请求中请求体所包含的数据传递给请求参数,此注解一个处理器方法只能使用一次
@RequestMapping("/listParamForJson") @ResponseBody public String listParamForJson(@RequestBody List<String> likes){ System.out.println("list common(json)参数传递 list ==> "+likes); return "{'module':'list common for json param'}" } public String pojoParamForJson(@RequestBody User user){ System.out.println("pojo(json)参数传递 user ==> "+user); return "{'module':'pojo for json param'}" } public String listpojoParamForJson(@RequestBody List<User> list){ System.out.println("list pojo(json)参数传递 list ==> "+list); return "{'module':'list pojo for json param'}" }
-
日期类型数据:2088-08-18 2088/08/18 08/18/2088
-
@DateTimeFormat:设定日期时间型数据格式
-
pattern:日期时间格式字符串
-
Converter接口:类型转换器1.String->Integer 2.String->Date
-
@EnableWebMVC功能之一:根据类型匹配对应的类型转换器
-
根据不同的日期格式设置不同的接收方式
@RequestMapping("/dateParam")
@ResponseBody
public String dataParam(Date date,@DateTimeFormat(parttern="yyyy-MM-dd") Date date1, @DateTimeFormat(parttern="yyyy-MM-dd HH:mm:ss") date2){
System.out.println("参数传递 date ==> "+date);//2088/08/18
System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1)//2088-08-18
System.out.println("参数传递 date2(yyyy-MM-dd HH:mm:ss) ==> "+date2)//2088-08-18 08:08:08
return "{'module':'date param'}"
}
@RequestBody与@RequestParam区别
1.区别
- @RequestParam用于接收ur地址传参,表单传参【application/x-www-form-urencoded】
- @RequestBody用于接受json数据【application/json】
2.应用
- 后期开发,发送json格式数据为主,@RequestBody应用较广
- 如果发送非json格式数据,选用@RequestParam接收请求参数
2.4响应
@ResponseBody:设置当前控制器返回值作为响应体
- 响应页面
@RequestMapping("/toPage")
public String toPage(){
return "page.jsp";
}
- 响应文本数据
@RequestMapping("/toText")
@ResponseBody
public String toText(){
return "response text";
}
- 响应json数据(对象转json)
@RequestMapping("/toJsonPojo")
@ResponseBody
public User toJsonPojo(){
User user = new User();
user.setName("赵云");
user.setAge(41);
return user;
}
- 响应json数据(对象集合转json数组)
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
User user1 = new User();
user.setName("赵云");
user.setAge(41);
User user2 = new User();
user.setName("马超");
user.setAge(40);
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
return userList;
}
3 Rest风格(资源访问的形式)
3.1简介
- REST,表现形式状态转换
- 传统风格
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser’
- REST
- http://localhost/user/1
- http://localhost/user/
- 传统风格
- 优点:
- 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
- 书写简化
- 按照REST风格访问资源时使用行为动作区分对资源进行何种操作
- http://localhost/users 查询全部用户信息 GET(查询)
- http://localhost/users/1 查询指定用户信息 GET(查询)
- http://localhost/users 添加用户信息 POST(新增保存)
- http://localhost/users 修改用户信息 PUT(修改/更新)
- http://localhost/user/1 删除用户信息 DELETE(删除)
描述模块通常使用复数,表示此类资源如users
- 根据REST风格对资源进行访问成为RESTful
3.2 入门案例
- 设定http请丢动作(动词):method=RequestMethod.DELETE
- 设定请求参数(路径变量):value="/users/{id}) @PathVariable Integer id
method:请求方式的设定
@PathVariable
@Controller
@RequestMapping("/user")//请求映射路径。路径前缀
public class UserController{
@RequestMapping(value="/save",method=RequestMethod.POST)
@ResponseBody
public String save(){
System.out.println("user save");
return "{'info':'user save'}"
}
@RequestMapping(value="/users/{id}",method=RequestMethod.DELETE)//路径变量
@ResponseBody
public String delete(@PathVariable Integer id){
System.out.println("user delete");
return "{'info':'user save'}"
}
@RequestMapping(value="/users",method=RequestMethod.PUT)
@ResponseBody
public String update(@RequestBody User user){
System.out.println("user update..."+user);
return "{'info':'user update '}"
}
@RequestMapping(value="/users/{id}",method=RequestMethod.GET)//路径变量
@ResponseBody
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'info':'user getById'}"
}
@RequestMapping(value="/users",method=RequestMethod.GET)
@ResponseBody
public String getAll(){
System.out.println("user getAll..."+id);
return "{'info':'user getAll'}"
}
}
@RequestBody @RequestParam @PathVariable
- 区别
- @RequestParam用于接收url地址传参或表单传参
- @RequestBody用于接收json数据
- @PathVariablei用于接收路径参数,使用参数名称描述路径参数
- 应用
- 后期开发,发送请求参数超过1个时,以json格式为主,@RequestBody应用较广
- 如果非json格式数据,选用@RequestParam接收请求参数
- 采用RESTful开发,当参数数量较少时,例如1个,可以采用@PathVariable接收请求路径变量,通常用于请求id值
3.3 快速开发
///@Controller
//@ResponseBody
@RestController//替代由@Controller和@ResponseBody的组成
@RequestMapping("/user")//请求映射路径。路径前缀
public class UserController{
//@RequestMapping(method=RequestMethod.POST)
@PostMapping//代替上面@RequestMapping
public String save(){
System.out.println("user save");
return "{'info':'user save'}"
}
//@RequestMapping(value="/{id}",method=RequestMethod.DELETE)//路径变量
@DeleteMapping(value="/{id}")
public String delete(@PathVariable Integer id){
System.out.println("user delete");
return "{'info':'user save'}"
}
//@RequestMapping(method=RequestMethod.PUT)
@PutMapping
public String update(@RequestBody User user){
System.out.println("user update..."+user);
return "{'info':'user update '}"
}
//@RequestMapping(value="/{id}",method=RequestMethod.GET)//路径变量
@GetMapping(value="/{id}")
public String getById(@PathVariable Integer id){
System.out.println("user getById..."+id);
return "{'info':'user getById'}"
}
//@RequestMapping(method=RequestMethod.GET)
@GetMapping()
public String getAll(){
System.out.println("user getAll..."+id);
return "{'info':'user getAll'}"
}
}
3.4 放行(访问page css js等)
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 当访问/pages/????时候,走/pages目录下的内容
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
}
}
4 SSM整合
4.1表现层数据封装
-
前端接收数据格式——创建结果模型类
- 封装数据到data属性中
- 封装操作到code属性中
- 封装特殊消息到message(msg)属性中(20040查询失败 20041查询成功 0结尾失败)
public class Result{ private Object data; private Integer code; private String msg; } public class Code{ public static final Integer SAVE_OK = 20011; public static final Integer DELETE_OK = 20021; public static final Integer UPDATE_OK = 20031; public static final Integer GET_OK = 20041; public static final Integer SAVE_ERR =20010; public static final Integer DELETE_ERR =20020; public static final Integer UPDATE_ERR = 20030, public static final Integer GET_ERR = 20048 } @RequestMapping('/books') public class BookController{ @Autowired private BookService bookService; @GetMapping("/{id}") public Result getById(@PathVariable Integer id){ Book book = bookService.getById(id); Integer code = book != null ? Code.GET_ok : Code.GET_ERR; String msg = book != null ? "" : "数据查询失败,请重试"; return new Result(code,book,msg); }
4.2异常处理器
-
出现的位置和诱因
- 框架内部抛出异常:使用不合规
- 数据层抛出异常:外部服务器故障导致(服务器访问超时)
- 业务层抛出异常:业务逻辑书写错误(遍历业务书写操作,导致索引异常)
- 表现层抛出异常:数据收集、校验等规则(不匹配的数据类型间导致)
- 工具类抛出异常:因工具类书写不严谨不够健壮导致(必要释放的连接长期未释放)
-
异常均放到表现层处理
-
异常使用AOP来处理
-
异常处理器
- 集中的统一的处理项目中的异常
@ExceptionHandler:拦截异常
@RestControllerAdvice public class ProjectExceptionAdvice{ @ExceptionHandler(Exception.class) public Result doException(Exception ex){ return new Result(666,null); } }
4.3 异常处理方案
-
项目异常分类
- 业务异常
- 规范的用户行为产生的异常
- 不规范的用户行为操作产生的异常
- 系统异常
- 项目运行过程中可预计且无法避免的异常
- 其他异常
- 编程人员未预期到的异常
- 业务异常
-
项目异常处理方案
- 业务异常
- 发送对应消息传递给用户,提醒规范操作
- 系统异常
- 发送固定消息传递给用户,安抚用户
- 发特定消息给运维人员,提醒维护
- 记录日常
- 其他异常
- 发送固定消息传递给用户,安抚用户
- 发特定消息给编程人员,提醒维护(纳入预期范围内 )
- 记录日志
- 业务异常
-
自定义项目系统级异常
public class SystemException extends RuntimeException {
private Integer code;
public SystemException(Integer code, String message) {
super(message);
this.code = code;
}
public SystemException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 自定义业务级异常
public class BusinessException extends RuntimeException {
private Integer code;
public BusinessException(Integer code, String message) {
super(message);
this.code = code;
}
public BusinessException(Integer code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
- 自定义异常编码
public class Code{
public static final Integer SYSTEM_UNKNOW_ERROR = 50001;
public static final Integer SYSTEM_TIMEOUT_ERROR = 50002;
public static final Integer PROJECT_VALIDATION_ERROR = 60001;
public static final Integer PROJECT_BUSINESS_ERROR = 60002;
}
- 触发自定义异常
@Service
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
public Book getById(Integer id){
if(id<0){
throw new BusinessException(Code.PROJECT_BUSINESS_ERROR,"请勿进行非法操作");
}
return bookDao.getById(id);
}
}
- 拦截并处理异常
@RestControllerAdvice
public class ProjectExceptionAdvice {
@ExceptionHandler(BusinessException.class)
public Result doBusinessException(BusinessException ex) {
return new Result(ex.getCode(), null, ex.getMessage());
}
@ExceptionHandler(SystemException.class)
public Result doSystemException(SystemException ex) {
// 记录日志(错误堆栈)
// 发送邮件给开发人员
// 发送短信给运维人员
return new Result(ex.getCode(), null, ex.getMessage());
}
@ExceptionHandler(Exception.class)
public Result doException(Exception ex) {
// 记录日志(错误堆栈)
// 发送邮件给开发人员
// 发送短信给运维人员
return new Result(Code.SYSTEM_UNKNOW_ERROR, null, "系统繁忙,请联系管理员!");
}
}
5 拦截器
-
拦截器是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
-
作用
- 在指定的方法调用前后执行预先设定的代码
- 阻止原始方法的执行
-
拦截器与过滤器的区别
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
5.1 入门案例
- 声明拦截器的bean,并实现HandlerInterceptor接口(扫描加载bean)
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
- 定义配置类,继承WebMvcConfigurationSupport,实现addInterceptor方法(注意:扫描加载配置)
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport{
@Override
public void addInterception(InterceptorRegistry registry){
...
}
}
- 添加拦截器并设定拦截的访问路径,路径可以通过可变参数设置多个
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport{
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterception(InterceptorRegistry registry){
registry.addInterceptor(projectInterceptor).addPathPatterns("/books");
}
}
- 使用标准接口简化开发(侵入性较强)
@Configuration
@EnableWebMvc
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig implements WebMvcConfigurer {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
}
}
- preHandle一定执行!如果其返回值是true则继续执行下面的controller postHandle afterComletion,如果是false则结束
5.2 拦截器参数
- 前置处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle...");
return true;
}
//参数:request:请求对象;response:响应对象;handler:被调用的处理器对象,本质是一个方法对象,对反射技术中的method对象进行再包装
//返回值:false:被拦截的处理器将不执行
- 后置处理
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
//参数:modelAndView:如果处理器执行完成具有返回结果,可以读取到对应数据与页面信息,并进行调整
- 完成后处理
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
//参数:ex:如果处理器执行过程中出现异常对象,可以针对异常情况进行单独处理
5.3 多拦截器执行顺序
- 当配置多个拦截器时,形成拦截器链
- 拦截器链的运行顺序参照拦截器添加顺序为准
- 当拦截器出现对原始处理器的拦截,后面的拦截器均终止运行
- 当拦截器运行终端,仅运行配置在前面的拦截器的afterCompletion操作