HandlerAdapter as the most complex part of SpringMVC, before the real analysis of its source code, it is necessary for us to solve some pre-knowledge points, otherwise in the process of looking at the source code will encounter blind spots…..
@ModelAttribute
Let's start by saying what this note does@ModelAttributeThe annotated method is executed once before the Controller method is executed. Here's an example:@Controller
public class TestController {
@PostMapping( "/test" )
public String test(a) {
System.out.println( request );
return "index";
}
@ModelAttribute
public void testModelAttribute1 (a) {
System.out.println( "Hello"); }} Analysis: Every time we request this /test, we call testModelAttribute1 before SpringMVC calls test, but that's just knowledge@ModelAttributeAnnotations are just a small feature@ModelAttribute: To define and manipulate the properties of Model, Model is a thing that runs through the whole SpringMVC. I believe that when you contact the framework, you must learn about Model. In fact, the essence of Model is just a Map. The data comes from several sources, such as the Request parameters, the session Atrribute, and the Model(which is a Map),@ModelAttributeThe annotation does two things. When applied to a method, the return value of the method is put into this so-called Model if specified@ModelAttributeIf not specified, the key is generated according to certain generation rules. When applied to a method parameter, the parameter is fetched from the Model and assigned to the parameter. If specified, the value of the annotation is returned as the value of the key in the Model@ModelAttributeSelect * from Model; select * from Model; select * from Model;@GetMapping( "/test" )
public String test (@ModelAttribute( "username" ) String username,
@ModelAttribute List<String> list, Model model) {
System.out.println( model );
System.out.println( username );
System.out.println( list );
return "index";
}
@ModelAttribute( "username" )
public String testModelAttribute1 (a) {
return "zhangsan";
}
@ModelAttribute
public List<String> testModelAttribute2 (a) {
return Arrays.asList( "a"."b"); } {username=zhangsan, stringList=[a, b]} zhangsan [a, b@ModelAttributeThe annotation annotation method is executed every time the Controller's method (test method) is executed, and the return value is put into the Model object, while our hanler(later called handler for request mapping methods such as test) is executed,@ModelAtrributeThe parameters to the annotation are retrieved from the Model, and as you can see, when we output the Model, there are two values in it. The key of the set we return is called stringList. Without actively specifying the value of the annotation, the key is generated according to certain rules. When we parameter bind, if we do not actively specify the value of the annotation, we will automatically extract a little summary from the Model that is most appropriate:@ModelAttributeThe annotated method executes every time the Hanler of the Controller it's in executes and puts the return value into the Model. The hanler property annotated by the annotation gets its value from the Model. So how do you get it just from the Model? This is the ArgumentResolver in HandlerAdapter. If you are coming from the next article, I will introduce ModelAttribute to the argument parser first. So I'm not confused about what a parameter parser is, of course, but I just need to understand what this annotation does, and the rest will become clear, okayCopy the code
@SessionAttributes
@SessionAttributesIt has a little bit to do with what we said above about the Model, which we know is actually a Map,@ModelAttributeIs used to put a key-value into the Map or get a value from the Map based on the key, but it only seems to work on the same request because@ModelAttributeThe Model is only valid for one request, and if you want to pass parameters across multiple requests, you can imagine that session can do that, and the properties that are stored in session are valid for one session, so if you have multiple requests, I can use the session to pass data, and@SessionAttributeSessionAttributes adds data to a session. The SessionAttributes annotation only applies to one class. When the request is complete, the attributes of the Model map will be stored in the session. The data in the Model will be converted from@ModelAttributeYou put it in, you take it out of the session@SessionAttributesThe specified data may be abstract, so we use two handlers to prove it:@Controller
@SessionAttributes( names={"username"})public class TestController1 {
@GetMapping( "/request1" )
public String request1 (Model model) {
model.addAttribute( "username"."lisi" );
return "index";
}
@GetMapping( "/request2" )
public String request2 (@ModelAttribute( "username" ) String username) {
System.out.println( username );
return "index"; Select * from request1; select * from request1; select * from request2; select * from request1@SessionAttributesSo when the hanlder is executed, the key/value pair in the Model with the key username is added to the session. When the second request comes in, the key/value pair in the Model will be added to the session@ModelAttributeThe value returned by the method is placed outside the Model and also taken from the session@SessionAttributesWhen the second request is executed by the handler, the Model already contains the username key pair.@SessionAtrributesIs scoped to a class, that is, only the data specified by Model in the requests in that class is put into session, and only the data defined by that class is fetched from session@SessionAttributesAs you can see, the parameters are stored in session, so they can be passed across multiple requests. Redirect cannot be used to transfer parameters. Here is an example@Controller
@SessionAttributes( names={"username"})public class TestController1 {
@GetMapping( "/request1" )
public String request1 (Model model) {
model.addAttribute( "username"."lisi" );
return "redirect:request2";
}
@GetMapping( "/request2" )
public String request2 (String username, SessionStatus sessionStatus) {
System.out.println( username );
sessionStatus.setComplete();
return "index"; }} analysis: /request1 = lisi; /request1 = lisi; /request1 = lisi; /request1 = lisi@SessionAttributesSpecifies the username will be put into the session, then redirect to/request2, username values from the model can be obtained, but we call sessionStatus. SetComplete method, the significance of this method is clear@SessionAttributesThe data saved to the session in,@SessionAttributesSo we can see in the previous source code, how does SpringMVC do thisCopy the code
@InitBinder
DataBinder
In the last article, we learned that BeanWrapper's properties are set up to rely on Java introspection propertyDescriptors while providing the ability to customize parsers. This is done by injecting the PropertyEditor implementation class into BeanWrapper with the registerCustomEditor method, which is usually a string to an object, using the PropertyEditorSupport class. Override its setAsText or getAsText method to complete the transformation. The DataBinder in this section is similar to a BeanWrapper in that everything DataBinder does is based on a BeanWrapper. Let's start with an example:public static void main(String[] args) {
Customer customer = new Customer();
DataBinder binder = new DataBinder( customer );
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add( "birth".newDate() ); binder.bind( propertyValues ); System.out.println( customer ); } If you bind a property from MutablePropertyValues to the Customer object, it is similar to BeanWrapper. Since the birth property for Customer is of type Date, it doesn't matter to bind it directly, so if I provide a string, I can't bind it. Like BeanWrapper, I can register a custom parser with the registerCustomEditor method. For example, string parsers:public static void main(String[] args) {
Customer customer = new Customer();
DataBinder binder = new DataBinder( customer );
MutablePropertyValues propertyValues = new MutablePropertyValues();
propertyValues.add( "birth"."The 2020-06-26 14:33:22" );
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); setValue( dateFormat.parse( text ) ); }}); binder.bind( propertyValues ); System.out.println( customer ); } the output is the same as above, but we do pass in the string......... This is what DataBinder does. It provides PropertyValues for an object to bind to, and it also provides a ValidatorCopy the code
The following inheritance system for WebDataBinder
The DataBinder is a data binding tool that comes with the Spring framework itself. It uses A BeanWrapper underneath which is PropertyDesciptor and PropertyEdtitor. In order to make it easier to bind data to objects in Request, a further implementation is based on DataBinder. As shown in the following figure, WebDataBinder inherits from DataBinder, and in its source code, it actually adds some judgment. There is adoThe Bind method, which is the WebDataBinder binding entry, makes some judgments and eventually calls the parent class DataBinderdoThe Bind method, is ServletRequestDataBinder and ExtendedServletRequestDataBinder back again, All these binders do is take param out of the Request object and wrap it into PropertyVlues, and then call the parent'sdoI'm not going to go into the details here, just be clear that when we get the value of getParameterValues in the HttpServletRequest object, To bound the handler is actually create a ExtendedServletRequestDataBinder, then the parameters in the handler objects, and it passed into the can complete binding, below is the pseudo code: @RequestMapping("/test" )
public String test (Customer customer) {
System.out.println( customer );
return "index"} When the binding is performed, the pseudo-code is as follows: HttpServletRequest request = getHttpServletRequest(); Customer customer = new Customer(); ServletRequestDataBinder binder = new ServletRequestDataBinder(customer); binder.bind(request);Copy the code
@InitBinder
Let's start with an example:@Controller
public class TestController1 {
@InitBinder
public void initBinder (WebDataBinder webDataBinder) {
webDataBinder.registerCustomEditor( Date.class, new PropertyEditorSupport() {
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );
try {
setValue( dateFormat.parse( text ) );
} catch(ParseException e) { e.printStackTrace(); }}}); }@GetMapping( "/request1" )
public String request1 (Date date) {
System.out.println( date );
return "index"; }} the url we requested:127.00.1 / request1? date=1997-11-23 14:00:00When the request comes in, it normally doesn't"1997-11-23 14:00:00." "Date object, but we manually registered a PropertyEditor in the WebDataBinder, and specified the type as Date, so when it binds to the Date type it will use this PropertyEditor to convert@InitBinderIt is executed before each request is executed@InitBinderCorresponding method, we can manually register their own parameter parser through this way, from the overall view, looks like with@ModelAttributeThe request is executed once before the request is executed, while@ModelAttributeWhen applied to a method, the return value is put into the Model, and@InitBinderThis allows us to initialize the WebDataBinder, both of which can be clearly seen in subsequent source code analysisCopy the code
@ControllerAdvice
In the above analysis, we mainly introduced three annotations, namely @ModelAttribute, @sessionAttributes and @initbinder. Among these three annotations, @modelAttribute attributes can apply to methods and handler method parameters. @sessionAttributes can apply to classes only and are class-scoped. Different classes cannot be shared. If you have multiple controllers, each Controller needs to perform the same @ModelAttribute and @initBinder annotations. So SpringMVC provides a global way to prevent this by creating a @ControllerAdvice annotated class and putting the annotated methods in that class. This is done globally, rather than per class, and @ControllerAdvice also provides multiple properties that allow you to flexibly configure packages, classes, etcCopy the code