• 周四. 12月 1st, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Handwritten spring config, the final battle, come and see!

[db:作者]

1月 6, 2022

image.png

The last one talked about handwriting Spring AOP, To enhance the function , The following is mainly about handwriting Spring Config. Use by configuration Spring

Link to the previous content :

I smile to the sky from the horizontal knife , Handwriting Spring IOC Containers , come quick Look Look!

Handwriting Spring DI Dependency injection , well , Your Yida !

Handwriting Spring AOP, Come and have a look !

Configuration analysis

Why provide a way to configure , In the previous content, when we test, we all test through code :

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);

Let’s take a look at the normal use , What is it like by configuration :

<?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>

It can be seen that , The advantages of providing configuration :

  • Practical and simple , It’s flexible to change
  • And there’s no need to change the code

Common configuration methods , Namely XML And the form of annotations , They work as follows :

image.png

The working process of configuration

Definition XML Tags and annotations

What kind of XML Tags and annotations ? Know from the previous content , The content of the configuration is Bean Defining information , that Bean The content defined is what needs to be configured

So first of all Bean Define what information is in the interface :

image.png

XML Configuration mode , First you need to define a DTD perhaps XSD file , To define a set of tag information , To assign Bean Definition

<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>

It can be seen that ,bean The content specified in the configuration of is Bean Define the information in the interface

How to annotate , You need to define a set of annotations , So what annotations are needed , It’s also Bean Define the content in the interface :

  • Specified class 、 Appoint BeanName、 Appoint scope、 Specify the factory method 、 Designated factories Bean、 Appoint init method、 Appoint destroy method, These are what we use Spring It’s through @Component To achieve
  • Specifies the dependency of the construction parameter :@Autowired、@Qualifier
  • Specify the property dependency :@Value

Bean Configuration resolution

Bean The parsing process of configuration , You need a separate interface to implement , Not in BeanFactory Middle to do , To achieve the principle of single responsibility , So you need to define a separate interface to parse Bean To configure , And then BeanFactory register Bean Definition

ApplicationContext Interface

ApplicationContext This interface is used to complete Bean Configuration resolution , As mentioned above, the ways to realize configuration are XML Annotation , So there are two implementation classes to implement ApplicationContext Interface

image.png

  1. XML The realization of the way :
  • XML There may be more than one… File , So it’s used here list
  • Need to complete : load xml、 analysis xml、 establish Bean Definition 、 register Bean Defined tasks
  1. Implementation of annotation mode
  • There will also be multiple scanned packets , It’s also used here list
  • Need to complete : Scanning package 、 Get comments 、 establish Bean Definition 、 register Bean Defined tasks

Because you need to create and register Bean Definition , So it’s going to use BeanFactory and BeanDefinitionRegistry Interface , This part of the code will be repeated if it is implemented separately in the subclass , So it’s abstracted and put in the parent class :

image.png

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

  1. Specify configuration related :xml、 annotation
  2. obtain bean relevant :BeanFactory

Then you can use the appearance mode , Let users just know ApplicationContext And its subclasses ,ApplicationContext Inherit BeanFactory, Then put the two interfaces together :

image.png

ApplicationContext Interface :

/**
* @className: ApplicationContext
* The interface used to build the whole application environment , To complete Bean Configuration and resolution of
* 1: To reduce the user's dependence on the framework class interface , Expanded BeanFactory Interface ,
* Bean Configuration and Bean All of them can be obtained through ApplicationContext Interface to complete
* 2: There are several ways to allocate resources xml Annotation , So there is xml And annotation
* 3. Bean Configuration resolution needs to be loaded first , So the configuration of resources is realized Resource Load interface for ResourceLoader
* @author: TR
*/
public interface ApplicationContext extends ResourceLoader,BeanFactory {
}

ApplicationContext The abstract class implementation of

/**
* @className: AbstractApplicationContext
* @description: ApplicationContext The abstract class implementation of
* @author: TR
*/
public abstract class AbstractApplicationContext implements ApplicationContext {
/** Hold... In combination BeanFactory, complete BeanFactory Interface method */
protected BeanFactory beanFactory;
public AbstractApplicationContext() {
super();
this.beanFactory = new PreBuildBeanFactory();
}
public AbstractApplicationContext(BeanFactory beanFactory) {
super();
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);
}
}

xml configurational ApplicationContext Implementation class

/**
* @className: XmlApplicationContext
* @description: xml configurational ApplicationContext Implementation class
* @author: TR
*/
public class XmlApplicationContext extends AbstractApplicationContext {
}

Annotation configuration mode ApplicationContext Implementation class

/**
* @className: AnnotationApplicationContext
* @description: Annotation configuration mode ApplicationContext Implementation class
* @author: TR
*/
public class AnnotationApplicationContext extends AbstractApplicationContext {
}

Implementation of configuration

XML The way

XML Handling of document sources

xml There are many sources of configuration files , such as :

image.png

From different sources XML file , It’s loaded differently , But in the process of parsing , In the end, we all hope to get InputStream

We also need to design a set of interfaces , For different sources of XML The files are processed separately

image.png

InputStreamSource Interface

/**
* @className: InputStreamSource
* @description: The final unified interface of configuration mode
* @author: TR
*/
public interface InputStreamSource {
/**
* The end result is the input stream
* @return: java.io.InputStream
**/
InputStream getInputStream() throws IOException;
}

Resource Interface

/**
* @className: Resource
* @description: Resource extension interface for input stream
* @author: TR
*/
public interface Resource extends InputStreamSource {
//classpath Formal xml The configuration file
String CLASS_PATH_PREFIX = "classpath:";
// In the form of system files xml The configuration file
String File_SYSTEM_PREFIX = "file:";
/**
* Determine whether the resource exists
* @return: boolean
**/
boolean exists();
/**
* Is it readable?
* @return: boolean
**/
boolean isReadable();
/**
* Whether to open
* @return: boolean
**/
boolean isOpen();
/**
* Get resource file
* @return: java.io.File
**/
File getFile();
}

InputStreamSource Implementation class of 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();
this.file = new File(fileName);
}
public FileSystemResource(File file) {
super();
this.file = file;
}
@Override
public boolean exists() {
return this.file == null ? false : this.file.exists();
}
@Override
public boolean isReadable() {
return this.file == null ? false : this.file.canRead();
}
@Override
public boolean isOpen() {
return false;
}
@Override
public File getFile() {
return file;
}
@Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(this.file);
}
}

ClassPathResource Implementation class :

/**
* @className: ClassPathResource
* @description: classpath Formal resource implementation classes
* @author: TR
*/
public class ClassPathResource implements Resource {
//classpath Information needed
private String path;
private Class<?> 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();
this.path = path;
this.clazz = clazz;
this.classLoader = classLoader;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Class<?> getClazz() {
return clazz;
}
public void setClazz(Class<?> clazz) {
this.clazz = clazz;
}
public ClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public boolean exists() {
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() {
return exists();
}
@Override
public boolean isOpen() {
return false;
}
@Override
public File getFile() {
return null;
}
@Override
public InputStream getInputStream() 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;
}
}

UrlResource Implementation class :

/**
* @className: UrlResource
* @description: URL Formal resource implementation classes
* @author: TR
*/
public class UrlResource implements Resource {
/** url Resource object */
private URL url;
public UrlResource(String url) throws IOException {
this.url = new URL(url);
}
public UrlResource(URL url) {
super();
this.url = url;
}
public URL getUrl() {
return url;
}
public void setUrl(URL url) {
this.url = url;
}
@Override
public boolean exists() {
return this.url != null;
}
@Override
public boolean isReadable() {
return exists();
}
@Override
public boolean isOpen() {
return false;
}
@Override
public File getFile() {
return null;
}
@Override
public InputStream getInputStream() throws IOException {
return null;
}
}

XML Resource loader

When a user gives a resource, it is a string , There are three resources on it , So who is responsible for creating these resources

Here we need to define a resource loader , To identify different resources , Then load , This part of the work is done by ApplicationContext To complete , therefore ApplicationContext Need to inherit ResourceLoader Interface

ResourceLoader Interface :

/**
* @className: ResourceLoader
* Configure the resource loading interface
* Different configurations , The loading process is different , So we need to abstract an interface to deal with the changing part
* Although the way of loading is different , But the result of the returned resource is the same , All are Resource
* @author: TR
*/
public interface ResourceLoader {
/**
* load resources
* @param location:
* @return: demo.context.Resource
**/
Resource getResource(String location) throws IOException;
}

ad locum , It is also necessary to distinguish which resource the string given by the user represents , So you need to define the rules for Strings :

image.png

Annotation mode

How to scan

What are the packages scanned ?

You need to find all the class files in the specified package directory , And it should include the children’s package

image.png

You need to define a resource path matching behavior

The results of the scan

It’s under the bag class After the document , What you need is a class name , And the scan is class file , Use the above FileResource that will do

Scanned classes ClassPathBeanDefinitionScanner

/**
* @className: ClassPathBeanDefinitionScanner
* @description: scanning class file
* @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();
this.registry = registry;
this.reader = new AnnotationBeanDefinitionReader(registry);
}
/**
* The way 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));
}
}
}
/**
* What will be scanned class To Resource
* @param basePackage:
* @return: demo.context.Resource[]
**/
private Resource[] doScan(String basePackage) throws IOException {
// Scan the classes under the package
// Construct a preliminary matching pattern string ,= The incoming packet string + / + **/*.class, Replace the inside . by /
String pathPattern = StringUtils.replace(basePackage, ".", "/") + "/" + this.resourcePatter;
if (pathPattern.charAt(0) != '/') {
pathPattern = "/" + pathPattern;
}
// Find the root path of the pattern
String rootPath = this.determineRootDir(pathPattern);
// Get the absolute path pattern of file name matching
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());
// To store the found class file resource aggregate
Set<Resource> scanedClassFileResources = new HashSet<>();
// call doRetrieveMatchingFiles To scan class file
this.doRetrieveMatchingFiles(fullPattern, rootDir, scanedClassFileResources);
return (Resource[]) scanedClassFileResources.toArray();
}
private String determineRootDir(String location) {
int rootDirEnd = location.length();
rootDirEnd = location.indexOf('*');
int zi = location.indexOf('?');
if (zi != -1 && zi < rootDirEnd) {
rootDirEnd = location.lastIndexOf('/', zi);
}
if (rootDirEnd != -1) {
return location.substring(0, rootDirEnd);
} else {
return location;
}
}
/**
* Recursively find all classes in the specified directory , Adding matching patterns to the results .
*
* @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(new FileSystemResource(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() {
return registry;
}
public void setRegistry(BeanDefinitionRegistry registry) {
this.registry = registry;
}
public BeanDefinitionReader getReader() {
return reader;
}
public void setReader(BeanDefinitionReader reader) {
this.reader = reader;
}
public PathMatcher getPathMatcher() {
return pathMatcher;
}
public void setPathMatcher(PathMatcher pathMatcher) {
this.pathMatcher = pathMatcher;
}
public String getResourcePatter() {
return resourcePatter;
}
public void setResourcePatter(String resourcePatter) {
this.resourcePatter = resourcePatter;
}
}

It can be interpreted as Bean Definition

XML And the final output of annotations is Resource, Here we need to put Resource It can be interpreted as Bean Define information

You need to define an interface to parse :

image.png

BeanDefinitionReader Interface :

/**
* @className: BeanDefinitionReader
* @description: take Resource Resources are resolved into Bean Defined interfaces
* @author: TR
*/
public interface BeanDefinitionReader {
/**
* Parsing a single resource
* @param resource:
* @return: void
**/
void loadBeanDefintions(Resource resource) throws Throwable;
/**
* Parsing multiple resources
* @param resource:
* @return: void
**/
void loadBeanDefintions(Resource... resource) throws Throwable;
}

AbstractBeanDefinitionReader abstract class :

/**
* @className: AbstractBeanDefinitionReader
* @description: TODO
* @date: 2021/6/10 15:58
* @author: jinpeng.sun
*/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {
/** hold BeanDefinitionRegistry Interface , In order to complete the registration to BeanFactory in */
protected BeanDefinitionRegistry beanDefinitionRegistry;
public AbstractBeanDefinitionReader(BeanDefinitionRegistry beanDefinitionRegistry) {
super();
this.beanDefinitionRegistry = beanDefinitionRegistry;
}
}

xml configurational bean Define parsers :

/**
* @className: XmlBeanDefinitionReader
* @description: xml configurational bean Define parsers
* @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 analysis xml file , obtain bean Definition , establish bean Define the object , Sign up to BeanDefinitionRegistry in
}
}

Annotation configuration mode bean Define parsers :

 * @className: AnnotationBeanDefinitionReader
* @description: Annotation configuration mode bean Define parsers :
* @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 construction methods , Find... In the construction method Autowired annotation , If any , Let's construct this method set To bd
this.handleConstructor(clazz, beanDefinition);
// Processing factory method parameter dependency
if(StringUtils.isNotBlank(beanDefinition.getFactoryMethodName())) {
this.handleFactoryMethodArgs(clazz, beanDefinition);
}
// Handling property dependencies
this.handlePropertyDi(clazz, beanDefinition);
String beanName = "".equals(component.value()) ? component.name() : null;
if (StringUtils.isBlank(beanName)) {
// TODO Apply name generation rules to generate beanName;
// Default hump nomenclature
beanName = CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, clazz.getSimpleName());
}
// register 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 construction methods , Find... In the construction method Autowired annotation , If any , Let's construct this method set To bd
Constructor<?>[] 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();
// Traverse to get comments on parameters , And create parameter dependencies
break;
}
}
}
}
private int classPathAbsLength = AnnotationBeanDefinitionReader.class.getResource("/").toString().length();
private String getClassNameFormFile(File file) {
// Returns the absolute pathname string
String absPath = file.getAbsolutePath();
String name = absPath.substring(classPathAbsLength+1, absPath.indexOf("."));
return StringUtils.replace(name, File.separator, ".");
}
}

perfect XmlApplicationContext and AnnotationApplicationContext:

public class XmlApplicationContext extends AbstractApplicationContext {
private List<Resource> resources;
private BeanDefinitionReader definitionReader;
public XmlApplicationContext(String... locations) throws Throwable {
super();
load(locations);
// Resources are resolved into BeanDefinition, Expatriate to BeanDefinitionReader Interface to implement
this.definitionReader = new XmlBeanDefinitionReader((BeanDefinitionRegistry) this.beanFactory);
Resource[] resourceArray = new Resource[resources.size()];
resources.toArray(resourceArray);
// Will be resolved after BeanDefinition Load to BeanFactory in
definitionReader.loadBeanDefintions(resourceArray);
}
/**
* According to the user specified profile location , Load resource information
* @param locations:
* @return: void
**/
private void load(String[] locations) throws IOException {
if (resources == null) {
resources = new ArrayList<Resource>();
}
// Finish loading , Create good 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)) {
// Distinguish according to the prefix of the string ,class、 System files 、url Loading of three kinds of resources
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 new UrlResource(location);
}
}
return null;
}
}
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;
}
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注