Spring和SpringMvc(四)

目录

一 SpringMVC简介:

二:具体实现过程:

(一)servlet过程

(二)SpringMVC的过程

(三)注意事项

(四)五种类型的传参数:

1普通类型

2POJO数据类型:

3嵌套POJO的类型

4数组类型参数:

5集合类型参数:

(五)JSON数据传输参数

1.json普通数组:

2.json对象数据:

3.JSON对象数组:

(六)日期类型的参数传递:

三:Rest风格:

(一):Rest的引入

(二)初级的Restful风格:

四:拦截器:


一 SpringMVC简介:

SpringMVC就是一个Spring内置的MVC框架。

MVC框架,它解决WEB开发中常见的问题(参数接收、文件上传、表单验证、国际化等等),而且使用简单,与Spring无缝集成。支持 RESTful风格的URL请求。

采用了松散耦合可插拔组件结构,比其他 MVC 框架更具扩展性和灵活性。

SpringMVC【入门篇】 - 知乎 (zhihu.com)

综合来看:其实SpringMVC就是一个web的框架,整合了servlet,使得web程序的开发更加简洁优美。

二:具体实现过程:

(一)servlet过程

1. 创建 web 工程 (Maven 结构 )
2. 设置 tomcat 服务器,加载 web 工程 (tomcat 插件 )
3. 导入坐标 (Servlet)
4. 定义处理请求的功能类 (UserServlet) 5. 设置请求映射 ( 配置映射关系 )

(二)SpringMVC的过程

SpringMVC 的制作过程和上述流程几乎是一致的,具体的实现流程是什么 ?
1. 创建 web 工程 (Maven 结构 )
2. 设置 tomcat 服务器,加载 web 工程 (tomcat 插件 )
3. 导入坐标 ( SpringMVC +Servlet)
4. 定义处理请求的功能类 ( UserController )
5. 设置请求映射 ( 配置映射关系 )
6. SpringMVC 设定加载到 Tomcat 容器中
具体知识点:
@Controller 设定SpringMVC的核心控制器bean
@RequestMapping 设置当前控制器方法的请求访问路径
@ResponseBody 设置当前控制器方法响应内容为当前返回值

@ComponentScan 

excludeFilters: 排除扫描路径中加载的 bean, 需要指定类别 (type) 和具体项
(classes)
includeFilters: 加载指定的 bean ,需要指定类别 (type) 和具体项 (classes)
具体的实现过程 我用一个程序来讲解。
服务器启动,执行 ServletContainersInitConfig 类,初始化 web 容器
功能类似于以前的 web.xml
2. 执行 createServletApplicationContext 方法,创建了 WebApplicationContext 对象
该方法加载 SpringMVC 的配置类 SpringMvcConfig 来初始化 SpringMVC 的容器
3. 加载 SpringMvcConfig 配置类
4执行 @ComponentScan 加载对应的 bean
扫描指定包及其子包下所有类上的注解,如 Controller 类上的 @Controller 注解
5 加载 UserController ,每个 @RequestMapping 的名称对应一个具体的方法
6 执行 getServletMappings 方法,设定 SpringMVC 拦截请求的路径规则
/ 代表所拦截请求的路径规则,只有被拦截后才能交给 SpringMVC 来处理请求
7然后就是正常的执行过程。
总结一下:
1发送请求 http://localhost/save
2. web 容器发现该请求满足 SpringMVC 拦截规则,将请求交给 SpringMVC 处理
3. 解析请求路径 /save
4. /save 匹配执行对应的方法 save(
上面的第五步已经将请求路径和方法建立了对应关系,通过 /save 就能找到对应的 save 方法
5. 执行 save()
6. 检测到有 @ResponseBody 直接将 save() 方法的返回值作为响应体返回给请求方

(三)注意事项

SpringMVC 加载其相关 bean( 表现层 bean), 也就是 controller 包下的类
Spring 控制的 bean
业务 bean(Service)
功能 bean(DataSource,SqlSessionFactoryBean,MapperScannerConfigurer )
所以在@ComponentScan的时候就用精准扫描包,不要直接把所有扫描了,要不然就会出现报错。
方式一 :Spring 加载的 bean 设定扫描范围为精准范围,例如 service 包、 dao 包等
方式二 :Spring 加载的 bean 设定扫描范围为 com.itheima, 排除掉 controller 包中的 bean
@ComponentScan ( value = "com.blue" ,
excludeFilters = @ComponentScan . Filter (
  type = FilterType . ANNOTATION ,
  classes = Controller . class
  )
  )
excludeFilters 属性:设置扫描加载 bean 时,排除的过滤规则
type 属性:设置排除规则,当前使用按照 bean 定义时的注解类型进行排除
ANNOTATION :按照注解排除
ASSIGNABLE_TYPE: 按照指定的类型过滤
ASPECTJ: 按照 Aspectj 表达式排除,基本上不会用
REGEX: 按照正则表达式排除
CUSTOM: 按照自定义规则排除
大家只需要知道第一种 ANNOTATION 即可
classes 属性:设置排除的具体注解类,当前设置排除 @Controller 定义的 bean
第二个要注意的就是,有了spring的配置类,要想在tomcat服务器加载并且启动。完全在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 () {
AnnotationConfigWebApplicationContext ctx = new
AnnotationConfigWebApplicationContext ();
ctx . register ( SpringConfig . class );
  return ctx ;
}
  }
其实可以去简化
Spring 还提供了一种更简单的配置方式,可以不用再去创建
AnnotationConfigWebApplicationContext 对象,不用手动 register 对应的配置类
public class ServletContainersInitConfig extends
AbstractAnnotationConfigDispatcherServletInitializer {
  protected Class <?> [] getRootConfigClasses () {
  return new Class []{ SpringConfig . class };
}
protected Class <?> [] getServletConfigClasses () {
return new Class []{ SpringMvcConfig . class };
}
protected String [] getServletMappings () {
return new String []{ "/" };
}
// 乱码处理post的情况
@Override
protected Filter [] getServletFilters () {
CharacterEncodingFilter filter = new CharacterEncodingFilter ();
filter . setEncoding ( "UTF-8" );
return new Filter []{ filter };
}
}
这样整体来讲,就是实现了由网页发送的请求信息,完全交给了SpringMVC来处理。

(四)五种类型的传参数:

普通参数
POJO 类型参数
嵌套 POJO 类型参数
数组类型参数
集合类型参数
1普通类型
@RequestMapping ( "/commonParamDifferentName" )
@ResponseBody
public String commonParamDifferentName ( @RequestParam ( "name" ) String
userName , int age ){
System . out . println ( " 普通参数传递 userName ==> " + userName );
System . out . println ( " 普通参数传递 age ==> " + age );
return "{'module':'common param different name'}" ;
}
@RequestParam 注解框架就不需要自己去解析注入,能提升框架处理性能
RequestParam使用后就可以使得如果传来的名字和这里的名字不一样,来进行替换,匹配好就行。
2POJO数据类型:
简单数据类型一般处理的是参数个数比较少的请求,如果参数很多的话。就用这个方式。
//POJO 参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping ( "/pojoParam" )
@ResponseBody
public String pojoParam ( User user ){
System . out . println ( "pojo 参数传递 user ==> " + user );
return "{'module':'pojo param'}" ;
}
3嵌套POJO的类型
public class Address {
private String province ;
private String city ;
//setter...getter...
}
public class User {
private String name ;
private int age ;
private Address address ;
//setter...getter...
}
嵌套 POJO 参数:请求参数名与形参对象属性名相同,按照对象层次结构关系即可接收嵌套 POJO 属性参数
发送的时候就是 
name: age: address.city  address.province
//POJO 参数:请求参数与形参对象中的属性对应即可完成参数传递
@RequestMapping ( "/pojoParam" )
@ResponseBody
public String pojoParam ( User user ){
System . out . println ( "pojo 参数传递 user ==> " + user );
return "{'module':'pojo param'}" ;
}
请求参数 key 的名称要和 POJO 中属性的名称一致,否则无法封装
4数组类型参数:
数组参数:请求参数名与形参对象属性名相同且请求参数为多个,定义数组类型即可接收参数
发送请求和参数 :
发送的时候就按照数组名去发送 
比如 要接受的是likes数组
key value
likes  game
likes  music
likes  travel
// 数组参数:同名请求参数可以直接映射到对应名称的形参数组对象中
@RequestMapping ( "/arrayParam" )
@ResponseBody
public String arrayParam ( String [] likes ){
System . out . println ( " 数组参数传递 likes ==> " + Arrays . toString ( likes ));
return "{'module':'array param'}" ;
}
5集合类型参数:
// 集合参数:同名请求参数可以使用 @RequestParam 注解映射到对应名称的集合对象中作为数据
@RequestMapping ( "/listParam" )
@ResponseBody
public String listParam ( @RequestParam List < String > likes ){
System . out . println ( " 集合参数传递 likes ==> " + likes );
return "{'module':'list param'}" ;
}
需要注意的是,这里需要要用@RequestParam来进行修饰。
不然的话,SPringMVC会将list看做是一个POJO对象来处理,但是是一个接口无法创建对象,所以报错。

(五)JSON数据传输参数

json 普通数组( ["value1","value2","value3",...]
json 对象( {key1:value1,key2:value2,...}
json 对象数组( [{key1:value1,...},{key2:value2,...}]
1.json普通数组:
1添加依赖:
<dependency>
<groupId> com.fasterxml.jackson.core </groupId>
<artifactId> jackson-databind </artifactId>
<version> 2.9.0 </version>
</dependency>
2在SpringMVCConfig开启注解支持json的格式转换
@Configuration
@ComponentScan ( "com.itheima.controller" )
// 开启 json 数据类型自动转换
@EnableWebMvc
public class SpringMvcConfig {
}
3参数前面添加@RequeBody
// 使用 @RequestBody 注解将外部传递的 json 数组数据映射到形参的集合对象中作为数据
@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'}" ;
}
这样完成了数据的传输。

2.json对象数据:
@RequestMapping ( "/pojoParamForJson" )
@ResponseBody
public String pojoParamForJson ( @RequestBody User user ){
System . out . println ( "pojo(json) 参数传递 user ==> " + user );
return "{'module':'pojo for json param'}" ;
}
3.JSON对象数组:
@RequestMapping ( "/listPojoParamForJson" )
@ResponseBody
public String listPojoParamForJson ( @RequestBody List < User > list ){
System . out . println ( "list pojo(json) 参数传递 list ==> " + list );
return "{'module':'list pojo for json param'}" ;
}
@RequestParam 用于接收 url 地址传参,表单传参【 application/x-www-form
urlencoded
@RequestBody 用于接收 json 数据【 application/json

(六)日期类型的参数传递:

如果传来的是: yyyy/mm/dd的形式
@RequestMapping ( "/dataParam" )
@ResponseBody
public String dataParam ( Date date )
System . out . println ( " 参数传递 date ==> " + date );
return "{'module':'data param'}" ;
}
如果传来的是 :yyyy-mm-dd的形式
@RequestMapping ( "/dataParam" )
@ResponseBody
public String dataParam ( Date date ,
@DateTimeFormat ( pattern = "yyyy-MM-dd" ) Date date1 )
System . out . println ( " 参数传递 date ==> " + date );
System . out . println ( " 参数传递 date1(yyyy-MM-dd) ==> " + date1 );
return "{'module':'data param'}" ;
}
加上了@DateTimeFormat的注解
如果传来的携带时间的日期:
yyyy/MM/dd HH:mm:ss的形式
@RequestMapping ( "/dataParam" )
@ResponseBody
public String dataParam ( Date date ,
@DateTimeFormat ( pattern = "yyyy-MM-dd" ) Date date1 ,
@DateTimeFormat ( pattern = "yyyy/MM/dd HH:mm:ss" ) Date
date2 )
System . out . println ( " 参数传递 date ==> " + date );
System . out . println ( " 参数传递 date1(yyyy-MM-dd) ==> " + date1 );
System . out . println ( " 参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> " + date2 );
return "{'module':'data param'}" ;
}

三:Rest风格:

(一):Rest的引入

REST Representational State Transfer ),表现形式状态转换 , 它是一种软件架构 风格
当我们想表示一个网络资源的时候,可以使用两种方式 :
传统风格资源描述形式
http://localhost/user/getById?id=1 查询 id 1 的用户信息
http://localhost/user/saveUser 保存用户信息
REST风格描述形式
http://localhost/user/1
http://localhost/user
传统方式一般是一个请求url对应一种操作,这样做不仅麻烦,也不安全,因为会程序的人读取了你的请求url地址,就大概知道该url实现的是一个什么样的操作。
REST的优点:
隐藏资源的访问行为,无法通过地址得知对资源是何种操作
书写简化
按照 REST 风格访问资源时使用 行为动作 区分对资源进行了何种操作
http://localhost/users 查询全部用户信息 GET (查询)
http://localhost/users/1 查询指定用户信息 GET (查询)
http://localhost/users 添加用户信息 POST (新增 / 保存)
http://localhost/users 修改用户信息 PUT (修改 / 更新)
http://localhost/users/1 删除用户信息 DELETE (删除)
请求的方式比较多,但是比较常用的就 4 种,分别是 GET , POST , PUT , DELETE
按照不同的请求方式代表不同的操作类型。
发送 GET 请求是用来做查询
发送 POST 请求是用来做新增 发送 PUT 请求是用来做修改
发送 DELETE 请求是用来做删除
但是 注意 :
         上述行为是约定方式,约定不是规范,可以打破,所以称REST 风格,而不是 REST 规范。
        REST中规定 GET/POST/PUT/DELETE 针对的是查询 / 新增 / 修改 /删除,但是我们如果非要用GET 请求做删除,这点在程序上运行是可以实现的
描述模块的名称通常使用复数,也就是加 s 的格式描述,表示此类资源,而非单个资源,例
:users books accounts......
清楚了什么是 REST 风格后,我们后期会经常提到一个概念叫 RESTful ,那什么又是 RESTful ?
根据 REST 风格对资源进行访问称为 RESTful

(二)初级的Restful风格:

前端发送请求的时候使用 : http://localhost/users/1/tom , 路径中的 1 tom 就是我们想要传递的
两个参数
@RequestMapping ( value = "/users/{id}/{name}" , method = RequestMethod . DELETE )
@ResponseBody
public String delete ( @PathVariable Integer id , @PathVariable String name )
{
System . out . println ( "user delete..." + id + "," + name );
return "{'module':'user delete'}" ;
}
@Controller
public class UserController {
// 设置当前请求方法为 GET ,表示 REST 风格中的查询操作
@RequestMapping ( value = "/users/{id}" , method = RequestMethod . GET )
@ResponseBody
public String getById ( @PathVariable Integer id ){
System . out . println ( "user getById..." + id );
return "{'module':'user getById'}" ;
}
}
@RequestParam 用于接收 url 地址传参或表单传参
@RequestBody 用于接收 json 数据
@PathVariable 用于接收路径参数,使用 { 参数名称 } 描述路径参数
改进后:
@RestController //@Controller + ReponseBody
@RequestMapping ( "/books" )
public class BookController {
//@RequestMapping(method = RequestMethod.POST)
@PostMapping
public String save ( @RequestBody Book book ){
System . out . println ( "book save..." + book );
return "{'module':'book save'}" ;
}
//@RequestMapping(value = "/{id}",method = RequestMethod.DELETE)
@DeleteMapping ( "/{id}" )
public String delete ( @PathVariable Integer id ){
System . out . println ( "book delete..." + id );
return "{'module':'book delete'}" ;
}
}
每个方法的 @RequestMapping 注解中都定义了访问路径 /books ,重复性太高。
@RequestMapping 提到类上面,用来定义所有方法共同的访问路径
:每个方法的 @RequestMapping 注解中都要使用 method 属性定义请求方式,重复性太高。
使用 @GetMapping @PostMapping @PutMapping @DeleteMapping 代替
每个方法响应 json 都需要加上 @ResponseBody 注解,重复性太高
1. ResponseBody 提到类上面,让所有的方法都有 @ResponseBody 的功能
2. 使用 @RestController 注解替换 @Controller @ResponseBody 注解,简化书写
总结一下:
@RestController:
设置当前控制器类为 RESTful 风格,
等同于 @Controller @ResponseBody 两个注解组合功能
@GetMapping @PostMapping @PutMapping @DeleteMapping
设置当前控制器方法请求访问路径与请求动作,每种对应一个请求动作,
例如 @GetMapping 对应 GET 请求
最后注意一下:如果说需要访问web下的文件夹目录信息,因为SpringMVC在配置路径的时候拦截的是”/“ 那么就会拦截了静态资源 却不知道怎么处理。就会报404。
这时候需要进行放行。
@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/" )
;
  }
  }
同时需要进行包的扫描:
@Configuration
@ComponentScan ({ "com.blue.controller" , "com.blue.config" })
@EnableWebMvc
public class SpringMvcConfig {
}

四:拦截器:

拦截器( Interceptor )是一种动态拦截方法调用的机制,在 SpringMVC 中动态拦截控制器方法
的执行。
作用 :
在指定的方法调用前后执行预先设定的代码
阻止原始方法的执行
总结:拦截器就是用来做增强
具体的情况
(1) 浏览器发送一个请求会先到 Tomcat web 服务器
(2)Tomcat 服务器接收到请求以后,会去判断请求的是静态资源还是动态资源
(3) 如果是静态资源,会直接到 Tomcat 的项目部署目录下去直接访问
(4) 如果是动态资源,就需要交给项目的后台代码进行处理
(5) 在找到具体的方法之前,我们可以去配置过滤器 ( 可以配置多个 ) ,按照顺序进行执行
(6) 然后进入到到中央处理器 (SpringMVC 中的内容 ) SpringMVC 会根据配置的规则进行拦截
(7) 如果满足规则,则进行处理,找到其对应的 controller 类中的方法进行执行 , 完成后返回结果
(8) 如果不满足规则,则不进行处理
(9) 这个时候,如果我们需要在每个 Controller 方法执行的前后添加业务,具体该如何来实现 ?
这个就是拦截器要做的事。
归属不同: Filter 属于 Servlet 技术, Interceptor 属于 SpringMVC 技术
拦截内容不同: Filter 对所有访问进行增强, Interceptor 仅针对 SpringMVC 的访问进行增强
 
拦截器的实现:
1.定义拦截器类,实现HandlerInterceptor接口
@Component
//定义拦截器类,实现HandlerInterceptor接口
//注意当前类必须受Spring容器控制
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...");
}
}

2配置拦截器类:
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
//配置拦截器
registry.addInterceptor(projectInterceptor).addPathPatterns("/books"
);
}
}
request: 请求对象
response: 响应对象
handler: 被调用的处理器对象,本质上是一个方法对象,对反射中的 Method 对象进行了再包装
拦截器链的执行顺序:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值