Description: The original intention of the project was to create a mature and distinctive IOC container on its own, but due to the fact that there were too many references to Spring in the process and not enough improvements to make, the goal was to use this project as a springboard for understanding Spring. Unlike some spring-like frameworks on the web, this project is mainly for annotation form

An overview of the

The original intention of the project was to create a mature and distinctive IOC container on its own, but since there were too many references to Spring in the process and too few improvements could be made, the purpose was to use the project as a springboard for understanding Spring. Unlike some spring-imitating frameworks on the web, this project is mainly for annotation form The address is Thales

process

In Spring, a bean is formed in three major stages,

  1. The bean definition phase (including BeanDefinition loading, parsing, and registration)
  2. The bean’s instantiation phase (including object creation, property injection)
  3. The initialization phase of the bean (including initialization of some resources, such as opening a file to establish a connection, etc.)

This is just a rough division, and there is no explicit mention of post-processing such as BeanPostProcessor.

The design of the class

If you just want to understand how a bean works from birth to death, debug it step by step. If you don’t understand, debug it several times. However, if you want to implement a similar container, the design of the class, the assignment of responsibilities, and the implementation inheritance of the interface must be understood (unless you want several classes to do everything).

The following is a DefaultListableBeanFactory class diagram

Is it too much?

Let’s look at another picture

The first image is Spring5.0 and the second image is Spring0.9, so there is no need to introduce too much design complexity at the outset

Let’s look at another set of comparisons

It’s easy to see which is 0.9 and which is 5.0.

The purpose of all this is to show that we don’t have to start with the best goal. We can start by adding features step by step

Achieving simple IOC

As we all know, the most basic part of SpringIoC is the BeanFactory

Let’s define a BeanFactory interface

Public interface BeanFactory {@param name * @return */ Object getBean(String name); }Copy the code

beanDefinition

As annotations, instead of giving a resource file and parsing it like XML, we should scan all classes in our classPath with @Component.

In this case, we need a given parameter to go from file path to package path, we just need to scan the package and its subpackages for the qualified classes, convert them to BeanDefinition and register them. Perform this function is ClassPathBeanDefinitionScanner this class. In this step, we are already different from the traditional process, passing in a ResourceLoader for the specific scanning functionality (that is, positioning), but there are no special classes to handle the parsing

public interface Resource { File getFile(); String getFilename(); String getFilePath(); } public abstract class AbstractResource implements Resource {@override public abstract class AbstractResource implements Resource {@override public String getFilename() { return getFile().getName(); } @Override public String getFilePath() { return getFile().getPath(); }} // This is the Resource class that we used to instantiate the bean. It is not used directly in Spring, but integrated with the appearance mode to become RootBeanDefinition Public Class ClassPathResource extends AbstractResource { private final String path; private ClassLoader classLoader; private Class<? > clazz; public ClassPathResource(String path, ClassLoader classLoader, Class<? > clazz) { this.path = path; this.classLoader = classLoader; this.clazz = clazz; } } public interface ResourceLoader { Resource getResource(String location); } public interface ResourcePatternResolver extends ResourceLoader {String CLASSPATH_ALL_URL_PREFIX = "classpath*:"; List<? extends Resource> getResources(String location); } / / this class is to scan the class of the public for formal class PathMatchingResourcePatternResolver implements ResourcePatternResolver {private final ResourceLoader resourceLoader; public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader){ this.resourceLoader = resourceLoader; } @Override public Resource getResource(String location) { return resourceLoader.getResource(location); } // Override public List<? Override public List<? extends Resource> getResources(String location) { Set<Class<? >> classes = ClassUtils.getClasses(location); List<ClassPathResource> classPathResources = new ArrayList<>(); for (Class<? > clazz:classes) { classPathResources.add(new ClassPathResource("",clazz.getClassLoader(),clazz)); } return classPathResources; }}Copy the code

But in the end not PathMatchingResourcePatternResolver directly use

But as an attribute of ClassPathBeanDefinitionScanner, him call in the class.

So we’ve got Resource, how do we get the BeanDefinition?

First consider the question, what kind of class can be registered as a BeanDefinition?

  1. Add the @Component annotation or meet other registration criteria
  2. Not interfaces or abstract classes

Boolean isCandidateComponent(Class<? > clazz) to determine whether it is registered

Now in the registration phase, we have abstracted the registration Bean definition separately, still adhering to the philosophy of interface programming and considering a single responsibility

public interface BeanDefinitionRegistry {
    void registerBeanDefinition(BeanDefinition beanDefinition);
}
Copy the code

Said to Bean definitions on positioning, parsing, registration is done in ClassPathBeanDefinitionScanner, hence BeanDefinitionRegistry nature also become ClassPathBeanDefinitionScanner properties One of the

So ClassPathBeanDefinitionScanner build is done

Public class ClassPathBeanDefinitionScanner {/ / is responsible for the specific Resource locating private ResourcePatternResolver ResourcePatternResolver;  Private BeanDefinitionRegistry registry; public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry,String... basePackage) { this.registry = registry; this.resourcePatternResolver = new PathMatchingResourcePatternResolver((ResourceLoader) registry); this.scan(basePackage); } public void scan(String... basePackages){ doScan(basePackages); } void doScan(String[] basePackages){ Set<BeanDefinition> beanDefinitions = new LinkedHashSet<>(); for (String basePackage:basePackages) { Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for(BeanDefinition candidate:candidates){ beanDefinitions.add(candidate); registry.registerBeanDefinition(candidate); Private Set<BeanDefinition> findCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>(); List<? extends Resource> resources = getResourcePatternResolver().getResources(basePackage); for(Resource resource:resources){ if(resource instanceof ClassPathResource){ ClassPathResource classPathResource = (ClassPathResource)resource; if(isCandidateComponent(classPathResource.getClazz())){ AnnotationBeanDefinition beanDefinition = new AnnotationBeanDefinition(); beanDefinition.setClazz(classPathResource.getClazz()); beanDefinition.setBeanName(BeanUtils.generateBeanName(classPathResource.getClazz().getName())); candidates.add(beanDefinition); } } } return candidates; } private ResourcePatternResolver getResourcePatternResolver() { return this.resourcePatternResolver; } Boolean isCandidateComponent(Class<? > clazz){ Component declaredAnnotation = clazz.getDeclaredAnnotation(Component.class); return declaredAnnotation! =null&&! clazz.isInterface(); }; }Copy the code

instantiation

When is it instantiated? Let’s say we instantiate when getBean() is called and there is no bean available

public abstract class AbstractBeanFactory implements BeanFactory{
    @Override
    public Object getBean(String beanName)
    
}
Copy the code

Object creation

There are two ways to do this, either with the Jdk’s default reflection, or with the Cglib proxy.

The default, of course, is no-argument construction, but if parameters are passed in, the constructor needs to be instantiated by matching the type and number of parameters

So we abstract the InstantiationStrategy as the instantiation interface. Both instantiation methods need to implement this interface, and we only need to call the method of this interface when we really use it

public interface InstantiationStrategy {
    Object instantiate(BeanDefinition beanDefinition, String beanName, BeanFactory owner);
}
public class SimpleInstantiationStrategy implements InstantiationStrategy {
}
Copy the code

Properties into

Field value acquisition

There are two ways to get field values

  1. Directly annotate Autowired or Value
  2. Value is filled with placeholders instead of values, so you need to parse the placeholders to get them

We get all the fields from the Class object, and we get them by iterating through all the fields looking for annotations that have been added to them (this is just a Spring injection).

/ / handle the @autowired annotation for (Field Field: declaredFields) {Autowired Autowired = Field. GetDeclaredAnnotation (Autowired. Class); if(autowired ! = null){ pvs.add(new PropertyValue(field.getName(),new BeanReference(BeanUtils.generateBeanName(field.getType().getName()),field.getType()))); }} / / @ Value annotations for (Field Field: declaredFields) {Value Value = Field. GetDeclaredAnnotation (Value. The class); if(value ! = null){ String value1 = value.value(); pvs.add(new PropertyValue(field.getName(),value1)); }}Copy the code

Field value fill

Get the value of the field and fill it into the corresponding field by reflection

for(Field field:mbd.getBeanClass().getDeclaredFields()){ field.setAccessible(true); if (field.getName().equals(propertiesValue.getName())&&field.getType().isAssignableFrom(newValue.getClass())) { field.set(bean,newValue); }}Copy the code

Initialize the

Call the specified initialization method to initialize the resource. In AN XML schema, you just put a tag on it, and if it’s an annotation schema, you put a tag on it or you put a parameter on one of the annotations that represents the initialization method, which hasn’t been implemented yet

Functional filler

Rear processor addition

Now that we have implemented a Bean container for dependency lookup and dependency injection, let’s review the Spring process. What is missing? The most obvious one is the BeanFactoryPostProcessor , the former for beanFactory modify operation, the latter for bean modify operation, is also interface oriented programming

First set up the BeanPostProcessor

public interface BeanPostProcessor {
   
    Object postProcessBeforeInitialization(Object bean, String beanName);

    Object postProcessAfterInitialization(Object bean, String beanName) ;
}
Copy the code

For now, what does the BeanPostProcessor need to do? We can separate out the code that handles annotations and gets injected properties and use a BeanPostProcessor

All custom implementations of the BeanPostProcessor need to inherit this interface, and because the BeanPostProcessor’s role is to process other beans, it must be created before other beans being processed are instantiated. So we’re finishBeanFactoryInitialization (the beanFactory); Before adding registerBeanPostProcessors (the beanFactory); Use to instantiate all beanpostProcessors

The importance of these BeanPostProcessors is different. For example, the beanPostProcessor that handles annotation injection has a higher priority than the normal beanPostProcessor, so it needs to be instantiated first

Aware Interface Addition

Now we have been able to actually complete add an object to the IOC container, but at the moment, the relations between the object and the container is one-way, container can bean operation, but bean can’t use container, in order to solve such problems, we add a Aware interface as a symbol of interface, by each more specific Aware to succeed him, and in the instance After the attribute is initialized, the initialization method execution completes the injection of the associated container attribute

Event listener added

Listeners are an implementation of the observer pattern

Let’s start by defining the following basic interfaces

Public interface ApplicationEventPublisher {/ * * * * @ release events param event * / void publishEvent (ApplicationEvent event); } public interface ApplicationEventMulticaster {/ * * * add broadcast events * @ param event * / void multicastEvent (ApplicationEvent event); /** * Add listener for an event * @param Listener */ void addApplicationListener(ApplicationListener listener); / * * * to remove the specified listener * @ param listener * / void removeApplicationListener (ApplicationListener listener); } public interface ApplicationListener <E extends ApplicationEvent> extends EventListener {/** * listen for a specific event * @param event */ void onApplicationEvent(E event); }Copy the code

The call flow is added to the broadcaster for a specific listener, the event is uniformly published through Publisher, and the publishEvent finally calls multicastEvent(ApplicationEvent) Event), and the corresponding listener will perform the corresponding operation after the corresponding judgment.

How do I determine if this listener is interested in the event?

The listener we implement in advance is generic, and we can tell by the relationship between the generic and the incoming event type

Public Boolean supportEvent (ApplicationListener < ApplicationEvent > listener, ApplicationEvent event) {/ / to get first Class object Class <?  extends ApplicationListener> listenerClass = listener.getClass(); / / get all the interface of its implementation, including generics information Type [] genericInterfaces = listenerClass. GetGenericInterfaces (); For (Type genericInterface: genericInterfaces) {/ / determine whether as generic interface if (genericInterface instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) genericInterface; / / get all the generic parameter Type [] actualTypeArguments = parameterizedType. GetActualTypeArguments (); for(Type actualTypeArgument:actualTypeArguments){ try { Class<? > aClass = Class.forName(actualTypeArgument.getTypeName()); If (aclass.isAssignableFrom (event.getClass())){return true; if(aclass.isassignableFrom (event.getClass())){return true; } } catch (ClassNotFoundException e) { e.printStackTrace(); } } } } return false; }Copy the code

FactoryBean add

Currently beans are generated by the BeanFactory,

We use the FactoryBean interface to identify the particular Bean that produces the Bean

Resolution of loop dependencies

Cyclic dependency is when A depends on B and B depends on A, the solution is to separate the instantiation from the initialization, and if you just think about the general case two levels of caching are actually enough,

Code optimization

Implementing simple AOP

If you start with orthodox AOP, a bunch of concepts come along, including pointcuts, advice and the like

Let’s look first at what AOP does

So the core of AOP is dynamic proxy, we take Cglib as an example to see how to use dynamic proxy

Enhancer enhancer = new Enhancer(); //1. For which class to delegate enhancer.setsuperclass (buy.class); enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> { //2. If (method.getName().equals("buyOne")){//3. Println ("hello"); system.out.println ("hello"); } //4. Call methodProxy.invokeSuper(o,objects); return o; }); Buy o = (Buy)enhancer. Create ();Copy the code

This is the core functionality of dynamic proxies, as well as AOP, whose ultimate goal is code 5: to generate a proxy object and hand it over to the IOC to manage

In order to do this,AOP frameworks need to do exactly what code 1-4 needs to do. One and two are combined to form JoinPoint,3 is called Advice, and the two together are called Advisor. Can you write all of these classes in one or more classes Do, but the development to today, has long adopted this division. This classification is also used in this project.

Starting with the join point, how to determine exactly where to implement enhancements is at both the class and method levels.

Let’s first define the ClassFilter and MethodMacther interfaces

Public interface ClassFilter {@param clazz * @return */ Boolean matches(Class<? > clazz); } public interface MethodMatcher {/** * whether the corresponding method of the corresponding class matches * @param method * @param targetClass * @return */ Boolean matches(Method method,Class< ? > targetClass); }Copy the code

These two interfaces must be used together, so we combine them with PointCut

Public interface Pointcut {/** * getClassFilter * @return */ ClassFilter getClassFilter(); /** * getMethodMatcher * @return */ getMethodMatcher(); }Copy the code

Interfaces only define abstract functions, which must be implemented concretely

We build the JdkRegexMethodMatcher by default using Java’s re to match method names

public class JdkRegexMethodPointcut implements MethodMatcher, Pointcut{ private Pattern[] compiledPatterns = new Pattern[0]; @Override public ClassFilter getClassFilter() { return null; } @Override public MethodMatcher getMethodMatcher() { return this; } @Override public boolean matches(Method method, Class<? > targetClass) { String name = method.getName(); for (Pattern pattern :compiledPatterns) { Matcher matcher = pattern.matcher(name); if(matcher.matches()){ return true; } } return false; } // Precompile private Pattern[] compilePatterns(String[] source) throws PatternSyntaxException {Pattern[] destination = new Pattern[source.length]; for (int i = 0; i < source.length; i++) { destination[i] = Pattern.compile(source[i]); } return destination; } public void initPatternRepresentation(String[] patterns) throws PatternSyntaxException { this.compiledPatterns = compilePatterns(patterns); }}Copy the code

In Spring, MethodMatcher is not directly inherited, but an extra layer of abstraction is taken into account for the syntax of the re, which is omitted here

The JdkRegexMethodMatcher also implements the PointCut class, which means that the PointCut is now ready

Look at the Advice

As there are more extensibility points to consider, there are more levels of inheritance

public interface Advice {
}
public interface BeforeAdvice extends Advice{
}
public interface MethodBeforeAdvice extends BeforeAdvice{
        void before(Method method, Object[] args, Object target) throws Throwable;
}
Copy the code

Now that Advice is defined, we leave the implementation to the user

The next step is to integrate into advisors

public interface Advisor { Advice getAdvice(); } public interface PointcutAdvisor extends Advisor{ Pointcut getPointcut(); } public abstract class AbstractPointcutAdvisor implements PointcutAdvisor{ private Advice advice; @Override public Advice getAdvice() { return advice; } public void setAdvice(Advice advice) { this.advice = advice; }}Copy the code

The functionality of the Advisor has now been defined

Let’s implement this interface

public class RegexMethodPointcutAdvisor extends AbstractPointcutAdvisor { JdkRegexMethodPointcut pointcut = new JdkRegexMethodPointcut(); private String[] patterns; public RegexMethodPointcutAdvisor() { } public RegexMethodPointcutAdvisor(Advice advice) { setAdvice(advice); } public void setPattern(String pattern) { setPatterns(pattern); } public void setPatterns(String... patterns) { this.patterns = patterns; pointcut.initPatternRepresentation(patterns); } @Override public Pointcut getPointcut() { return pointcut; }}Copy the code

RegexMethodPointcutAdvisor to combine pointcuts and Advice, through him, we can determine where to do what.

The advisor can now verify that a class is being proxied, but if that class needs to be proxied, the Advisor cannot store information about that class

So we need a class to combine the Advisor with the corresponding proxy class, AdvisedSupport

public class AdvisedSupport { private TargetSource targetSource; private List<MethodInterceptor> methodInterceptors = new ArrayList<>(); private List<PointcutAdvisor> advisors = new ArrayList<>(); public TargetSource getTargetSource() { return targetSource; } public void setTargetSource(TargetSource targetSource) { this.targetSource = targetSource; } public List<MethodInterceptor> getMethodInterceptor() { return methodInterceptors; } public void addMethodInterceptor(MethodInterceptor methodInterceptor) { this.methodInterceptors.add(methodInterceptor); } public List<PointcutAdvisor> getAdvisor() { return advisors; } public void addAdvisor(PointcutAdvisor advisor) { MethodBeforeAdviceInterceptor methodBeforeAdviceInterceptor = new MethodBeforeAdviceInterceptor(); methodBeforeAdviceInterceptor.setAdvice((MethodBeforeAdvice) advisor.getAdvice()); addMethodInterceptor(methodBeforeAdviceInterceptor); this.advisors.add(advisor); }}Copy the code

The TargetSource in the above class attribute is the class that actually holds the proxy object information

Now that we have everything, we just need to use Cglib to create new classes using the information we already have

public class CglibAopProxy implements AopProxy{ private final AdvisedSupport advised; public CglibAopProxy(AdvisedSupport advised) { this.advised = advised; } @Override public Object getProxy() { Enhancer enhancer = new Enhancer(); SetSuperclass (set.gettargetSource ().gettargetClass ())); enhancer.setCallback(new DynamicAdvisedInterceptor(advised)); Return enhancer. Create (); } private static class DynamicAdvisedInterceptor implements MethodInterceptor { private final AdvisedSupport advised; public DynamicAdvisedInterceptor(AdvisedSupport advised) { this.advised = advised; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { CglibInvocation cglibInvocation = new CglibInvocation(method,objects,o,methodProxy); For (PointcutAdvisor Advisor: advised.getAdvisor()){ if(advisor.getPointcut().getMethodMatcher().matches(method,advised.getTargetSource().getTargetClass())){ //3. Agent what to do return advised. GetMethodInterceptor () get (0) invoke (cglibInvocation); Return cglibInvocation. Proceed (); }}}Copy the code

Compare this code to the original cglib code, and the process is almost identical. But as a framework, it should be as convenient as possible for users

We need a Creator to do all of this. He is responsible for combining Advice and PointCut into Advisor, Advisor and TargetSource into AdvisedSupport, and then AdvisedSupport to Cglib action State proxy, which produces proxy objects, and the user only needs to write Advice and pointcut expressions

Function demonstration

  1. Property injection base type reference type loop dependency
  2. Container perception
  3. FactoryBean generates objects
  4. AOP facets enhanced
  5. Custom BeanPostProcessor

Difficulties and Solutions

  1. The first is design
  2. The realization of the FactoryBean
  3. Combination of AOP and IOC
  4. Field injection

The original link

This article is the original content of Aliyun and shall not be reproduced without permission.