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,
- The bean definition phase (including BeanDefinition loading, parsing, and registration)
- The bean’s instantiation phase (including object creation, property injection)
- 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?
- Add the @Component annotation or meet other registration criteria
- 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
- Directly annotate Autowired or Value
- 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
- Property injection base type reference type loop dependency
- Container perception
- FactoryBean generates objects
- AOP facets enhanced
- Custom BeanPostProcessor
Difficulties and Solutions
- The first is design
- The realization of the FactoryBean
- Combination of AOP and IOC
- Field injection
The original link
This article is the original content of Aliyun and shall not be reproduced without permission.