SpringBoot整合SpringMVC

一、初识SpringMVC

1.1 SpringMVC框架的设计

  目前前后端都流行使用MVC框架,他松散的结构使得开发高效、简便,下面看一下SpringMVC的流程图:
在这里插入图片描述
  对于上述的流程,MVC先通过控制器处理请求,然后通过模型层处理逻辑,因为通常是在nosql或关系数据库进行数据的处理,因此模型层分为业务层和数据访问层,最后将数据渲染到视图并返回给控制器,最后交给客户端。

1.2 SpringMVC流程

  我们需要了解一下SpringMVC的组件,否则我们不知道SpringBoot为我们做了哪些默认的初始化操作,在SpringMVC的流程中,不一定要走完全部的流程,下面我们看一下流程图:
在这里插入图片描述
  在SpringBoot机制下,启动SpringMVC会做一些组件的初始化,在spring-webmvc-xxx.jar包有一个DispatcherServlet.properties文件,里面有一些对象的初始化定义,启动时读取信息并初始化到IOC容器。可以帮助我们简化SpringMVC的配置。
  下面来开发一个控制器:

@Controller
@RequestMapping("/user")
public class UserController{
	//注入服务类
	private UserService userService = null;

	//展示用户详情
	@ReqeustMapping("details")
	public ModelAndView details(long id){
		//访问模型层获取数据
		User user = userService.getUser(id);
		//模型和视图
		ModelAndView mv = new ModelAndView;
		//定义模型视图
		mv.setViewName("user/details");
		//加入数据模型
		mv.addObject("user",user);
		//返回模型和视图
		return mv;
	}
}

  @Controller代表这是一个控制器,@RequestMapping表示的路径和控制器会被扫描到HandlerMapping机制中存储,当用户请求到来会根据HandlerMapping找到相关的控制器,只不过HandlerMapping返回一个HandlerExecutionChain对象,这是处理器链,这个对象是对控制器的包装,包含控制和他的拦截器等。
  不过上述的处理器链执行还需要一个适配器来运行,因为有的请求是http,有的是websocket等,运行时先通过模型层拿到数据,在将数据放入模型,最后返回模型和视图,最后交给视图解析器去解析视图。
  为了定制视图解析器,可以在application.properties文件中配置如下:

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

  通过上述的配置,即可解析视图了,上述配置了视图的前缀和后缀,所以返回逻辑视图会找/WEB-INF/jsp/user/details.jsp这个视图。但目前的前后端分离的趋势,有时候我们只需要返回json数据即可,即返回非逻辑视图,这在后边再介绍。

1.3 定制SpringMVC的初始化

  在SpringBoot中回去尽可能的配置SpringMVC相关的组件等,但是我们可以通过自定义的方式来实现不同场景下的SpringMVC,为此spring提供了一个接口WebMvcConfigurer,这是一个java8的接口,因此里面的方法都提供了default实现,如果需要自定义,只需要实现接口并重写特定的方法即可。
  在SpringBoot中是通过WebMvcAutoConfigurer进行自动配置的,他有一个静态的内部类WebMvcAutoConfigurationAdapter,通过它实现了自动配置SpringMVC,关于SpringBoot对于SpringMVC的可配置项,可以参考官方文档,这里不赘述。

二、深入SpringMVC的开发

2.1 处理器映射

  在启动时会将@RequestMapping注解配置的内容保存到HandlerMapping中,然后等待拦截请求,并与HandlerMapping进行匹配找到对应的处理器,并将控制器和处理器保存到HandlerExecutionChain对象交给DispatcherServlet,就可以运行了。
  @RequestMapping配置项主要有value和method,就是拦截路径和http请求的方法,之后为了简化,提供了很多注解如:@GetMapping、@PostMapping等。

2.2 获取控制器参数

  之前介绍过,处理器是对控制器的包装,请求到来之前会先将http请求的参数解析之后在进行控制器的方法,下面介绍一下如何获取参数。

2.2.1 无注解下获取参数

  无注解下可以允许参数为空,但参数名必须和http请求的参数名一致,如:

@GetMapping("/user")
@ResponseBody
public User noAnnotation(int id,String userName){
	....
}

  注意,@ResponseBody注解表示返回的是json格式的数据。

2.2.2 使用@RequestParam获取参数

  在前后端分离的环境下,有可能前端参数名和后端不对应,因此可以使用注解@RequestParam来进行映射:

@GetMapping("/user")
@responseBody
public User reqeustParam(@RequestParam(value="id",required=false) int id,@RequestParam(value="user_name",requried=false) String userName){
	....
}

  上述代码中,参数前的注解@RequestParam表示匹配前端传递的参数id和user_name,而且都可以为null。

2.2.3 传递数组

  SpringMVC支持使用逗号分隔来传递数组

@GetMapping("/user")
@RespnseBody
public User requestArray(int [] intArr,String [] strArr){
	....
}

  上述可以接受数组传递,因此前端的参数可以如下:
? intArr=1,2,3 & strArr=str1,str2,str3

2.2.4 传递Json

  在目前前后端分离下,可以使用json体传递参数,使用@RequestBody注解,且需要提供一个对象,对象的属性与json体的属性一一对应:

@PostMapping("/insert")
@ResponseBody
public User insert(@RequstBody User user){
	....
}

  这样可以提交类似表单的json数据。

2.2.5 传递url参数

  对于当下的restful风格的开发,使用url传递参数会更加简洁,使用{…}并结合@PathVariable注解使用:

@GetMapping("/{id}")
@ResponseBody
public User get(@PathVariable("id") int id){
	....
}

  通过{ … }来表明参数的名称和位置,注解@PathVariable知道如何从url获取参数,前端可以传递如:http://locahost/user/1,这个参数1就会被获取到。

2.2.6 获取格式化参数

  SpringMVC提供了对日期和数字类型的格式化参数处理,分别是注解@DateTimeFormat和@NumberFormat,使用如下:

@GetMapping("/format")
@ResponseBody
public Map<String,String> format(@DateTimeFormat(iso=ISO.Date) Date date,@NumberFormat(pattern="#,###.##") Double number){
	....
}

  在SpringBoot中,也可以不用@DateTimeFormat注解,在application.properties配置文件中配置如下即可:

spring.mvc.date-format=yyyy-MM-dd

2.3 自定义参数转换规则

  对于上述的参数接收,之所以使用简单的注解甚至不用注解就可获取到参数,这是因为SpringMVC会默认提供一套机制来获取这些参数,但是有的参数传递,SpringMVC默认并不支持,因此就需要我们自定义转换规则来适应这种情况。下面我们介绍一下SpringMVC是如何从http请求获取参数的。

2.3.1 处理器获取参数逻辑

  在处理器执行过程中会先从http请求和上下文环境得到参数,对于简单的参数会直接用简单的转换器转换,这些转换器是SpringMVC提供的,但如果转换请求体,则会调用HttpMessageConverter接口,看一下该接口:

public interface HttpMessageConverter<T>{
	//是否可读,clazz是java类型,mediaType是http请求类型
	boolean canRead(Class<?> clazz,MediaType mediaType);

	//判断clazz类型能否转换为mediaType媒体类型
	boolean canWrite(Class<?> clazz,Mediatype mediaType);

	//可支持的媒体类型列表
	List<MeidaType> getSupportedMeidaTypes();

	//当canRead验证通过后,读入http请求信息
	T read(Class<? extend T> clazz,HttpInputMessage inputMessage) throws IOException,HttpMessageNotReadableException;

	//当canWrite方法验证通过后写入响应
	void write(T t,Mediatype contentType,HttpOutputMessage outPutMessage) throws IOException,HttpMessageNotWritableExecption;
}

  下面对@ResponseBody注解进行解析,这个注解会使处理器采用请求体的内容进行参数转换,而前端的请求体是json,因此会先调用canRead方法判断可读,之后调用read方法将前端的参数转换为控制器User类型的参数,这样控制器就能得到参数了。

  下面讨论一下处理器转换参数的过程,在SpringMVC中是用WebDataBinder机制,作用是解析http请求的上下文,然后在控制器调用之前转换参数。

  处理器会从http请求中读取数据,然后通过三种接口来进行各类参数转换,Converter、Formatter、GenericConverter,这三个接口的实现类都是采用注册机机制,默认MVC注册多个转换器。

  下面是http请求体的转换流程:
在这里插入图片描述
  上述的流程并不一定完全走完,对于三个接口Converter处理普通简单的参数,如前端传递字符串,而后端用int接收,那么他就负责将前端的string转换为int,而Formatter是负责格式化处理参数,最后GenericConverter负责将字符转换为数组的。

  对于数据类型转换,SpringMVC提供一个服务机制去管理,他就是ConversionService接口,默认使用这个接口的子类DefaultFormattingConversionService对象管理这些转换类,下面是关系图:
在这里插入图片描述
  上述的三个转换接口通过注册机进行注册,在SpringBoot中还提供一个特殊的机制管理这些转换器,SpringBoot的自动配置类有一个方法可以实现注册:

@Override
public void addFormatters(FormatterRegistry registry){
	//遍历IOC容器,找到Converter类型的Bean注册到服务类中
	for(Converter<?,?> converter : getBeansOfType(Converter.class)){
		registry.addConverter(converter);
	}

	//遍历IOC容器,找到GenericConverter类型的Bean注册到服务类中
	for(GenericConverter converter : getBeansOfType(GenericConverter.class)){
		registry.addConverter(converter);
	}
	//遍历IOC容器,找到Formatter类型的Bean注册到服务类中
	for(Formatter<?> formatter : getBeansOfType(Formatter.class)){
		registry.addConverter(formatter);
	}
}

  我们可以实现三个转换器接口,SpringBoot会自动注册到服务类中。

2.3.2 一对一转换器(Converter)

  这个接口实现简单的一对一转换,定义如下:

public interface Converter<S,T>{
	//转换方法,S代表原类型,T代表目标类型
	T Convert(S source);
}

  可以看出通过Convert方法转换,如果是简单的类似string到int的转换,该方法会使用Spring默认的StringToNumber( T extends Number )进行转换。
  但是如果是前端传递一个{id}-{userName}格式的数据,而控制器的参数是User对象,那么只能自定义实现了:

@Component
public class StringToUserConverter implements Converter<String,User>{
	/**
	*转换方法
	*/
	@Override
	public User convert(String userStr){
		User user = new User();
		String [] strArr = userStr.split("-");
		Long id = Long.parseLong(strArr[0]);
		String userName = strArr[1];
		user.setId(id);
		user.setUserName(userName);
		return user;
	}
}

  这里使用注解@Component标注,并且实现了Converter接口,SpringBoot会扫描到IOC中,并在初始化的时候自动将这个类注册到转换机制中,下面开发一个控制器:

@GetMapping
@ResponseBody
public User getUserByConverter(User user){
	return user;
}

  在浏览器传入参数?user=1-userName1即可完成测试。

2.3.3 GenericConverter集合和数组的转换

  GenericConverter是数组转换器,讨论一下他的自定义实现,假设我们需要传递多个用户,那么MVC会使用 StringToCollectionConverter进行转换,他实现了GenericConverter接口,且已经是SpringMVC注册的转换器。
  他首先会将字符串以逗号分隔为一个个子串,然后根据每个子串和我们之前定义好的StringToUserConverter进行匹配,这样就可以转换为List<user>。
  下面开发一个控制器:

@GetMapping("/list")
@ResponseBody
public List<User> list(List<User> userList){
	return userList;
}

  下面传递参数:http://localhost/list?userList=1-username1,2-username2,3-username3即可完成验证。

2.4 数据验证

  上述介绍的是参数的转换和传递,紧接着就是参数的合法性验证,SpringBoot默认引入关于Hibernate Validator机制来支持JSR-303验证规范,有时还需要自定义验证机制。

2.4.1 JSR-303验证

  主要是通过注解的方式验证,先定义一个pojo:

public class ValidatorPojo{
	//非空判断
	@NotNull(message="id不能为空")
	private Long id;
	
	@Future(message="需要一个将来的日期")//只能是将来日期
	//@Past//只能是过去的日期
	@DateTimeFormat(pattern="yyyy-MM-dd")//日期格式化转换
	@NotNull//不能为空
	private Date date;

	@NotNull//不能为空
	@DecimalMin(value="0.1")//最小值0.1元
	@DecimalMax(value="10000.00")//最大值10000.00元
	private Double doubleValue = null;

	@Min(value=1,message="最小值为1")
	@Max(value=88,message="最大值为88")
	@NotNull//不能为空
	private Integer integer;

	@Range(min=1,max=88,message="范围是1到88")
	private Long range;

	//邮箱验证
	@Email(message="邮箱格式错误")
	private String email;

	@Size(min=20,max=30,message="字符串长度要求在20到30之间")
	private String size;
}

  上述就是使用JSR-303注解验证的案例,下面开发一个控制器:

/**
*解析参数验证错误
*@Param vp——需要验证的pojo,使用注解@Valid表示验证
*@Param errors 他由MVC验证参数后自动填充
*@Return 返回错误信息map
*/
@RequestMapping("/valid/validate")
@ResponseBody
public Map<String,Object> validate(@Valid @RequestBody ValidatorPojo vp,Errors errors){
	Map<String,Object> errMap = new HashMap<>();
	//获取错误列表
	List<ObjectError> oes = errors.getAllErrors();
	for(ObjectErrors oe : oes){
		String key = null;
		String msg = null;
		//字段错误
		if(oe instanceof FieldError){
			FieldError fr = (FieldError)oe;
			key = fe.getField();//获取错误验证字段名
		}else{
			//非字段错误
			key = oe.getObjectName()//获取验证对象名称
		}
		//错误信息
		msg = oe.getDefaultMessage();
		errMap.put(key,msg);
	}
	return errMap;
}

  上述就是JSR-303验证,但有时验证逻辑较为复杂,因此也可以自定义。

2.4.2 参数验证机制

  之前的注册转换器的WebDataBinder还可以注册验证器,注解@InitBinder允许在进入控制器方法前修改WebDataBinder的机制,在那之前先了解一下SpringMVC的验证机制,在SpringMVC中定义了一个接口Validator:

public interface Validator{
	/**
	*判定当前验证器是否支持该Class类型的验证
	*@Param clazz pojo类型
	*@return 当前验证器是否支持该pojo验证
	*/
	boolean supports(Class<?> clazz);

	/**
	*如果supports返回true,则这个方法执行验证逻辑
	*@param target 被验证pojo对象
	*@param errors 错误信息对象
	*/
	void validate(Object target,Errors errors);
}

  自定义验证器实现这个接口即可,下面看一下案例:

public class UserValidator implements Validator{
	//该验证器只支持User类验证
	@Override
	public boolean supports(Class<?> clazz){
		return clazz.equals(User.class);
	}

	//验证逻辑
	@Override
	public void validate(Object target ,Errors errors){
		//对象为空
		if(target == null){
			//直接在参数出报错,这样就不能进入控制器的方法
			errors.rejectValue("",null,"用户不能为空");
			return ;
		}
		//强制转换
		User user = (User)target;
		//用户名非空串
		if(StringUtils.isEmpty(user.getUserName())){
			//增加错误可以进入控制器方法
			errors.rejectValue("userName",null,"用户名不能为空");
		}
	}
}

  有了上述的自定义验证器,但还不能运行,因为需要注册给WebDataBinder,使用注解@InitBinder将会在执行控制器之前先执行被这个注解标注的方法,将WebDataBinder对象传递进去,然后使用这个对象的setValidator方法绑定自定义的验证器,除此之外还可以进行参数的自定义,看下面的控制器

@Controller
@RequestMapping("/user")
public class UserController{

	/**
	*调用控制器前先执行这个方法
	*@param binder
	*/
	@InitBinder
	public void initBinder(WebDataBinder binder){
		//绑定验证器
		binder.setValidator(new UserValidator());
		//定义日期参数格式,参数不在需要注解@DateTimeFormat,boolean表示允许参数为空
		binder.registerCustomEditor(Date.class,new CustomDateEditor(new SimpeDateFormat("yyyy-MM-dd"),false));
	}


	/**
	*@param user 用户对象用StringToUserConverter转换
	*@param errors 验证器返回的错误
	*@param date 因为WebDataBinder已经绑定了格式,所以不再需要注解
	*/
	@GetMapping("validator")
	@ResponseBody
	public Map<String,Object> validator(@Valid User user,Errors errors,Date date){
		Map<String,Object> map = new HashMap<>();
		map.put("user",user);
		map.put("date",date);
		//判断是否存在错误
		if(errors.hasErrors()){
			//获取全部错误
			List<ObjectError> oes = Errors.getAllErrors();
			for(ObjectError oe : oes){
				//判断是否字段错误
				if(oe instanceof FieldError){
					//字段错误
					FieldError fe = (FieldError)oe;
					map.put(fe.getField(),fe.getDefaultMessage());
				}else{
					//对象错误
					map.put(oe.getObjectName(),oe.getDefaultMessage());
				}
			}
		}
		return map;
	} 
  ....
}

  绑定了验证器和规定好日期格式后,通过注解@Valid标注,此时SpringMVC就会遍历对应的验证器,匹配验证器之后去验证User参数。

2.5 数据模型

  我们知道控制器的核心就是对数据的处理,在SpringMVC中提供了模型和视图,先来看模型,就是存放数据的地方,下图是SpringMVC使用的模型接口:
在这里插入图片描述
  SpringMVC有三种数据模型对象,ModelAndView、Model、ModelMap,这三个对象会由SpringMVC自动创建:

@RequestMapping("/data")
@Controller
public class DataModelContrller{
	//注入用户服务类
	@Autowired
	private UserService userService = null;

	//测试Model接口
	@GetMapping("/model")
	public String userModel(Long id,Model model){
		User user = userService.getUser(id);
		model.addAttribute("user",user);
		//这里返回字符串,SpringMVC会自动创建ModelAndView且绑定名称
		return "data/user";
	}

	//测试ModelMap类
	@GetMapping("/modelMap")
	public ModelAndView useModelMap(Long id,ModelMap,modelMap){
		User user = userService.getUser(id);
		ModelAndView mv = new ModelAndView();
		//设置视图名称
		mv.setViewName("data/user");
		//设置数据模型,这里系统会自动将modelMap与mv绑定
		modelMap.put("user",user);
		return mv;
	}

	//测试ModelAndView
	@GetMapping("/mav")
	public ModelAndView useModelAndView(Long id,ModelandView mv){
		User user = userService.getUser(id);
		//设置数据模型
		mv.addObject("user",user);
		//设置视图名称
		mv.setViewName("data/user");
		return mv;
	}
}

2.6 视图和视图解析器

  视图是将数据模型渲染给用户的组件,视图分为逻辑视图和非逻辑视图,对于逻辑视图需要视图解析器(常用的如:InternalResourceViewResolver)去解析,才能最终定位,而非逻辑视图仅仅将数据模型渲染出来即可,如MappingJackson2JsonView。

2.6.1 视图设计

  除了Json、Jsp等,还有其他的视图如Excel、PDF等,但都需要实现SpringMVC定义的接口View:

public interface View{
	//响应状态属性
	String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";

	//路径变量
	String PATH_VARIABLES = View.class.getName() + ".pathVaariables";

	//选择内容类型
	String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";

	//响应类型
	String getContentType();

	//渲染方法
	void render(Map<String,?> model,HttpServletRequest request,HttpServletResponse response) throws Exception;
}

  其中,getContentType是获取Http响应类型,而render方法是将数据模型渲染到视图的,model是从控制器返回的模型,这样就可渲染,渲染过程还是比较复杂的,因此SpringMVC提供了一些实现好的视图:
在这里插入图片描述
  对于其他的视图,可以阅读官方文档。

2.7 文件上传

2.7.1 SpringMVC对文件上传的支持

  DispatcherServlet会使用适配器模式,将HttpServletRequest接口对象转换为MultiPartHttpServletRequest对象,而他扩展了HttpServletRequest接口的所有方法:
在这里插入图片描述
  这样在文件上传时会将参数的接口对象进行转换,但在使用SpringMVc实现文件上传时,还需要配置MultiPartHttpServletRequest对象,这个任务是通过MultiPartResolver接口实现的,他存在两个实现类,StandardServletMultiPartResolver和CommonsMultiPartResolver,推荐使用前者,后者需要依赖apahce的第三方包。
  在SpringBoot机制中,如果没有自定义MultiPartResolver接口,SpringBoot会为我们自动创建对象StandardServletMultiPartResolver,并提供简单的配置:

//是否使用SpringMVC的多分部上传功能
spring.servlet.multipart.enabled=true
//将文件写入磁盘的阈值,值可以使用MB或KB作为单位
spring.servlet.multipart.file-size-threshold=0
//指定默认的上传文件夹
spring.servlet.multipart.location=E://data
//限制单个文件最大的大小
spring.servlet.multipart.max-file-size=1MB
//限制所有文件最大大小
spring.servlet.multipart.max-request-size=10MB
//是否延迟多部件文件请求的参数和文件的解析
spring.servlet.multipart.resolve-lazily=false

  之后可以使用Servlet规范的接口Part作为参数,也可以使用SpringMVC提供的MultipartFile接口作为参数,后者需要依赖第三方包。

2.7.2 开发文件上传功能

  开发控制器前需要自定义配置一些文件大小等参数,不在赘述,下面开始开发控制器:

@Controller
@RequestMapping("/file")
public class FileController{


	//使用HttpServletRequest对象作为参数
	@PostMapping("/upload/request")
	@ResponseBody
	public Map<String,Object> uploadRequest(HttpServletRequest request){
		boolean flag = false;
		MultipartHttpServletRequest mreq = null;
		//强制转换为MultipartHttpServletRequest接口对象
		if(request instanceof MultpartHttpServletRequest){
			mreq = (MultipartHttpServletRequest) request;
		}else{
			return dealResultMap(false,"上传失败");
		}
		//获取MultipartFile文件信息
		MultipartFile mf = mreq.getFile("file");
		//获取源文件名
		String filename = mf.getOriginalFilename();
		File file = new file(filename);
		try{
			//保存文件
			mf.transferTo(file);
		}catch(Exception e){
			e.printStackTrace();
			return dealResultMap(false,"上传失败");
		}
		return dealResultMap(true,"上传成功");
	}



	//使用SpringMVC的MultipartFile类作为参数
	@PostMapping("/upload/multipartFile")
	@ResponseBody
	public Map<String,Object> uploadMultipartFile(MultipartFile file){
		String filename = file.getOriginalFilename();
		File dest = new File(filename);
		try{
			file.transferto(dest);
		}catch(Exception e){
			e.printStackTrace();
			return dealResultMap(false,"上传失败");
		}
		return dealResultMap(true,"上传成功");
	}



	//测试part接口作为参数
	@PostMapping("/upload/part")
	@ResponseBody
	public Map<String,Object> uploadPart(Part file){
		//获取提交文件名
		String filename = file.getSubmittedFilename();
		try{
			//写入文件
			file.write(filename);
		}catch(Exception e){
			e.printStackTrance();
			return dealResultMap(fasle,"上传失败");
		}
		return dealResultMap(true,"上传成功");
	}


	//处理上传结果
	private Map<String,Object> dealResultMap(boolean success,String msg){
		Map<String,Object> reslut = new HashMap<String,Object>();
		result.put("success",success);
		result.put("msg",msg);
		return result;
	}
}

2.8 拦截器

  之前谈到HandlerMapping机制,当请求来到DispatcherServlet时,会通过HandlerMapping机制来匹配控制器,这样会返回一个HandlerExecutionChain对象,它包含了控制器和拦截器,而拦截器是来增强控制器功能的,有点类似SpringAOP。

2.8.1 拦截器的设计

  首先所有拦截器都要实现HandlerInterceptor接口:

public interface HandlerInterceptor{
	//处理器执行前方法
	default boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception{
		return true;
	}

	//处理器处理后方法
	default void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,@Nullable ModelAndView modelAndView) throws Exception{
	}

	//处理器完成后方法
	default void afterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,@Nullable Exception ex) throws Exception{
	}
}

  下面看一下这些拦截方法的执行流程
在这里插入图片描述

  • 执行preHandle方法,返回false结束整个流程,返回true则进行下一步
  • 执行处理器逻辑,包含控制器的功能
  • 执行postHandle方法
  • 执行视图解析和视图渲染
  • 最后执行afterCompletion方法
      该接口是基于java8的,所以默认提供了空实现,我们只需要实现接口并覆盖相应的方法即可。

2.8.2 开发拦截器

  根据需要重写对应的方法即可,但是重写之后还不能被SpringMVC发现,需要我们注册才可以使用,为此需要实现接口WebMvcConfigurer接口,并重写addInterceptors方法进行注册拦截器即可:
开发拦截器

public class Interceptor1 extends HandlerInterceptor{
	@Override
	public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception{
		System.out.println("处理器前方法");
		//返回true来继续后面的处理
		return true;
	}

	@Override
	public void postHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView) throws Exception{
		System.out.println("处理器后的方法");
	}
}

注册拦截器

@Configuration
public class MyInterceptors implements WebMvcConfigurer{
	@Override
	public void addInterceptors(IntercepterRegistry registry){
		//注册拦截器到SpringMVC机制,然后会返回一个拦截器注册
		InterceptorRegistration ir = registry.addInterceptor(new Interceptor1());
		//指定拦截匹配模式,限制拦截器拦截请求
		ir.addPathPatterns("/interceptor/*");//会拦截所有/interceptr路径的请求
	}
}

  下面开发一个控制器

@Controller
@RequestMapping("/interceptor")
public class InterceptorController{
	@GetMapping("/start")
	public String start(){
		System.out.pringln("执行处理器逻辑");
		return "/welcome";//返回欢迎页面
	}
}

  对于多个拦截器,按照按先注册先执行的方式进行,如:
1->2->3->处理器->3>2->1,对于处理器前方法返回false的场景是,执行到返回false后,之后的所有拦截器、处理器和所有拦截器的postHandle方法都不会执行。

2.9 SpringMVC拾遗

2.9.1 @ResponseBody转换为Json的秘密

  在方法上标注@ResponseBody注解后,处理器会记录这个方法的响应类型是Json,当执行完控制器返回后,处理器会启用结果解析器去解析这个结果,他会去轮询注册给SpringMVC的HttpMessageConverter接口的实现类,因为MappingJackson2HttpMessageConverter已经注册给SpringMVC,而SpringMVC将控制器的结果类型标注为Json,因此就可匹配上,于是在处理器内部把结果转换为Json。
下面是流程图:
在这里插入图片描述

2.9.2 重定向

  重定向就是通过各种方法将各种网络请求重新转向其他位置。假设一个这样的需求,向数据库插入新用户,之后通过JSP展现给请求者,则开发一个如下的控制器:

@Controller
@RequestMapping("/user")
public class UserController{
	//显示用户
	@GetMapping("/show")
	public String showUser(Long id,Model model){
		User user = userService.getUser(id);
		model.addAttribute("user",user);
		return "data/user";
	}

	//使用字符串指定跳转
	@GetMapping("/redirect1")
	public String redirect1(String username,String note){
		User user = new User();
		user.setUserName(username);
		user.setNote(note);
		//插入后主键回填
		userService.insertUser(user);
		return "redirect:/user/show?id=" + user.getId();
	}


	//使用模型和视图指定跳转
	@GetMapping("/redirect2")
	public ModelAndView redirect2(String userName,String note){
		User user = new User();
		user.setUserName(username);
		user.setNote(note);
		userService.insertUser(user);
		ModelAndView mv = new ModelAndView();
		mv.setViewName("redirect:/user/show?id=" + user.getId());
		return mv;
	}
}

2.9.3 操作会话对象

  操作HTTPSession十分普遍,SpringMVC提供了两个注解来操作,@SessionAttribute和@SessionAttributes,前者用于参数,将HTTPSession中的属性读出,赋予控制器的参数。后者只用于类的注解,将相关数据模型的属性保存到session中。不太常用,有兴趣可以查看官网即可。

2.10 总结

  SpringMVC的内部原理还是比较复杂的,我们需要做的就是了解各个组件之间的流程,以及可以在哪些组件加入业务代码,比如控制器,就是主要的业务逻辑,我们可以开发几个用于特定用途的拦截器,比如用户登录验证等操作都需要进行验证。还有比如淘宝买东西,当点击购买或加入购物车等涉及到用户信息的操作时,会进行拦截,判断一下用户是否登录。
  如上类似的功能组件,需要我们在工作学习中一点点的体会,当对各个组件以及他们的关系有一定的了解后,可以阅读源码来进一步的深入学习。

  • 6
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值