Java technology lovers, we meet again! I had this question in an interview and couldn’t answer it. It’s been on my mind ever since. I believe many people have this kind of experience after the interview, haha ~ but today is different from the past, now I have figured out, in fact, it is not very difficult.
Set the ball rolling
To learn a technology, of course, it’s important to know what it solves, otherwise it’s like buying a bottle opener and trying to open a beer bottle with your mouth.
example
First we have a Request object, which represents a Request.
public class Request {
// Request data
private String data;
public String getData(a) {
return data;
}
public void setData(String data) {
this.data = data; }}Copy the code
Suppose there is a lot of data in the data, and we want to filter out some keywords, such as fruits and vegetables. Instead of using design mode, use if to solve the problem:
public static void main(String[] args) throws Exception {
// Create the request body
String data = "Meat, egg, scallion, chicken, fruit and vegetables, Thousand-layer cake, Tasteful and vulgar, Mr. Ma, floor 5.";
Request request = new Request();
request.setData(data);
String requestData = request.getData();
// If the fruit keyword is included
if (requestData.contains("Fruit")) {
// Filter fruit keywords
requestData = filterByWord(requestData, "Fruit");
}
// If include vegetable keyword
if (requestData.contains("Vegetables")) {
// Filter vegetable keywords
requestData = filterByWord(requestData, "Vegetables");
}
request.setData(requestData);
System.out.println(requestData);// Meat, egg, scallion, chicken thousand-layer cake
}
// Methods for filtering keywords
private static String filterByWord(String data, String word) {
StringBuilder sb = new StringBuilder(data);
while (true) {
int index = sb.indexOf(word);
// If not equal to -1, search for the keyword
if(index ! = -1) {
for (int i = 0; i < word.length(); i++) {
// Delete the keyword as many times as it is long. For example, delete fruit twice in index positionsb.deleteCharAt(index); }}else {
// = -1, no keyword, out of the loop
break; }}returnsb.toString(); }}Copy the code
Don’t have too much of this code in your project. If you don’t get it, do it again. Typically such programmers see only the first layer.
Question:
1. What if more keywords need to be added at this time? If you keep adding if to the original code, the method will get longer and longer. And it violates the open close principle.
2. What if you want to switch the order? Do you cut and copy code?
So a good programmer, the implementation of the function is just the basic requirements, the important thing is that the code should have good maintenance and scalability.
How do you optimize it?
One way to think about it is to split each keyword filtering method into a class and define a keyword filtering method. Since there are many classes with similar responsibilities, define an interface where common methods can be defined. Step 1: Define the filter interface
public interface Filter {
// Interface method
String doFilter(String data, FilterChain filterChain);
// Methods for filtering keywords
default String filterByWord(String data, String word) {
StringBuilder sb = new StringBuilder(data);
while (true) {
int index = sb.indexOf(word);
if(index ! = -1) {
for (int i = 0; i < word.length(); i++) {
// Delete several times. For example, fruit can be deleted twice in indexsb.deleteCharAt(index); }}else {
// If the keyword is not found, jump out of the loop
break; }}returnsb.toString(); }}Copy the code
Step 2: Define fruit keyword filters
public class FruitsFilter implements Filter {
@Override
public String doFilter(String data, FilterChain filterChain) {
// Filter fruit keywords
data = filterByWord(data, "Fruit");
// Get the controller and proceed to call the next filter
returnfilterChain.preHandleRequest(data); }}Copy the code
Step 3: Define vegetable keyword filters
public class VegetablesFilter implements Filter {
@Override
public String doFilter(String data, FilterChain filterChain) {
// Filter vegetable keywords
data = filterByWord(data, "Vegetables");
// Get the controller and proceed to call the next filter
returnfilterChain.preHandleRequest(data); }}Copy the code
Step 4: Define a filter controller
public class FilterChain {
// Use List to install the filter, List is ordered
private List<Filter> filters = new ArrayList<>();
// Filter subscript, starting at 0, +1 each time the preHandleRequest method is called
private int index = 0;
// Add a filter
public void addFilter(Filter filter) {
filters.add(filter);
}
// Call the doFilter() method of the filter and point the pointer +1 to the next filter
public String preHandleRequest(String data) {
// If the pointer is larger than the size of the collection, return is not called back
if (index == filters.size()) {
return data;
}
Filter filter = filters.get(index);
// subscript pointer +1
index++;
// The essence of this is to pass itself in as an argument to achieve recursion
return filter.doFilter(data, this); }}Copy the code
Now that we’re done, let’s try creating a Main method
public class Main {
public static void main(String[] args) throws Exception {
// Create the request body
String data = "Meat, egg, scallion, chicken, fruit and vegetables, Thousand-layer cake, Tasteful and vulgar, Mr. Ma, floor 5.";
Request request = new Request();
request.setData(data);
// Create a FilterChain
FilterChain filterChain = new FilterChain();
// Add a filter
filterChain.addFilter(new FruitsFilter());
filterChain.addFilter(new VegetablesFilter());
// Perform preprocessing
String s = filterChain.preHandleRequest(request.getData());
request.setData(s);
// Print the result to verify
System.out.println(request.getData());// The result is correct
// Meat, egg, scallion, chicken thousand-layer cake}}Copy the code
This is the chain of responsibility model! Here is a diagram to see the order in which the call chain is executed. It’s not as hard as you think. Did you learn?
Advantages of the chain of responsibility model
We look back, why so around, directly if not sweet? In the case of “if”, the problem of breaking the open/close principle and the order of calls is solved by using the chain of responsibility mode.
1. If you want to add some filtered keywords, you just need to add a filter class and then add it to the filter set, without intrusive development of the original code. Conform to theThe open closed principle
.
2. To change the order of execution, there is no need to change the code in an intrusive way, just change the order in which filters are added. inServlet
In, the filter order is pressxml
The order in which files are defined. inSpringMVC
Is in the order of registration. This is very easy to extend.
Think more about the next programmer, one for all, all for one, the world will be a better place! Ha ha ~ ~
The Chain of responsibility pattern of SpringMVC
So that’s a simple demo, but it’s important to learn the idea. So let’s see how it is applied in actual combat, the so-called know what it is, know what it is, learn to dabbler, strike while the iron is hot, let’s see how to apply the responsibility chain model in the framework.
Use of SpringMVC interceptors
The HandlerInterceptor interface has three methods that need to be overwritten.
- PreHandle () : Called before the request is processed by the business handler. Pretreatment.
- PostHandle () : executed after the business processor completes the execution of the request and before the view is generated. Post-processing.
- AfterCompletion () : in
DispatcherServlet
Called after the request has been fully processed and can be used to clean up resources, etc. Return processing (the page has already been rendered);
We define a keyword filter, WordInterceptor:
// Define a keyword interceptor
public class WordInterceptor implements HandlerInterceptor {
// before executing the request method defined by controller
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Execute the WordInterceptor preHandle()");
return true;
}
// After the request method defined by controller is executed, but before the page is rendered, it is executed
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Execute the WordInterceptor postHandle()");
}
// executes after rendering the page, or when preHandle() returns fasle
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Execute afterCompletion() for WordInterceptor"); }}Copy the code
Again, define a login interceptor
public class LoginInterceptor implements HandlerInterceptor {
// before executing the request method defined by controller
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Execute the LoginInterceptor preHandle()");
return true;
}
// After the request method defined by controller is executed, but before the page is rendered, it is executed
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Perform LoginInterceptor's postHandle()");
}
// executes after rendering the page, or when preHandle() returns fasle
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Execute afterCompletion() on LoginInterceptor"); }}Copy the code
It then registers with a collection of interceptors.
@Component
public class WebInterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// Register a keyword blocker to block all requests
registry.addInterceptor(new WordInterceptor()).addPathPatterns("/ * *");
// Register the login interceptor again to block all requests
registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/ * *"); }}Copy the code
Then start the project, make a request, and we can see the order of the calls on the console. Pay attention to:
// Execute the preHandle() method in the order of registrationExecute the WordInterceptor preHandle() execute the LoginInterceptor preHandle()// Execute the postHandle() method in reverse order of registrationExecute LoginInterceptor's postHandle() execute WordInterceptor's postHandle()// From the last preHandle() class, execute afterCompletion() in reverse orderPerform afterCompletion() on LoginInterceptorCopy the code
The flow chart for execution looks like this:
SpringMVC interceptor source code analysis
Again, look at the doDispatch() method of the DispatcherServlet
doDispatch()
Call to order
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Request object
HttpServletRequest processedRequest = request;
// Define a call chain
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
/ / to omit
// Get the call chain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Get the corresponding adapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Call the interceptor's preHandle() method
if(! mappedHandler.applyPreHandle(processedRequest, response)) {// If fasle is returned, the inverse is true
return;
}
// Call the Controller's RequestMapping method
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Call the interceptor's postHandle() post-processing method
mappedHandler.applyPostHandle(processedRequest, response, mv);
/ / to omit
}catch (Exception ex) {
// If an exception occurs, the triggerAfterCompletion() method will still be executed
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}catch (Throwable err) {
// If an exception occurs, the triggerAfterCompletion() method will still be executed
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
/ / to omit}}Copy the code
The sequence of interceptor interfaces is obvious from the source code above: ApplyPreHandle () to handle () – > applyPostHandle () – > triggerAfterCompletion () or applyPreHandle () – > triggerAfterCompletion ()
The key lies inHandlerExecutionChain
interface
All of the above methods are for this interface except handle(), which is defined in the adapter. The handle() method was covered in my previous article, adapter patterns and SpringMV. Now let’s look at the HandlerExecutionChain interface.
public class HandlerExecutionChain {
/ / to omit
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable// Interceptor collection
private List<HandlerInterceptor> interceptorList;
// pointer to which interceptor the applyPreHandle() method executes to
private int interceptorIndex = -1;
}
Copy the code
Isn’t that similar to our demo? Defines a collection wrapper interceptor that defines a pointer traversal collection.
ApplyPreHandle () method
How does the prehandle method applyPreHandle() perform?
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
// Get interceptor
HandlerInterceptor[] interceptors = getInterceptors();
// Determine not null
if(! ObjectUtils.isEmpty(interceptors)) {// Iterate sequentially, so preHandle() defined by the interceptor interface is executed sequentially
for (int i = 0; i < interceptors.length; i++) {
// Get the registered interceptors in order
HandlerInterceptor interceptor = interceptors[i];
// Executes the interceptor's preHandle() method
if(! interceptor.preHandle(request, response,this.handler)) {
// If preHandle() returns false, the triggerAfterCompletion() method is called
triggerAfterCompletion(request, response, null);
// Return false to end the call
return false;
}
// If preHandle() returns true, continue execution
// Record the subscript index in the pointer to the member variable for later execution of the triggerAfterCompletion() method
this.interceptorIndex = i; }}return true;
}
Copy the code
ApplyPostHandle () method
And then what about the next applyPostHandle() method?
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if(! ObjectUtils.isEmpty(interceptors)) {// iterate backwards, so postHandle() is executed from the last interceptor
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
//postHandle() can retrieve the ModelAndView object, and the interceptor can postprocess the MV object
interceptor.postHandle(request, response, this.handler, mv); }}}Copy the code
TriggerAfterCompletion () method
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
HandlerInterceptor[] interceptors = getInterceptors();
if(! ObjectUtils.isEmpty(interceptors)) {// From the member variable interceptorIndex record of the pointer value, start traversal in reverse order
for (int i = this.interceptorIndex; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
}
catch (Throwable ex2) {
logger.error("HandlerInterceptor.afterCompletion threw exception", ex2); }}}}Copy the code
Interceptor collectioninterceptorList
How to assemble
We’ve all seen that the above three methods have a common method, getInterceptors().
@Nullable
public HandlerInterceptor[] getInterceptors() {
if (this.interceptors == null && this.interceptorList ! =null) {
this.interceptors = this.interceptorList.toArray(new HandlerInterceptor[0]);
}
return this.interceptors;
}
Copy the code
The key is where does the interceptorList add() to it? AbstractHandlerMapping: AbstracthandlerExecutionChain ()
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// Determine whether the interceptor parent class is inherited
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
// Add interceptorchain.addInterceptor(mappedInterceptor.getInterceptor()); }}else {
// Add interceptorchain.addInterceptor(interceptor); }}// Return the call chain
return chain;
}
Copy the code
When is this method called? We go all the way up to the caller. The first is the AbstractHandlerMapping getHandler() method
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// Get the call chain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
/ / to omit
return executionChain;
}
Copy the code
Then we go to the getHandler() method of the DispatcherServlet
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings ! =null) {
for (HandlerMapping hm : this.handlerMappings) {
/ / to omit
// Get the call chain
HandlerExecutionChain handler = hm.getHandler(request);
if(handler ! =null) {
// Return if it is not null
returnhandler; }}}return null;
}
Copy the code
Finally, we return to the doDispatch() method of the DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// Get call chain method!!
mappedHandler = getHandler(processedRequest);
/ / to omit}}}}Copy the code
Ha ha ~~ the truth is out! The doDispatch() method gets the interceptorList collection from the getHandler() method.
SpringMVC interceptor execution flow diagram
To summarize, this is it, and I’ve marked it in different colors step by step from light to dark (thumbs up) :
A few more words at the end
In addition to SpringMVC, there are many frameworks that use the chain of responsibility pattern, such as Servlet Filter, Struts2 Interceptor, and so on. Interested students can go to see the source code, in fact, are more or less the same, after understanding the idea, the source code looks less laborious.
If you want to see my updated article for the first time, you can search the public account “Java Technology lover” on wechat. I refuse to be a salty fish. I am a programmer living in the wilderness of the Internet. See you next time!!
Ability is limited, if there is any mistake or improper place, please criticize and correct, study together!
It is easy to learn. Do not learn, then easy is difficult.