[summary] implementation summary of self-developed Spring framework

Posted by srihari3d2010 on Sat, 05 Feb 2022 09:59:10 +0100

Instance creation

1. Create annotations @ Controller, @ Service, @ Repository, @ Component

2. Extract tag object

Realization idea

◆ specify the range and obtain all classes within the range
◆ traverse all classes, get the annotated classes and load them into the container

How to get the class collection according to the package name?

Create a method extractPacakgeClass, use the class loader to obtain the resource url, filter out the resource of file type according to the protocol name, traverse the directory to obtain the class file, and then use reflection to obtain the corresponding class object and add it to the collection.

What needs to be done in extractPacakgeClass

◆ get the class loader
◆ obtain the loaded resource information through the class loader
◆ obtain the collection of resources in different ways according to different resource types

Implementation and loading of Bean container

Reading Guide:

Because the same container is needed to manage all the objects that need to be managed, the container needs to be implemented with a singleton.

Ensure that there is only one instance of a class and provide unified access to the outside world. The client does not need and cannot instantiate the object of this class.

realization:

To construct a container, we use thread safe enumerated singleton mode that can resist reflection and serialization.

Components of container

◆ a carrier that holds Class objects and their instances
◆ loading of containers (it is necessary to define the method of acquiring and filtering the target object by configuration)

◆ operation mode of container

We use concurrentHashMap as a container to store class objects and instances.

concurrentHashMap is used because it supports concurrency and has good concurrency performance. In jdk1 8 has made great changes, abandoned the segmented lock, and refined the lock granularity by using CAS + red black tree to further improve the concurrency performance.

Not all class objects are container managed objects, but the class objects specified in the configuration, that is, the objects marked with annotations, are selected from them before they are stored in the carrier and managed.

Loading of containers:

Realization idea

◆ management and acquisition of configuration (how to manage annotations, read the target annotation at any time, and get the class marked by it)
◆ obtain Class objects within the specified range

◆ extract the Class object according to the configuration, and store it together with instance 1 - in the container

Specific implementation:

1. First judge whether the container has been loaded according to the flag.

2. First get all the class collections according to the package name.

3. After traversing the class collection, add the annotated classes that need to be managed by spring to the map container with the class itself as the key and the class instance as v.

4. Set the load flag to true.

Operation mode of container

Implement the operation mode of the container
Adding, deleting, modifying and querying containers

◆ add and delete operations (add and delete the map set)
◆ obtain the annotated Class (i.e. all key s) through annotation

clazz.isAnnotationPresent(annotation)

◆ get the corresponding instance according to Class (get v through key)

◆ obtain the corresponding subclass Class through the superclass (whether to break the subclass of the parameter and remove itself)

interfaceOrClass.isAssignableFrom(clazz) && !clazz.equals(interfaceOrClass)
    
A.isAssignableFrom(B); //To judge whether A is the parent class of B, you can also see whether AB and ab are the same or have inheritance relationship, which is not limited to parent-child classes, implementation classes, but also grandparents and grandchildren 
Parent class.isAssignableFrom(Subclass);
Interface.isAssignableFrom(Implementation class);

◆ get all Bean instance collections

beanMap.values();

◆ obtain the number of classes stored in the container carrier (size of map)

Summarize the implementation of IoC container:

First, the annotation labels (@ Component, @ Controller, @ Service, @ Repository) are defined to save the class object and object related instances marked by the above annotation under the specified packageName in the form of key value pairs to the member variable beanMap of the ConcurrentHashMap type of the container, so as to realize the initialization of the container. In addition, in order to ensure the uniqueness of container instances, a secure singleton against reflection and serialization is realized by enumerating. It can be seen from here that the beans managed by our framework are single instance and loaded without delay (mainly because the implementation is simple and can meet most business requirements).

Dependency injection

Understand the definition and use of Autowired:

@Target(ElementType.FIELD) //Only supported on member variables
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String value() default "";
}
@Autowired(value = "HeadLineServiceImpl")//When using, specify the implementation class to be injected into the member variable
private HeadLineService headLineService;

IoC logic:

1. Traverse all Class objects in the Bean container

2. Traverse all member variables of the Class object

Field[] fields = clazz.getDeclaredFields();

3. Find the member variable marked by annotated Autowired (isAnnotationPresent)

4. Get the types of attributes and member variables in the annotation

5. Obtain the corresponding instance or implementation class in the container according to the type of member variable

//Get its instance or implementation Class in beanContainer according to Class
//If the corresponding instance in the container is found directly according to the incoming class object, it indicates that the incoming class is. If the corresponding instance is not obtained according to the class in the container, there are two situations: 1. The interface is passed in, and the implementation class is stored in the bean. 2. There is no instance

Object fieldValue=getFileInstance(fieldClass,autowiredValue);
//At this time, you need to define a method to obtain the implementation class according to the interface (directly call the operation of the above implementation container: obtain the class set of the class or subclass through the interface or parent class). After obtaining the set, if the set length is greater than 1 and the annotation attribute is empty, an error will be reported if the implementation class name is not specified. If specified, go through the class collection, find the corresponding class and return.

6. Inject the corresponding member variable instance into the instance of the class where the member variable is located through reflection

field.set(targetBean,value);

summary

First, define the @Autowired annotation, implement the logic injected by the annotation dependency, and then call the doIoC method to deal with the attributes marked by @Autowired in the bean instances that have been loaded. For these attributes, the getFieldInstance method is used to get these attributes to face the bean instances in the bean container, and also to support the corresponding implementation classes according to the interface. Finally, the obtained instance is injected into the instance of the class where the member variable is located through reflection.

Implementation of AOP

Because CGLIB dynamic proxy does not require the proxy class to implement relatively flexible interfaces, we use CGLIB to implement spring AOP

Idea:

◆ solve the problem of marking (identify Aspect and Advice) and define the skeleton of crosscutting logic

◆ define the Aspect crosscutting logic and the execution order of the proxy method

◆ weave crosscutting logic into the proxied object to generate dynamic proxy object

Prerequisite preparation:

1. Define the annotation aspect (whose attribute is pointcut expression) and Order, which are used to mark the aspect class and the sequence executed by the aspect class

2. Set the notification Advice (pre notification, notification of correctly returned results, exception notification) as a method and encapsulate it in an abstract class

3. Define a class to parse the Aspect expression and locate the woven target

Using AspectJ's section expression and related location analysis mechanism

Pointcut parser

Assign him all expressions of Aspectj directly to support the parsing of many expressions

The PointcutParser instance needs to be created and assembled with the relevant syntax tree to recognize the pointcut expression in the annotated Aspect attribute

private PointcutParser pointcutParser=PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(
      PointcutParser.getAllSupportedPointcutPrimitives()
);

expression parser

PointcutExpression: it is the product parsed by PointcutParser according to the expression. It is used to judge whether a class or method matches the pointcut expression.

public PointcutLocator(String expression){
//In the constructor, the facet expression parsing mechanism of AspectJ is used to assign values to member variables
  this.pointcutExpression=pointcutParser.parsePointcutExpression(expression);
}

location

Coarse screening and fine screening are carried out according to the positioning analysis mechanism of AspectJ

//Coarse screening: judge whether the incoming Class object is the target proxy Class of Aspect, that is, match the PointCut expression (preliminary screening)
pointcutExpression.couldMatchJoinPointsInType(targetClass);

//Fine screening: determine whether the incoming Method object is the target proxy Method of Aspect and match the Pointcut expression (fine screening)
//Exact matches: alwaysMatches
pointcutExpression.matchesMethodExecution(method).alwaysMatches();

4. Encapsulate the execution order of facet classes, notifying abstract classes, parsing expressions and locating classes in one class (convenient for management and operation)

5. Define a class to implement the MethodInterceptor interface (to define the crosscutting logic Aspect)

This class is used to intercept the methods of each proxied object

5.1. First arrange the section class set in ascending order according to the order attribute to ensure that the section class with small order value is woven in first.

5.2 rewrite the intercept method in the MthodInterceptor interface

intercept crosscutting logic execution:

1. First, fine screen the sorted section class set after preliminary screening, and remove the unqualified classes from the section class set

2. If the facet class collection becomes empty, only its own methods are executed

returnValue=methodProxy.invokeSuper(proxy,args);

3. Execute all before methods of Aspect in ascending order of order

invokeBeforeAdvices(method,args);

4. Execute the method of the proxied class

returnValue=methodProxy.invokeSuper(proxy,args);

5. If the proxy method returns normally, all after methods of Aspect are executed in descending order of order

returnValue=invokeAfterReturningAdvices(method,args,returnValue);

6. If the proxy method throws an exception, it will be executed in descending order according to the order

invokeAfterThrowingAdvices(method,args,e);

Because the facet class collection is originally sorted in ascending order according to order, the above descending order is directly executed, and the collection can be traversed from back to front.

Create proxy class

Using Enhancer of CGLIB to create proxy class

public static Object createProxy(Class<?> targetClass, MethodInterceptor methodInterceptor){
    return  Enhancer.create(targetClass,methodInterceptor);
}

Crosscutting logic weaving

doAOP

1. Get all the facet class collections in the container according to the annotation

2. Encapsulating AspectInfo classes

Encapsulate the Order value of the Order attribute, notify the abstract class object instance, parse the Apect expression and locate the object instance woven into the target class into the AspectInfo class.

3. Traverse the classes in the container

4. Qualified sections of coarse screen

5. Weave the Apect crosscutting logic (create a dynamic proxy object for execution)

private void wrapIfNecessary(List<AspectInfo> roughMatchedAspectList, Class<?> targetClass) {
    if(ValidationUtil.isEmpty(roughMatchedAspectList)){return;}
    //Create a dynamic proxy object
    AspectListExecutor aspectListExecutor=new AspectListExecutor(targetClass,roughMatchedAspectList);
    Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor);
    beanContainer.addBean(targetClass,proxyBean);
}

code

public void doAop(){
    //1. Get all facet classes
    Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
    if(ValidationUtil.isEmpty(aspectSet)){return;}//Air judgment processing
    //2. Splice AspectInfoList
    List<AspectInfo> aspectInfoList=packAspectInfoList(aspectSet);
    //3. Traverse the classes in the container
    Set<Class<?>> classSet = beanContainer.getClasses();
    for (Class<?> targetClass : classSet) {
        //Exclude AspectClass itself
        if(targetClass.isAnnotationPresent(Aspect.class)){
            continue;
        }
        //4. Coarse screen qualified Aspect
        List<AspectInfo> roughMatchedAspectList=collectRoughMatchedAspectListForSpecificClass(aspectInfoList,targetClass);
        //5. Try weaving Aspect
        wrapIfNecessary(roughMatchedAspectList,targetClass);
    }

MVC implementation

General process

obtain http Requests and requests to be sent back http Response objects, and then delegate them to RequestProcessorChain handle. We only deal with get and post Method request, RequestProcessorChain It refers to the processing logic of the post processor of the responsibility chain mode, which stores the processing logic RequestProcessor The reason why there are multiple different implementation classes corresponding to DispatcherServlet It is the only entry for all requests in the project. There will be access in these requests jsp For the request of the page, there will also be the request to obtain static resources and direct acquisition json Data requests, etc. different methods will be used for different requests RequestProcessor To deal with.

Dispatcher servlet implementation

Inherit HttpServlet and rewrite init and service methods

init() initialization

servlet is the entrance of program execution: initialize the container and load the relevant bean s, and complete the weaving of AOP related logic and IoC dependency injection. At the same time, in order to implement the RequestProcessor matrix in the responsibility chain mode later, the corresponding processor needs to be added to the processor list.

Add the request processor according to PreRequestProcessor, StaticResourceRequestProcessor, JspRequestProcessor and ControllerRequestProcessor. Because our request is processed after coding and path processing. Put the ControllerRequestProcessor to the last, because its processing is time-consuming and needs to match the request with the method instance of the controller.

service method implementation

Detailed explanation of responsibility chain mode (responsibility chain mode)

1. Create an instance of the responsibility chain object

RequestProcessorChain requestProcessorChain = new RequestProcessorChain(PROCESSOR.iterator(), req, resp);

2. Call the request processor to process the request in turn through the responsibility chain mode

requestProcessorChain.doRequestProcessorChain();

2.1. Traverse the registered request processor implementation class list through the iterator

2.2 until a request processor returns false after execution

2.3. If an exception occurs during, it will be handled by the internal exception renderer

3. Render the processing results

3.1 if the request processor implementation class is to select the appropriate renderer, the default is used

3.2, call the render method of the renderer to render the results

Implementation of ControllerRequestProcessor

Controller request processor

Function:

◆ select the matching Controller method for specific requests
◆ analyze the parameters in the request and their corresponding values, and assign them to the parameters of the Controller method
◆ select an appropriate Render to prepare for the rendering of subsequent request processing results

Prerequisite preparation:

Encapsulate the request method and request path into the RequestPathInfo Class. Encapsulate the Class object corresponding to the Controller, the executed Controller method instance, the method parameter name and the corresponding parameter type into the ControllerMethod Class. The two form a mapping table in the form of KV.

//Mapping set of request and controller methods
private Map<RequestPathInfo, ControllerMethod> pathControllerMethodMap = new ConcurrentHashMap<>();
realization:

1. Rely on the ability of the container to establish the mapping of request path, request method and Controller method instance

1. Traverse all classes modified by @ RequestMapping

2. Get the annotation attribute, that is, the first level path

3. Get the array of all methods in the class through reflection, and traverse to get the method modified by @ RequestMapping

Method[] methods = requestMappingClass.getDeclaredMethods();

4. Get the annotation attribute, that is, the secondary path. The url is obtained by splicing the primary path and the secondary path

5. Resolve the parameters marked by @ RequestParam in the method, and save the annotation attribute, method parameter name and parameter type in the form of kv in the map.

(in order to simply specify the method marked with @ RequestMapping annotation, if there are parameters, they must be marked with @ RequestParam annotation)

6. Encapsulate the obtained information into the mapping table

Rewrite the process method in the RequestProcessor of the request executor

1. Parse the request method and request path of HttpServletRequest, encapsulate it as RequestPathInfo, and then get the corresponding ControllerMethod in the mapping table

2. Parse the request parameters and pass them to the obtained controllerMethod instance for execution

Object result = invokeControllerMethod(controllerMethod, requestProcessorChain.getRequest());

1) Get the parameter name of get or post and its corresponding value from the request

2) Get the mapping relationship between the parameters and parameters in the controllerMethod, traverse the collection, and convert the parameter value in the request path to the type required by the parameters in the controller method. Finally, add the converted value to the method parameter set.

3) Use the reflection invoke to execute the corresponding method in the controller and return the result

3. According to the processing results, select the corresponding render for rendering

(set different renderers according to different situations)

Judge whether the method is modified by the annotation ResponseBody (isAnnotationPresent). It is set to JsonResultRender, not ViewResultRender.

Topics: Java Spring Back-end