Realizing spring Core Function 2 by Yourself

Keywords: Java Spring Attribute xml

Preface

Last article We talked about some features of spring and analyzed what functions need to be implemented. We have finished all the preparatory work. In this article, we begin to implement specific functions.

Container loading process

We know that refesh() method does a lot of initialization work in spring, which almost covers the core process of spring.

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //Preparations before refreshing include setting the start time, activating the identifier, and initializing the property source(property source)To configure
        prepareRefresh();
        //Refresh by subclasses BeanFactory(Create if not already created),And will BeanFactory Return
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //Get ready BeanFactory For supply ApplicationContext Use
        prepareBeanFactory(beanFactory);
        try {
            //Subclasses can be paired by formatting this method BeanFactory Modification
            postProcessBeanFactory(beanFactory);
            //Instantiate and invoke all registered BeanFactoryPostProcessor object
            invokeBeanFactoryPostProcessors(beanFactory);
            //Instantiate and invoke all registered BeanPostProcessor object
            registerBeanPostProcessors(beanFactory);
            //Initialization MessageSource
            initMessageSource();
            //Initialize Event Broadcaster
            initApplicationEventMulticaster();
            //Subclasses override this method to do extra work in the refresh process
            onRefresh();
            //Registered Application Monitor ApplicationListener
            registerListeners();
            //Instantiate all non-lazy-init bean
            finishBeanFactoryInitialization(beanFactory);
            //Refresh completes work, including initialization LifecycleProcessor,Publish refresh completion events, etc.
            finishRefresh();
        }
        catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            // Reset 'active' flag.
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }
    }
}

It's more complicated to do, and we can do something basic.

In the init method of CJDispatcher Servlet class, we implement the following business logic to initialize spring functions and use dependency injection

    @Override
    public void init(ServletConfig config) {
        //load configuration

        //Get the package address to scan

        //Scanning classes to load
    
        //Instantiate the class to load

        //Loading dependency injection to assign attributes
 
        //Load mapping address
     
    }

 

load configuration

 String contextConfigLocation = config.getInitParameter("contextConfigLocation");

        loadConfig(contextConfigLocation);

Here you get the value in the init-param node in web.xml

Specifically, it points to the application.properties configuration file under the spring file, which has only one line of configuration.

 

As you can see by configuring the key name, this specifies the package path that needs to be scanned.

Represents all classes defined in the scanned red box

The second line of code creates a loadConfig method that passes in the package path

    void loadConfig(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

Note that the yellow part of the code uses a member variable

private Properties properties = new Properties();
It's good to define the first half of the class. The purpose here is to get the configuration content in the application.properties file and load it into the properties variable for later use.

Get the package address to scan

  

 //Get the package address to scan
 String dirpath = properties.getProperty("scanner.package");

Here the directory address is read out using the key in the configuration

Scanning classes to load

   //Scanning classes to load
  doScanner(dirpath);

Scanning class we define a doScanner method to pass in the package directory address

 1     void doScanner(String dirpath) {
 2         URL url = this.getClass().getClassLoader().getResource("/" + dirpath.replaceAll("\\.", "/"));
 3         File dir = new File(url.getFile());
 4         File[] files = dir.listFiles();
 5         for (File file : files) {
 6             if (file.isDirectory()) {
 7                 doScanner(dirpath + "." + file.getName());
 8                 continue;
 9             }
10 
11             //Take the file name
12             String beanName = dirpath + "." + file.getName().replaceAll(".class", "");
13             beanNames.add(beanName);
14         }
15     }

The second line of code is escaped

The code job in this method is to read the file under the specified path, if it is a folder, it is called recursively, if it is a file, the file name and path are stored in the collection container.

Note that the yellow part of the variable is defined externally as a member variable

private List<String> beanNames = new ArrayList<>();

We add it to the first half of the class.

The resulting beanName is as follows

From this point of view, it has found out the annotations to our definition.

 

 

Instantiate the class to load

 //Instantiate the class to load
   doInstance();

We've just got a list of the names of these defined classes, and now we need to instantiate them and store them in the ioc container.

Define a container for loading classes, which you can do with HashMap. Set it as a member variable and define it in the first half of the class.

private Map<String, Object> ioc = new HashMap<>();
Next, create a method doInstance
 1  void doInstance() {
 2         if (beanNames.isEmpty()) {
 3             return;
 4         }
 5         for (String beanName : beanNames) {
 6             try {
 7                 Class cls = Class.forName(beanName);
 8                 if (cls.isAnnotationPresent(JCController.class)) {
 9                     //Instantiating objects with reflection
10                     Object instance = cls.newInstance();
11                     //Default class name in lowercase
12                     beanName = firstLowerCase(cls.getSimpleName());
13                     //Write in ioc container
14                     ioc.put(beanName, instance);
15 
16 
17                 } else if (cls.isAnnotationPresent(JCService.class)) {
18                     Object instance = cls.newInstance();
19                     JCService jcService = (JCService) cls.getAnnotation(JCService.class);
20 
21                     String alisName = jcService.value();
22                     if (null == alisName || alisName.trim().length() == 0) {
23                         beanName = cls.getSimpleName();
24                     } else {
25                         beanName = alisName;
26                     }
27                     beanName = firstLowerCase(beanName);
28                     ioc.put(beanName, instance);
29                     //If it's an interface, automatically inject its implementation class
30                     Class<?>[] interfaces = cls.getInterfaces();
31                     for (Class<?> c :
32                             interfaces) {
33                         ioc.put(firstLowerCase(c.getSimpleName()), instance);
34                     }
35                 } else {
36                     continue;
37                 }
38             } catch (ClassNotFoundException e) {
39                 e.printStackTrace();
40             } catch (IllegalAccessException e) {
41                 e.printStackTrace();
42             } catch (InstantiationException e) {
43                 e.printStackTrace();
44             }
45         }
46     }

As long as we provide a fully qualified class name, through the Class.forName static method, we can load the class information into memory and return the Class object, instantiate it by reflection, as shown in line 10.

We instantiate each class by iterating through the bean Names collection and loading the instantiated object into HashMap

Note: Line 12 lowercase the first letter of the class name into a map, which is defined as follows

1   String firstLowerCase(String str) {
2         char[] chars = str.toCharArray();
3         chars[0] += 32;
4         return String.valueOf(chars);
5     }

This line of code converts strings into char arrays, and then converts the first character of the array into capitals. This is done in a clever way. Teacher tom uses a more saucy operation.

After the instantiation is completed, the data in the ioc container is as follows:

Explain:

As you can see in the picture, hashMap's key s are all lowercase, value is already the object, see the red box.

Why do I mark the blue box here because it's a field attribute in a class, and you can see that even though the class has been instantiated, the attribute is null?

I added two interfaces and two implementation classes to test dependency injection.

The interface is defined as follows:
public interface IHomeService {
    String sayHi();
    String getName(Integer id,String no);
    String getRequestBody(Integer id, String no, GetUserInfo userInfo);
}



public interface IStudentService {
     String sayHi();
}

 

Implementation class:
@JCService
public class StudentService  implements IStudentService{
    @Override
    public String sayHi(){
        return "Hello world!";
    }
}
@JCService
public class HomeService  implements IHomeService{

    @JCAutoWrited
     StudentService studentService;
    @Override
    public String sayHi() {
      return   studentService.sayHi();
    }

    @Override
    public String getName(Integer id,String no) {
        return "SB0000"+id;
    }

    @Override
    public String getRequestBody(Integer id, String no, GetUserInfo userInfo) {
        return "userName="+userInfo.getName()+" no="+no;
    }
}

Dependent entities:

public class GetUserInfo {
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public BigDecimal getGrowthValue() {
        return growthValue;
    }

    public void setGrowthValue(BigDecimal growthValue) {
        this.growthValue = growthValue;
    }

    private String name;
    private Integer age;
    private BigDecimal growthValue;

}

 



Loading dependency injection to assign attributes

//Loading dependency injection to assign attributes
        doAutoWrited();

Now that we implement dependency injection, we need to define a nonparametric method, doAutoWrite

 

 1     void doAutoWrited() {
 2         for (Map.Entry<String, Object> obj : ioc.entrySet()) {
 3             try {
 4                 for (Field field : obj.getValue().getClass().getDeclaredFields()) {
 5                     if (!field.isAnnotationPresent(JCAutoWrited.class)) {
 6                         continue;
 7                     }
 8                     JCAutoWrited autoWrited = field.getAnnotation(JCAutoWrited.class);
 9                     String beanName = autoWrited.value();
10                     if ("".equals(beanName)) {
11                         beanName = field.getType().getSimpleName();
12                     }
13 
14                     field.setAccessible(true);
15 
16                     field.set(obj.getValue(), ioc.get(firstLowerCase(beanName)));
17                 }
18             } catch (IllegalAccessException e) {
19                 e.printStackTrace();
20             }
21 
22         }
23 
24 
25     }

This method finds the fields by reflecting the entities in the loop ioc to see if there is a tag JCAutoWrited that needs to be injected. If tagged, it reflects the values to the fields, and the types are obtained from the ioc container.

 

Load mapping addresses

    //Load mapping address
   doRequestMapping();

 

The purpose of mapping addresses is to match method methods based on the requested url

 1     void doRequestMapping() {
 2         if (ioc.isEmpty()) {
 3             return;
 4         }
 5         for (Map.Entry<String, Object> obj : ioc.entrySet()) {
 6             if (!obj.getValue().getClass().isAnnotationPresent(JCController.class)) {
 7                 continue;
 8             }
 9             Method[] methods = obj.getValue().getClass().getMethods();
10             for (Method method : methods) {
11                 if (!method.isAnnotationPresent(JCRequestMapping.class)) {
12                     continue;
13                 }
14                 String baseUrl = "";
15                 if (obj.getValue().getClass().isAnnotationPresent(JCRequestMapping.class)) {
16                     baseUrl = obj.getValue().getClass().getAnnotation(JCRequestMapping.class).value();
17                 }
18                 JCRequestMapping jcRequestMapping = method.getAnnotation(JCRequestMapping.class);
19                 if ("".equals(jcRequestMapping.value())) {
20                     continue;
21                 }
22                 String url = (baseUrl + "/" + jcRequestMapping.value()).replaceAll("/+", "/");
23                 urlMapping.put(url, method);
24                 System.out.println(url);
25             }
26         }
27     }

This is actually the value value obtained from the object reflection on the JCRequestMapping.

@JCRequestMapping("/sayHi")

What you get is / sayHi

Also note that the variable used in the yellow section is a hashMap, defined in the first half of the class

private Map<String, Method> urlMapping = new HashMap<>();

There are URLs and corresponding method objects. This will be used later when processing requests.


Ending

This is where the initialization of the container ends. A total of four containers are used to store related objects, which will be used by subsequent servlet s when processing requests.

In the next article, we will continue to improve it to verify whether the desired results can be achieved through requests. In addition, parameter binding will be implemented, which can handle all kinds of requests and respond.

Full Code Address

Posted by amob005 on Sat, 17 Aug 2019 03:58:51 -0700