preface
In this article, we will take a look at how to implement the @Component annotation, which is commonly used in development using the Spring framework. This annotation can be used on fields, constructors, and setter methods. For space reasons, we will focus on the implementation of fields. The general idea is the same, but the differences are parsing and injection. Let’s move on to today’s topic – how to implement a simplified version of Spring – how to implement the @AutoWired annotation.
Implement step splitting
The implementation steps are generally divided into three steps:
- Analyze and summarize what needs to be done, abstracting out the data structure
- Use these data structures to do something
- At some point in time
Spring
In the container
Careful friends can find that in fact, the implementation of the previous several articles is also routine, among which the most critical and difficult point is how to abstract the data structure. What we do here is that when a field on a Bean has an @AutoWired annotation, we get the Bean of that type from the container and call the setter method for that field to set it into the property of the object. This is how we implement the @Autowired annotation.
Data structure abstraction
To inject the corresponding instance in the container based on the field type, you first need to provide the ability to get the corresponding Bean instance from a type. This requires the BeanFactory interface to provide such an ability. And so on. Like this kind of internal operation should try to be transparent to the user, so this new added an interface AutowireCapableBeanFactory inherited from the BeanFactory, so inside can interface directly with the new interface. Note that the method parameters of the new interface cannot directly use the Class type to find the corresponding Bean in the container. For flexible extension (e.g., whether a dependency is required, etc.), we need to use a Class to describe the DependencyDescriptor, named DependencyDescriptor, part of the source code is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public class DependencyDescriptor {
private Field field;
private boolean required;
public DependencyDescriptor(Field field, boolean required) {
Assert.notNull(field, "Field must not be null");
this.field = field;
this.required = required;
}
publicClass<? > getDependencyType() {if (this.field ! =null) {
return field.getType();
}
throw new RuntimeException("only support field dependency");
}
public boolean isRequired(a) {
return this.required; }}Copy the code
Interface AutowireCapableBeanFactory statement is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public interface AutowireCapableBeanFactory extends BeanFactory {
Object resolveDependency(DependencyDescriptor descriptor);
}
Copy the code
Now that we’ve abstracted the dependency lookup function, let’s take a look at how the core steps abstract the process of encapsulating injection. After abstracting the process, we can see that injection can be divided into two main parts: The injected target object and the list of elements that need to be injected. This is metadata for injection, called InjectionMetadata. It contains two fields: the injected target object and the list of elements that need to be injected. Another important method is to inject the list of elements into the target object passed in as a method parameter.
Each injection elements to provide an injection to the specified target object ability, so public abstract superclass InjectionElement extract, use the above AutowireCapableBeanFactory interface to resolve the current field type corresponding to the Bean, then injected into the specified target object. The main code for InjectinElement is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public abstract class InjectionElement {
protected Member member;
protected AutowireCapableBeanFactory factory;
public InjectionElement(Member member, AutowireCapableBeanFactory factory) {
this.member = member;
this.factory = factory;
}
abstract void inject(Object target);
}
Copy the code
The main code for InjectionMetadata is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public class InjectionMetadata {
private finalClass<? > targetClass;private List<InjectionElement> injectionElements;
public InjectionMetadata(Class
targetClass, List
injectionElements)
{
this.targetClass = targetClass;
this.injectionElements = injectionElements;
}
public void inject(Object target) {
if (injectionElements == null || injectionElements.isEmpty()) {
return;
}
for(InjectionElement element : injectionElements) { element.inject(target); }}... }Copy the code
The process of converting a Class to InjectionMedata is as follows: Then call InjectionMedata provide inject (Object) method to complete the injection (dependent AutowireCapableBeanFactory interface provides ResolveDependency (DependencyDescriptor), the class diagram of the field injection part after abstraction is as follows:
Parse to construct a defined data structure
InjectionMetadata buildAutowiringMetadata(Class
CLZ), the implementation process is relatively simple, scan the declared attributes in the class to find @Autowried annotation parsing to construct InjectinMetadata instance, the core implementation code is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public class AutowiredAnnotationProcessor {
private final String requiredParameterName = "required";
private boolean requiredParameterValue = true;
private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>();
public AutowiredAnnotationProcessor(a) {
this.autowiredAnnotationTypes.add(Autowired.class);
}
public InjectionMetadata buildAutowiringMetadata(Class
clz) {
LinkedList<InjectionElement> elements = newLinkedList<>(); Class<? > targetClass = clz;do {
LinkedList<InjectionElement> currElements = new LinkedList<>();
for (Field field : targetClass.getDeclaredFields()) {
Annotation ann = findAutowiredAnnotation(field);
if(ann ! =null) {
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
boolean required = determineRequiredStatus(ann);
elements.add(new AutowiredFieldElement(field, required, beanFactory));
}
}
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
} while(targetClass ! =null&& targetClass ! = Object.class);return new InjectionMetadata(clz, elements);
}
protected boolean determineRequiredStatus(Annotation ann) {
try {
Method method = ReflectionUtils.findMethod(ann.annotationType(), this.requiredParameterName);
if (method == null) {
return true;
}
return (this.requiredParameterValue == (Boolean) ReflectionUtils.invokeMethod(method, ann));
} catch (Exception e) {
return true; }}private Annotation findAutowiredAnnotation(AccessibleObject ao) {
for (Class<? extends Annotation> annotationType : this.autowiredAnnotationTypes) {
Annotation ann = AnnotationUtils.getAnnotation(ao, annotationType);
if(ann ! =null) {
returnann; }}return null; }... }Copy the code
InjectionElement; AutowiredFieldElement; AutowiredFieldElement Rely on AutowireCapableBeanFactory interface is the ability to analyze the field belongs to type of Bean, then calls the attribute setter injection method is complete, based on our above defined data structure after the implementation is simpler, mainly as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public class AutowiredFieldElement extends InjectionElement {
private final boolean required;
public AutowiredFieldElement(Field field, boolean required, AutowireCapableBeanFactory factory) {
super(field, factory);
this.required = required;
}
public Field getField(a) {
return (Field) this.member;
}
@Override
void inject(Object target) {
Field field = this.getField();
try {
DependencyDescriptor descriptor = new DependencyDescriptor(field, this.required);
Object value = factory.resolveDependency(descriptor);
if(value ! =null) { ReflectionUtils.makeAccessible(field); field.set(target, value); }}catch (Throwable e) {
throw new BeanCreationException("Could not autowire field:"+ field, e); }}}Copy the code
Inject into Spring
The next question is: When do you call these classes and methods? Here we review the lifecycle of a Bean in Spring, where several hook entries are shown below:
Open hooks by life cycle method can be seen that we need in InstantiationAwareBeanPostProcessor interface postProcessPropertyValues method implemented Autowired injection, Will the AutowiredAnnotationProcessor in front of the class implements the interface and then injection in postProcessPropertyValues method processing. The overall class diagram for this section is as follows:
AutowiredAnnotationProcessor processor implementation postProcessPropertyValues () method is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public class AutowiredAnnotationProcessor implements InstantiationAwareBeanProcessor {...@Override
public void postProcessPropertyValues(Object bean, String beanName) throws BeansException {
InjectionMetadata metadata = this.buildAutowiringMetadata(bean.getClass());
try {
metadata.inject(bean);
} catch (Throwable e) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed"); }}}Copy the code
Then only need those registered in abstract superclass constructor AbstractApplicationContext we define processor, Then when Bean injection (DefaultBeanFactory populateBean ()) call processor postProcessPropertyValues method to complete attribute injection, An abstract class AbstractApplicationContext change part code is as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public abstract class AbstractApplicationContext implements ApplicationContext {...public AbstractApplicationContext(String configFilePath) {... registerBeanPostProcessor(beanFactory); }protected void registerBeanPostProcessor(ConfigurableBeanFactory beanFactory) {
AutowiredAnnotationProcessor postProcessor = newAutowiredAnnotationProcessor(); postProcessor.setBeanFactory(beanFactory); beanFactory.addBeanPostProcessor(postProcessor); }... }Copy the code
The default implementation of the BeanFactory interface, populateBean(BeanDefinition, Object), is changed as follows:
/ * * *@author mghio
* @sinceThe 2021-03-07 * /
public class DefaultBeanFactory extends DefaultSingletonBeanRegistry implements ConfigurableBeanFactory.BeanDefinitionRegistry {...private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<>();
private void populateBean(BeanDefinition bd, Object bean) {
for (BeanPostProcessor postProcessor : this.getBeanPostProcessors()) {
if (postProcessor instanceofInstantiationAwareBeanProcessor) { ((InstantiationAwareBeanProcessor) postProcessor).postProcessPropertyValues(bean, bd.getId()); }}... }... }Copy the code
On the whole the whole process of using the processor is divided into two steps, first registered in AbstractApplicationContext construction method we custom processor, Then call its postProcessPropertyValues method in DefaultBeanFactory injection, thus using the @autowired annotation on the class field implementation.
conclusion
This paper briefly introduces the implementation of Spring’s @Autowired annotation (using in the way of class fields), among which the more troublesome step is the data structure abstraction part, which needs to consider the later expansibility and internal operation to users as transparent as possible. Limited by space, only part of the core implementation code is listed. The complete code has been uploaded to GitHub, interested friends can view the complete code.