Last article said handwritten Spring AOP, to enhance the function, the following content is mainly handwritten Spring Config. Use Spring by configuration

Links to previous content:

I smile to the sky from the horizontal knife, writing Spring IOC container, come and Look!

Handwriting Spring DI dependency injection, hey, your benefit!

Handwriting Spring AOP, come and have a look have a look!


Configuration analysis

Why provide the configuration method? In the previous content, we tested the code to carry out:

GeneralBeanDefinition bd = new GeneralBeanDefinition();
bd.setBeanClass(Lad.class);
List<Object> args = new ArrayList<>();
args.add("sunwukong");
args.add(new BeanReference("magicGril"));
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("swk", bd);

bd = new GeneralBeanDefinition();
bd.setBeanClass(MagicGril.class);
args = new ArrayList<>();
args.add("baigujing");
bd.setConstructorArgumentValues(args);
bf.registerBeanDefinition("magicGril", bd);
Copy the code

Let’s take a look at what the configuration looks like in normal use:

<? xml version="1.0" encoding="UTF-8"? > <beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="girl" class="di.MagicGirl"
     init-method="start" destroy-method="end">
        <constructor-arg type="java.lang.String" value="girl"></constructor-arg>
        <property name="friend" ref="boy"></property>
    </bean>

    <bean id="boy" class="di.Lad">
        <constructor-arg type="java.lang.String" value="boy"></constructor-arg>
        <constructor-arg type="di.MagicGirl" value="girl"></constructor-arg>
    </bean>
</beans>
Copy the code

You can see the advantages of the way configuration is provided:

  • Practical and simple, more flexible change
  • And you don’t have to change the code

Common configurations, in the form of XML and annotations, work as follows:


The working process of the configuration

Define XML tags and annotations

What XML tags and annotations need to be defined? As you can see from the previous content, what is configured is the Bean definition information, so what is configured is what needs to be configured

Let’s first look at what’s in the Bean definition interface:

The XML configuration approach begins by defining a DTD or XSD document that defines a set of tag information to specify Bean definitions

<bean id="girl" class="di.MagicGirl"
     init-method="start" destroy-method="end">
        <constructor-arg type="java.lang.String" value="girl"></constructor-arg>
        <property name="friend" ref="boy"></property>
</bean>
Copy the code

As you can see, the configuration of the bean specifies information in the bean definition interface

We need to define a set of annotations, so which annotations are required, which are also the contents of the Bean definition interface:

  • Specifying the class, specifying the BeanName, specifying the scope, specifying the factory method, specifying the factory Bean, specifying the init Method, and specifying the destroy Method are all done with @Component when we use Spring
  • Specify dependencies for construct parameters: @autowired, @qualifier
  • Specify the attribute dependency: @value

Resolution of Bean configuration

Parsing Bean configurations requires a separate interface, not in the BeanFactory. To achieve the single responsibility principle, you need to define a separate interface to parse Bean configurations, and then register Bean definitions with the BeanFactory

ApplicationContext interface

The ApplicationContext interface is used to perform Bean configuration parsing. As mentioned above, there are XML and annotations to implement the configuration, so there are two implementation classes to implement the ApplicationContext interface

  1. XML implementation:
  • There can be multiple XML files, so list is used here
  • Tasks to complete: load XML, parse XML, create Bean definitions, register Bean definitions
  1. Annotation mode implementation
  • There will also be multiple packages to scan, and list is also used here
  • You need to do: scan packages, get annotations, create Bean definitions, register Bean definitions

BeanFactory and BeanDefinitionRegistry interfaces are used because Bean definitions need to be created and registered. This code will repeat if implemented separately in subclasses, so abstracted in parent classes:

What interfaces and classes do users need to know about using?

  1. Specify configuration related: XML, annotations
  2. Get the bean-related: BeanFactory

You can use the appearance mode to let the user know only about ApplicationContext and its subclasses. ApplicationContext can inherit BeanFactory and then join the two interfaces together:

ApplicationContext interface:

/ * * *@className: ApplicationContext * is used to build the interface for the entire application environment, which is used to configure and parse beans * 1: To reduce user dependence on the framework class interface, the BeanFactory interface has been extended so that Bean configuration and Bean retrieval can be done through the ApplicationContext interface * 2: The way to configure resources is XML and annotations, so there are XML and annotations of two sub-classes * 3. Bean configuration parsing first need to load, so the realization of the configuration Resource Resource loading interface ResourceLoader *@author: TR
 */
public interface ApplicationContext extends ResourceLoader.BeanFactory {}Copy the code

An abstract class implementation of ApplicationContext

/ * * *@className: AbstractApplicationContext
 * @descriptionAbstract class implementation of ApplicationContext *@author: TR
 */
public abstract class AbstractApplicationContext implements ApplicationContext {

    /** Hold the BeanFactory interface as a combination of methods */
    protected BeanFactory beanFactory;

    public AbstractApplicationContext(a) {
        super(a);this.beanFactory = new PreBuildBeanFactory();
    }

    public AbstractApplicationContext(BeanFactory beanFactory) {
        super(a);this.beanFactory = beanFactory;
    }

    @Override
    public Object getBean(String beanName) throws Exception {
        return this.beanFactory.getBean(beanName);
    }

    @Override
    public void registerBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
        this.beanFactory.registerBeanPostProcessor(beanPostProcessor); }}Copy the code

ApplicationContext implementation class configured in XML

/ * * *@className: XmlApplicationContext
 * @descriptionApplicationContext implementation class * for XML configuration@author: TR
 */
public class XmlApplicationContext extends AbstractApplicationContext {}Copy the code

Annotate the ApplicationContext implementation class for configuration

/ * * *@className: AnnotationApplicationContext
 * @descriptionThe ApplicationContext implementation class * annotates the configuration@author: TR
 */
public class AnnotationApplicationContext extends AbstractApplicationContext {}Copy the code

Implementation of configuration

XML way

Processing of XML file sources

XML configuration files can come from many sources, such as:

XML files from different sources are loaded in different ways, but in the process of parsing, they all want to get an InputStream

There is also a need to design a set of interfaces to handle XML files from different sources separately

InputStreamSource interface

/ * * *@className: InputStreamSource
 * @description: final unified interface of configuration mode *@author: TR
 */
public interface InputStreamSource {

    /** * the final input stream is *@return: java.io.InputStream
     **/
    InputStream getInputStream(a) throws IOException;
}

Copy the code

The Resource interface

/ * * *@className: Resource
 * @description: Resource extension interface for the input stream *@author: TR
 */
public interface Resource extends InputStreamSource {

    // XML configuration file in classpath form
    String CLASS_PATH_PREFIX = "classpath:";

    // XML configuration file in the form of a system file
    String File_SYSTEM_PREFIX = "file:";

    /** * Check whether the resource exists *@return: boolean
     **/
    boolean exists(a);

    /** * is readable *@return: boolean
     **/
    boolean isReadable(a);

    /** * Whether to open *@return: boolean
     **/
    boolean isOpen(a);
    
    /** * Get the resource file *@return: java.io.File
     **/
    File getFile(a);
}
Copy the code

An implementation class for the InputStreamSource interface

FileSystemResource implementation class:

/ * * *@className: FileSystemResource
 * @description: resource implementation class * of system file type@author: TR
 */
public class FileSystemResource implements Resource {

    /** File resource object */
    private File file;

    public FileSystemResource(String fileName) {
        super(a);this.file = new File(fileName);
    }

    public FileSystemResource(File file) {
        super(a);this.file = file;
    }

    @Override
    public boolean exists(a) {
        return this.file == null ? false : this.file.exists();
    }

    @Override
    public boolean isReadable(a) {
        return this.file == null ? false : this.file.canRead();
    }

    @Override
    public boolean isOpen(a) {
        return false;
    }

    @Override
    public File getFile(a) {
        return file;
    }

    @Override
    public InputStream getInputStream(a) throws IOException {
        return new FileInputStream(this.file); }}Copy the code

ClassPathResource implementation class:

/ * * *@className: ClassPathResource
 * @description: Resource implementation class in the form of CLASspath@author: TR
 */
public class ClassPathResource implements Resource {

    // Classpath required information
    private String path;

    privateClass<? > clazz;private ClassLoader classLoader;

    public ClassPathResource(String path) {
        this(path, null );
    }

    public ClassPathResource(String path, Class
        clazz) {
        this(path, clazz, null);
    }

    public ClassPathResource(String path, Class
        clazz, ClassLoader classLoader) {
        super(a);this.path = path;
        this.clazz = clazz;
        this.classLoader = classLoader;
    }

    public String getPath(a) {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    publicClass<? > getClazz() {return clazz;
    }

    public void setClazz(Class
        clazz) {
        this.clazz = clazz;
    }

    public ClassLoader getClassLoader(a) {
        return classLoader;
    }

    public void setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public boolean exists(a) {
        if (StringUtils.isNotBlank(path)) {
            if (this.clazz ! =null) {
                return this.clazz.getResource(path) ! =null;
            }
            if (this.classLoader ! =null) {
                return this.classLoader.getResource(path.startsWith("/")? path.substring(1) : path) ! =null;
            }
            return this.getClass().getResource(path) ! =null;
        }
        return false;
    }

    @Override
    public boolean isReadable(a) {
        return exists();
    }

    @Override
    public boolean isOpen(a) {
        return false;
    }

    @Override
    public File getFile(a) {
        return null;
    }

    @Override
    public InputStream getInputStream(a) throws IOException {
        if (StringUtils.isNotBlank(path)) {
            if (this.clazz ! =null) {
                return this.clazz.getResourceAsStream(path);
            }
            if (this.classLoader ! =null) {
                return this.classLoader.getResourceAsStream(path.startsWith("/")? path.substring(1) : path);
            }
            return this.getClass().getResourceAsStream(path);
        }
        return null; }}Copy the code

UrlResource implementation class:

/ * * *@className: UrlResource
 * @description: resource implementation class * in the form of URL@author: TR
 */
public class UrlResource implements Resource {

    /** The url's resource object */
    private URL url;

    public UrlResource(String url) throws IOException {
        this.url = new URL(url);
    }

    public UrlResource(URL url) {
        super(a);this.url = url;
    }

    public URL getUrl(a) {
        return url;
    }

    public void setUrl(URL url) {
        this.url = url;
    }

    @Override
    public boolean exists(a) {
        return this.url ! =null;
    }

    @Override
    public boolean isReadable(a) {
        return exists();
    }

    @Override
    public boolean isOpen(a) {
        return false;
    }

    @Override
    public File getFile(a) {
        return null;
    }

    @Override
    public InputStream getInputStream(a) throws IOException {
        return null; }}Copy the code

XML resource loader

When a user gives a resource, it is a string with three resources on it, so who is responsible for creating those resources

You need to define a ResourceLoader to distinguish between different resources and load them. This is done by the ApplicationContext, so the ApplicationContext needs to inherit the ResourceLoader interface

ResourceLoader interface:

/ * * *@className: ResourceLoader * Configure the Resource loading interface * Different configuration methods, the loading process is not the same, so you need to abstract out an interface to cope with the change of the part * although the loading method is not the same, but the returned resources are the same, are Resource *@author: TR
 */
public interface ResourceLoader {

    /** * Load resources *@param location:
     * @return: demo.context.Resource
     **/
    Resource getResource(String location) throws IOException;
}
Copy the code

Here, we also need to distinguish which resource the user-given string represents, so we need to define rules for strings:

Annotation way

How it was scanned

What are the packages scanned?

You need to go to the specified package directory to find all the class files, including the descendants of the package

The matching behavior of a resource path needs to be defined

Scan results

Once you have scanned the class file in the package, you need the class name and the class file to scan, just use the FileResource above

Scan class ClassPathBeanDefinitionScanner

/ * * *@className: ClassPathBeanDefinitionScanner
 * @description: Scans class files *@author: TR
 */
public class ClassPathBeanDefinitionScanner {

    private static Log logger = LogFactory.getLog(ClassPathBeanDefinitionScanner.class);

    private BeanDefinitionRegistry registry;

    private BeanDefinitionReader reader;

    private PathMatcher pathMatcher = new AntPathMatcher();

    private String resourcePatter = "**/*.class";

    public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(a);this.registry = registry;
        this.reader = new AnnotationBeanDefinitionReader(registry);
    }

    /** * How to scan packets *@param basePackages:
     * @return: void
     **/
    public void scan(String... basePackages) throws Throwable {
        if(basePackages ! =null && basePackages.length > 0) {
            for (String b : basePackages) {
                this.reader.loadBeanDefintions(doScan(b)); }}}/** * convert the scanned class to Resource *@param basePackage:
     * @return: demo.context.Resource[]
     **/
    private Resource[] doScan(String basePackage) throws IOException {
        // Scan the classes under the package
        // Construct the initial matching pattern string, = the package string + / + **/*.class, replace the inside. As /
        String pathPattern = StringUtils.replace(basePackage, "."."/") + "/" + this.resourcePatter;
        if (pathPattern.charAt(0) != '/') {
            pathPattern = "/" + pathPattern;
        }
        // Find the root package path of the schema
        String rootPath = this.determineRootDir(pathPattern);
        // Get the absolute path pattern that matches the file name
        String fullPattern = this.getClass().getResource("/").toString() + pathPattern;
        // Get the directory corresponding to the root package according to the root package understanding
        File rootDir = new File(this.getClass().getResource(rootPath).toString());
        // Hold the resource collection of the found class files
        Set<Resource> scanedClassFileResources = new HashSet<>();
        // Call doRetrieveMatchingFiles to scan the class files
        this.doRetrieveMatchingFiles(fullPattern, rootDir, scanedClassFileResources);
        return (Resource[]) scanedClassFileResources.toArray();
    }

    private String determineRootDir(String location) {
        int rootDirEnd = location.length();
        rootDirEnd = location.indexOf(The '*');
        int zi = location.indexOf('? ');
        if(zi ! = -1 && zi < rootDirEnd) {
            rootDirEnd = location.lastIndexOf('/', zi);
        }
        if(rootDirEnd ! = -1) {
            return location.substring(0, rootDirEnd);
        } else {
            returnlocation; }}/** * recursively find all classes in the specified directory, and add matching patterns to the result. * *@param fullPattern
     * @param dir
     * @param result
     * @throws IOException
     */
    protected void doRetrieveMatchingFiles(String fullPattern, File dir, Set<Resource> result) throws IOException {
        if (logger.isTraceEnabled()) {
            logger.trace("Searching directory [" + dir.getAbsolutePath() + "] for files matching pattern ["
                    + fullPattern + "]");
        }
        for (File content : listDirectory(dir)) {
            String currPath = StringUtils.replace(content.getAbsolutePath(), File.separator, "/");
            if (content.isDirectory() && getPathMatcher().matchStart(fullPattern, currPath + "/")) {
                if(! content.canRead()) {if (logger.isDebugEnabled()) {
                        logger.debug("Skipping subdirectory [" + dir.getAbsolutePath()
                                + "] because the application is not allowed to read the directory"); }}else{ doRetrieveMatchingFiles(fullPattern, content, result); }}if (getPathMatcher().match(fullPattern, currPath)) {
                result.add(newFileSystemResource(content)); }}}protected File[] listDirectory(File dir) {
        File[] files = dir.listFiles();
        if (files == null) {
            if (logger.isInfoEnabled()) {
                logger.info("Could not retrieve contents of directory [" + dir.getAbsolutePath() + "]");
            }
            return new File[0];
        }
        Arrays.sort(files, Comparator.comparing(File::getName));
        return files;
    }

    public BeanDefinitionRegistry getRegistry(a) {
        return registry;
    }

    public void setRegistry(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    public BeanDefinitionReader getReader(a) {
        return reader;
    }

    public void setReader(BeanDefinitionReader reader) {
        this.reader = reader;
    }

    public PathMatcher getPathMatcher(a) {
        return pathMatcher;
    }

    public void setPathMatcher(PathMatcher pathMatcher) {
        this.pathMatcher = pathMatcher;
    }

    public String getResourcePatter(a) {
        return resourcePatter;
    }

    public void setResourcePatter(String resourcePatter) {
        this.resourcePatter = resourcePatter; }}Copy the code

Parse into Bean definitions

The final output of both the XML and annotations is Resource, which in this case also needs to be parsed into Bean definition information

Interfaces need to be defined for parsing:

BeanDefinitionReader interface:

/ * * *@className: BeanDefinitionReader
 * @description: parses the Resource Resource into the bean-defined interface *@author: TR
 */
public interface BeanDefinitionReader {

    /** * Parse a single resource *@param resource:
     * @return: void
     **/
    void loadBeanDefintions(Resource resource) throws Throwable;

    /** * Parse multiple resources *@param resource:
     * @return: void
     **/
    void loadBeanDefintions(Resource... resource) throws Throwable;
}
Copy the code

AbstractBeanDefinitionReader abstract class:

/ * * *@className: AbstractBeanDefinitionReader
 * @description: TODO
 * @date: 2021/6/10 therefore *@author: jinpeng.sun
 */
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

    /** Hold the BeanDefinitionRegistry interface to complete registration to the BeanFactory */
    protected BeanDefinitionRegistry beanDefinitionRegistry;

    public AbstractBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
        super(a);this.beanDefinitionRegistry = beanDefinitionRegistry; }}Copy the code

Xml-configured bean definition parser:

/ * * *@className: XmlBeanDefinitionReader
 * @description: XmL-configured bean definition parser *@author: TR
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
        super(beanDefinitionRegistry);
    }

    @Override
    public void loadBeanDefintions(Resource resource) throws Throwable {
        this.loadBeanDefintions(new Resource[] {resource});
    }

    @Override
    public void loadBeanDefintions(Resource... resource) throws Throwable {
        if(resource ! =null && resource.length > 0) {
            for (Resource r : resource) {
                this.parseXml(r); }}}private void parseXml(Resource r) {
        //TODO parses the XML document, gets the bean definition, creates the bean definition object, and registers it with BeanDefinitionRegistry}}Copy the code

Annotated configuration bean definition parser:

 * @className: AnnotationBeanDefinitionReader
 * @descriptionAnnotated configuration bean definition parser: *@author: TR
 */
public class AnnotationBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public AnnotationBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
        super(beanDefinitionRegistry);
    }

    @Override
    public void loadBeanDefintions(Resource resource) throws Throwable {
        this.loadBeanDefintions(new Resource[] {resource});
    }

    @Override
    public void loadBeanDefintions(Resource... resource) throws Throwable {
        if(resource ! =null && resource.length > 0) {
            for (Resource r : resource) {
                this.retriveAndRegistBeanDefinition(r); }}}private void retriveAndRegistBeanDefinition(Resource resource) {
        if(resource ! =null&& resource.getFile() ! =null) {
            String className = getClassNameFormFile(resource.getFile());

            try{ Class<? > clazz = Class.forName(className); Component component = clazz.getAnnotation(Component.class);if(component ! =null) {
                    GeneralBeanDefinition beanDefinition = new GeneralBeanDefinition();
                    beanDefinition.setBeanClass(clazz);
                    beanDefinition.setScope(component.scope());
                    beanDefinition.setFactoryMethodName(component.factoryMethodName());
                    beanDefinition.setFactoryBeanName(component.factoryBeanName());
                    beanDefinition.setInitMethodName(component.initMethodName());
                    beanDefinition.setDestroyMethodName(component.destroyMethodName());

                    // Get all the constructors, find the Autowired annotation on the constructor, and set the constructor to BD, if any
                    this.handleConstructor(clazz, beanDefinition);

                    // Handle factory method parameter dependencies
                    if(StringUtils.isNotBlank(beanDefinition.getFactoryMethodName())) {
                        this.handleFactoryMethodArgs(clazz, beanDefinition);
                    }

                    // Handle attribute dependencies
                    this.handlePropertyDi(clazz, beanDefinition);

                    String beanName = "".equals(component.value()) ? component.name() : null;
                    if (StringUtils.isBlank(beanName)) {
                        // TODO uses name generation rules to generate beanName;
                        // Default hump naming
                        beanName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, clazz.getSimpleName());
                    }
                    // Register the bean definition
                    this.beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinition); }}catch(ClassNotFoundException | BeanDefinitionException e) { e.printStackTrace(); }}}private void handlePropertyDi(Class
        clazz, GeneralBeanDefinition bd) {
        // TODO Auto-generated method stub

    }

    private void handleFactoryMethodArgs(Class
        clazz, GeneralBeanDefinition bd) {
        // TODO Auto-generated method stub

    }

    private void handleConstructor(Class
        clazz, GeneralBeanDefinition bd) {
        // Get all the constructors, find the Autowired annotation on the constructor, and set the constructor to BD, if anyConstructor<? >[] constructors = clazz.getConstructors();if(constructors ! =null && constructors.length > 0) {
            for (Constructor c : constructors) {
                if(c.getAnnotation(Autowired.class) ! =null) {
                    bd.setConstructor(c);
                    Parameter[] ps = c.getParameters();
                    // Iterate to get annotations on parameters and create parameter dependencies
                    break; }}}}private int classPathAbsLength = AnnotationBeanDefinitionReader.class.getResource("/").toString().length();

    private String getClassNameFormFile(File file) {
        // Returns an absolute pathname string
        String absPath = file.getAbsolutePath();
        String name = absPath.substring(classPathAbsLength+1, absPath.indexOf("."));
        return StringUtils.replace(name, File.separator, "."); }}Copy the code

Perfect XmlApplicationContext and AnnotationApplicationContext:

public class XmlApplicationContext extends AbstractApplicationContext {

    private List<Resource> resources;

    private BeanDefinitionReader definitionReader;

    public XmlApplicationContext(String... locations) throws Throwable {
        super(a); load(locations);// Resources are parsed to BeanDefinitionReader, which is assigned to the BeanDefinitionReader interface
        this.definitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) this.beanFactory);
        Resource[] resourceArray = new Resource[resources.size()];
        resources.toArray(resourceArray);
        // Load the parsed BeanDefinition into the BeanFactory
        definitionReader.loadBeanDefintions(resourceArray);
    }

    /** * Loads resource information * according to the specified configuration file location@param locations:
     * @return: void
     **/
    private void load(String[] locations) throws IOException {
        if (resources == null) {
            resources = new ArrayList<Resource>();
        }
        // Finish loading and create Resource
        if(locations ! =null && locations.length > 0) {
            for (String lo : locations) {
                Resource resource = getResource(lo);
                if(resource ! =null) {
                    this.resources.add(resource); }}}}@Override
    public Resource getResource(String location) throws IOException {
        if (StringUtils.isNotBlank(location)) {
            // Load resources of class, system file, and URL according to the prefix of the string
            if (location.startsWith(Resource.CLASS_PATH_PREFIX)) {
                return new ClassPathResource(location.substring(Resource.CLASS_PATH_PREFIX.length()));
            } else if (location.startsWith(Resource.File_SYSTEM_PREFIX)) {
                return new FileSystemResource(location.substring(Resource.File_SYSTEM_PREFIX.length()));
            } else {
                return newUrlResource(location); }}return null; }}Copy the code
public class AnnotationApplicationContext extends AbstractApplicationContext {

    private ClassPathBeanDefinitionScanner scanner;

    public AnnotationApplicationContext(String... locations) throws Throwable {
        scanner = new ClassPathBeanDefinitionScanner((BeanDefinitionRegistry) this.beanFactory);
        scanner.scan(locations);
    }

    @Override
    public Resource getResource(String location) throws IOException {
        return null; }}Copy the code