文章目录
SpringMVC 简介
Spring MVC 和 Servlet技术相同,都是做Web层(表现层)开发的
Spring MVC 比 Servlet更简便,可以用更少的代码完成功能
SpringMVC是一种基于java实现MVC模型的轻量级Web框架
如何快速搭建一个极简的SpringMVC项目:
-
从原型创建一个maven项目,在项目路径的main文件夹下创建一个java源码文件夹用于写项目代码
-
在pom.xml中导入spring-webmvc和Servlet的坐标
<dependencies> <!-- 1. 导入坐标spring-webmvc和Servlet--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> <!-- 这个和Tomcat插件会有冲突,因此要配成provided --> </dependency> </dependencies>
-
定义C层的controller。首先使用@Controller注解定义bean,然后写业务方法,用@RequestMapping注解设置当前业务方法的访问路径,并用@ResponseBody设置当前方法的返回值类型
// 2. 定义controller @Controller // 2.1 使用@Controller定义bean public class UserController { @RequestMapping("/save") // 2.2 写操作,并用@RequestMapping设置访问路径 @ResponseBody // 2.3 设置当前操作的返回值类型。意思是将返回的东西整体作为响应的内容给到外面 public String save(){ System.out.println("user save ..."); return "{'module':'springmvc'}"; } }
-
创建SpringMVC的配置文件(本质上还是Spring的配置文件),加载controller对应的bean
// 3. 创建SpringMVC的配置文件,加载controller对应的bean @Configuration @ComponentScan("com.itheima.controller") public class SpringMvcConfig { }
-
定义一个Servlet容器启动初始化的配置类,用于加载spring的配置
// 4. 定义一个Servlet容器启动的配置类,在里面加载spring的配置 public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer { // 加载SpringMVC容器的配置,Tomcat启动时会自动加载 @Override protected WebApplicationContext createServletApplicationContext() { AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); ctx.register(SpringMvcConfig.class); return ctx; } // 设置哪些请求归属SpringMVC处理 @Override protected String[] getServletMappings() { return new String[]{"/"}; // 所有请求归SpringMVC处理 } // 加载Spring容器的配置 @Override protected WebApplicationContext createRootApplicationContext() { return null; } }
-
在pom.xml中配置Tomcat插件,并设置以Tomcat启动,然后运行就可以访问了
<!-- 配置Tomcat插件 --> <build> <plugins> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.1</version> <configuration> <port>80</port> <path>/</path> </configuration> </plugin> </plugins> </build>
项目工作流程分析
启动服务器初始化过程
简洁版:将springMVC的配置加载到Tomcat的容器中,同时设置所有请求由SpringMVC处理
详细版:
- 服务器启动,执行ServletContainersInitConfig类,初始化Web容器
- 执行createServletApplicationContext方法,创建WebApplicationContext对象ctx
- 用ctx注册SpringMvcConfig
- 执行@ComponentScan加载所有的bean
- 建立Controller Bean中的每个@RequestMapping的名称与方法的映射关系(注册请求路径和对应方法)
- 执行getServletMappings方法,定义所有的请求都通过SpringMVC
单次请求过程
- 客户端发起请求
- web容器发现所有请求被设置都要经过 SpringMVC,将请求交给 SpringMVC 处理
- SpringMVC 解析请求路径,执行对应的方法
- 检测到方法有@ResponseBody注解,将方法的返回值作为响应体返回给请求方
Bean加载控制
如何加载Bean呢?
用Web容器加载 Spring 和 SpringMVC 配置
public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
@Override
protected WebApplicationContext createServletApplicationContext() {
// 加载SpringMVC环境
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringMvcConfig.class);
return ctx;
}
@Override
protected WebApplicationContext createRootApplicationContext() {
// 加载Spring环境
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(SpringConfig.class);
return ctx;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
下面这种事上面的简化开发版本,只需要提供配置类即可
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
SpringMVC 要控制表现层的相关Bean(Controller包中的)
Spring 要控制业务层和数据层的相关Bean(Service和Dao包中的)
如何避免Spring错误加载SpringMVC 中的 Bean?
加载 Spring 控制的 Bean 时排除掉 SpringMVC 控制的 Bean
SpringMVC 只加载controller包内的bean
//@Configuration
@ComponentScan("com.itheima.controller")
public class SpringMvcConfig {
}
Spring
- 设置 SpringConfig 扫描加载所有包,排除掉controller(若要使用排除,必须去掉SpringMvcConfig上的@Configuration,不然Spring会将SpringMvcConfig扫上,SpringConfig就白排除了)
- 设置 SpringConfig 扫描范围为精准范围,如只扫 Service 和 Dao
或者 不区分Spring和SpringMVC的环境,加载到同一环境中
@Configuration
//@ComponentScan({"com.itheima.service", "com.itheima.dao"})
// 或
@ComponentScan(value = "com.itheima",
excludeFilters = @ComponentScan.Filter(
type = FilterType.ANNOTATION, // 按注解过滤
classes = Controller.class // 排除Controller注解的
)
)
public class SpringConfig {
}
PostMan(接口测试工具)
网页调试 与 发送HTTP请求的Chrome插件,常用于接口测试
请求与响应
请求映射路径
在Controller或其方法上用**@RequestMapping**注解可以设置请求访问路径
为避免方法名冲突,有两种做法
-
将每个方法的请求映射路径设置为:模块名 + 方法名
@Controller public class UserController { @RequestMapping("/user/save") @ResponseBody public String save(){ return "{'moudle':'user save'}"; } @RequestMapping("/user/delete") @ResponseBody public String delete(){ return "{'moudle':'user delete'}"; } }
-
为Controller设置请求路径前缀,然后每个方法的路径依然设置为方法名
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/save") @ResponseBody public String save(){ return "{'moudle':'user save'}"; } @RequestMapping("/delete") @ResponseBody public String delete(){ return "{'moudle':'user delete'}"; } }
请求
前端携带数据发起请求,后端接收数据并做处理
Get请求和Post请求
Get和Post的区别:
SpringMVC 如何接参数
在对应方法中用形参接收
请求:http://localhost/commonParam?name=zjh&id=18
// 普通参数
@RequestMapping("/commonparam")
@ResponseBody
public String commonParam(String name, String id){
System.out.println("普通参数传递 name ===>" + name);
System.out.println("普通参数传递 id ===>" + id);
return "{'module':'common param'}";
}
中文乱码怎么办?在Web容器初始化配置中添加过滤器
public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
// 乱码处理,为web容器添加一个字符编码过滤器,并指定字符集
@Override
protected Filter[] getServletFilters() {
// 用Spring-web提供的字符过滤器
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
return new Filter[]{filter};
}
}
一般数据
请求参数类型
- 普通参数:URL地址传参
- 请求参数名 = 形参名:直接接收
- 请求参数名 ≠ 形参名:用**@RequestParam**注解绑定参数关系
- POJO类型参数:
- 请求参数名 与 形参对象属性名 相同,定义POJO类型形参即可接收
- 若存在POJO嵌套,子类的对应请求参数要用"类名.属性名"作为 请求参数名
- 数组类型参数
- 多个 同名请求参数,用数组类型的同名形参即可接收
- 集合类型参数
- 传参与数组类型相同,区别在于List是集合接口,直接用同名的List类型对象会报错(因为是要把参数放到集合对象里,而不是作为集合对象的某个属性,且接口无法直接实例化一个对象),需要用**@RequestParam**注解修饰List,将参数作为List的元素进行接收
总结:默认请求名和参数名要对应,对不上的用@RequestParam绑定对应关系
@Controller
//@RequestMapping("/user")
public class UserController {
// 普通参数
@RequestMapping("/commonParam")
@ResponseBody
public String commonParam(String name, String id){
System.out.println("普通参数传递 name ===>" + name);
System.out.println("普通参数传递 id ===>" + id);
return "{'module':'common param'}";
}
// 普通参数:请求参数名与形参名不同
@RequestMapping("/commonParamDifName")
@ResponseBody
public String commonParamDifName(@RequestParam("name") String userName, String id){
System.out.println("普通参数传递 userName ===>" + userName);
System.out.println("普通参数传递 id ===>" + id);
return "{'module':'common param'}";
}
// POJO参数(可以嵌套传递)
@RequestMapping("/pojoParam")
@ResponseBody
public String commonParamDifName(User user){
System.out.println("POJO参数传递 user ===>" + user);
return "{'module':'common param'}";
}
// 数组参数
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
System.out.println("数组参数传递 likes ===>" + Arrays.toString(likes));
return "{'module':'common param'}";
}
// 集合参数
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<Address> likes){
System.out.println("集合参数传递 likes ===>" + likes);
return "{'module':'common param'}";
}
}
json格式数据
步骤:
①添加json数据转换的坐标(jackson-databind)
②在SpringMvcConfig上用 @EnableWebMvc 注解开启自动转换json数据的功能,这个不是该注解的全部作用
③发送json数据
④设置接收json数据。用 @RequestBody 修饰方法形参,从请求体中取数据赋值给形参
json数据怎么发:
- 普通 json数组
[“game”,“travel”,“music”] - POJO json数据
{“name”:“zjh”, “age”:“18”, “address”:{“province”:“gansu”, “city”:“lanzhou”}} - POJO json数组
[{“name”:“zjh”, “age”:“18”}, {“name”:“zjh”, “age”:“18”}]
@RequestParam和@RequestBody的区别
- @RequestParam 用于接收 URL地址传参、表单传参
- @RequestBody 用于接收 json数据
日期类型参数
不同系统中的日期格式也不同,如2088-08-18、2088/08/18、08/18/2088
因此接收参数时,要根据不同的日期格式设置不同的接收方式
可以通过设置 @DateTimeFormat 注解的pattern属性值实现,用该注解修饰对应的形参
该功能是通过converter接口的一个实现类实现的
@EnableWebMvc的功能之一:根据类型匹配对应的类型转换器
// 日期参数
@RequestMapping("/dateParam")
@ResponseBody
public String dateParam(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("参数传递 date (yyyy/MM/dd) ===>" + date1);
System.out.println("参数传递 date (yyyy/MM/dd HH:mm:ss) ===>" + date2);
return "{'module':'data param'}";
}
响应
后端将处理结果反馈给前端,前端进行展示
@ResponseBody注解:用于设置将对应方法的返回的对象转换格式(转为JSON)后写入到response的body区,作为响应体
由HttpMessageConverter接口的实现类转,他是专门做web消息类型转换,他依赖于Jackson坐标
响应页面
将页面名称作为字符串return即可,不加@ResponseBody注解
// 响应页面(跳转页面)
@RequestMapping("/toJumpPage")
public String toJumpPage(){
System.out.println("跳转页面");
return "index.jsp";
}
响应数据
需要用@ResponseBody注解修饰方法,否则Spring默认方法返回值是一个页面
文本数据:简单的字符串
// 响应文本数据
@RequestMapping("/toText")
@ResponseBody // 将返回的对象转换格式后写入到response的body区
public String toText(){
System.out.println("返回纯文本");
return "response text";
}
JSON数据:键值对
// 响应POJO对象(JSON数据)
// 将方法返回类型设定为POJO对象类型,然后return POJO对象即可
@RequestMapping("/toJsonPOJO")
@ResponseBody
public User toJsonPOJO(){
User user = new User();
user.setName("zjh");
user.setAge(18);
return user;
}
// 响应POJO对象集合(JSON数据)
// 将方法返回类型设定为POJO对象类型的List,然后return List对象即可
@RequestMapping("/toJsonList")
@ResponseBody
public List<User> toJsonList(){
User user1 = new User();
user1.setName("aaa");
user1.setAge(15);
User user2 = new User();
user2.setName("bbb");
user2.setAge(19);
List<User> userList = new ArrayList<User>();
userList.add(user1);
userList.add(user2);
return userList;
}
REST风格
REST
(Representational State Transfer):表现形式状态转换
传统风格资源描述:
- http://localhost/user/getById?id=1
- http://localhost/user/saveUser
REST风格资源描述:
- http://localhost/user/1
- http://localhost/user
优点:
- 书写简化
- 隐藏资源访问行为,无法通过地址得知操作类型
如何区分操作
按照REST风格访问资源时通过行为动作(请求方式)区分对资源如何操作
路径 + 请求方式 = 具体访问行为
SpringMVC 支持8种请求方式,常用的有4种
资源访问路径 | 功能 | 请求方式 |
---|---|---|
http://localhost/users | 查询全部用户信息 | Get(查询) |
http://localhost/users/1 | 查询指定用户信息 | Get(查询) |
http://localhost/users | 添加用户信息 | POST(新增/保存) |
http://localhost/users | 修改用户信息 | PUT(修改/更新) |
http://localhost/users/1 | 删除用户信息 | DELETE(删除) |
在REST风格中,描述模块的名称通常使用复数,即加s,表示此类资源而非单个资源,如users、books
根据REST风格对资源进行访问称为RESTful
实现方法
- 设置请求方式同一个controller内,在@RequestMapping注解中,value设置为相同的(如/users),用method属性设置http的请求方式
- 设置请求参数
- 路径参数:①用@PathVariable修饰对应方法的形参 ②路径处用{形参名}绑定参数和形参
@RequestMapping(value = "/users/{id}&{name}", method = RequestMethod.DELETE) @ResponseBody public String delete(@PathVariable Integer id, @PathVarible String name){ // @PathVariable表示从请求路径中取值,在@RequestMapping中用大括号{形参名}绑定路径中的值和形参 System.out.println("user delete" + id + name); return "{'module':'user delete'}}"; }
- 路径参数:①用@PathVariable修饰对应方法的形参 ②路径处用{形参名}绑定参数和形参
快速开发
-
@RestController = @Controller + @ResponseBody
设置当前控制器类为RESTful风格,将方法级别要写的@ResponseBody提到类级别 -
用@RequestMapping注解设置对应控制器的根访问路径,简化每个方法上的访问路径设置
-
@请求方式Mapping = @RequestMapping(method = RequestMethod.请求方式)
若还有路径参数,则@请求方式Mapping(“/{id}”)
@RestController
@RequestMapping("/books2")
public class BookController2 {
@PostMapping
public String save(@RequestBody Book book){
System.out.println("book save" + book);
return "{'module':'book save'}}";
}
@DeleteMapping("/{id}")
public String delete(@PathVariable Integer id){
System.out.println("book delete" + id);
return "{'module':'book delete'}}";
}
}
基于RESTful的页面数据交互
要求:前端将数据发送给后端,后端接收后将反馈数据返回给前端,其中通过REST风格进行资源访问
重点:
设置对静态资源的放行。在Servlet容器初始化时设置了默认对资源的访问都由SpringMVC接管,这会导致webapp下的静态资源(html,css,js等)无法访问。此时需要创建一个配置类(继承WebMvcConfigurationSupport)去设置放行相关资源,在SpringMvcConfig中扫描config路径加载该配置到Servlet容器中
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
// 实现资源的普通访问:当访问某些路径时(如/pages/???),走/pages目录下的内容,不经过SpringMVC
registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
super.addResourceHandlers(registry);
}
}
拦截器
拦截器是一种动态拦截方法调用的机制,在SpringMVC中动态拦截控制器方法的执行
可以在指定方法调用前后执行预先设定的代码;或阻止原方法的执行
拦截器与过滤器的区别
归属:Filter属于Servlet技术,Interceptor属于SpringMVC技术
拦截内容:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强
实现
制作拦截器功能类
实现 HandlerInterceptor 接口
@Component
public class ProjectInterceptor implements HandlerInterceptor {
@Override
方法执行前
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("pre");
// 这里return false可以终止原始方法的运行
return true;
}
方法完整执行后(即不报错)
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("post");
}
拦截完成后执行后
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("after");
}
}
配置拦截器执行位置
通过 WebMvcConfigurationSupport 接口
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns支持可变参数,往后加路径就行,支持任意多个
registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
}
参数
- preHandle
- request:请求对象
- response:响应对象
- handler:被调用的方法对象,是反射中的Method方法的再包装。可以通过它拿到原始执行的对象,然后通过反射为所欲为
HandlerMethod hm = (HandlerMethod) handler; System.out.println(hm.getMethod().getParameters());
- 返回值:true执行post和after;false终止原始方法的运行
- postHandle
- ModelAndView:封装了SpringMVC页面跳转的一些相关数据,如页面名称等
- afterHandle
- Exception:ex是运行过程中抛出的异常,但是Spring有异常处理机制可以替换它
拦截器链
配置
写多个拦截器,在WebMvcConfigurationSupport 接口的addIntercrptors方法中多次配置拦截器
@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
@Autowired
private ProjectInterceptor projectInterceptor;
@Autowired
private ProjectInterceptor2 projectInterceptor2;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
// addPathPatterns支持可变参数,往后加路径就行,支持任意多个
registry.addInterceptor(projectInterceptor).addPathPatterns("/books", "/books/*");
registry.addInterceptor(projectInterceptor2).addPathPatterns("/books", "/books/*");
}
}
执行顺序
整体来说,按照拦截器的配置添加顺序依次栈执行
对于preHandle,按添加顺序执行;对于 postHandle和 afterCompletion ,按添加顺序的逆序执行
每层拦截器的pre返回true才会进入下一层拦截器,否则级联跳过下一层拦截器
如:若pre2返回false,不会进入pre3那一层,而是直接执行post1和after1
- pre1
- pre2
- pre3
- controller
- post3
- after3
- pre3
- post2
- after2
- pre2
- post1
- after1
注解速记
请求参数是URL路径**?后**的参数
http://localhost:8888/SpringMVC/main ?name=springmvc
路径变量是在URL项目名后 ?之前 所跟的信息,
http://localhost:8888/SpringMVC/3769?
http://localhost/api/employee/status/1?id=2
其中1为路径变量,需要在后端用@PathVariable注解修饰对应参数,id为请求参数,同名形参即可接收