In the previous article, we discussed how to write the Spring IOC container by hand. Now that we have IOC, we are ready to write DI by hand
Instantiate the singleton Bean ahead of time
For singleton beans, you can pre-instantiate them using the following method
/** * Build the singleton bean project ahead of time */
public class PreBuildBeanFactory extends DefaultBeanFactory {
private final Logger logger = LoggerFactory.getLogger(getClass());
private List<String> beanNames = new ArrayList<>();
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionRegisterException {
super.registerBeanDefinition(beanName, beanDefinition);
synchronized(beanNames) { beanNames.add(beanName); }}public void preInstantiateSingletons(a) throws Exception {
synchronized (beanNames) {
for (String name : beanNames) {
BeanDefinition bd = this.getBeanDefinition(name);
if (bd.isSingleton()) {
this.doGetBean(name);
if (logger.isDebugEnabled()) {
logger.debug("preInstantiate: name=" + name + "" + bd);
}
}
}
}
}
}
Copy the code
DI analysis
DI dependency injection has setter injection, constructor injection, etc., so there are two kinds of dependencies:
- Structural parameter dependence
- Attribute dependence
The essence of dependency injection is to give values, to give values to construct parameters, to give values to properties
Parameter and attribute values can be basic or bean dependent, such as basic data type values, Strings, arrays, collections, maps, properties, and so on
Both parameter and attribute values are values that the bean factory feeds into when it does dependency injection
The realization of the DI
Structural parameter dependence
One: Definition analysis
public class Girl {
public Girl(String name, int age, Boy boy) {
/ /...}}Copy the code
The above code is a simple class with some parameters in the constructor, so normally create the Girl through the new method, i.e.
Boy boy = new Boy("Xiao Ming");
Girl girl = new Girl("Small fang".18, boy);
Copy the code
You can create a Girl by giving the value directly to Girl, and it’s as simple as that
To define a construct parameter dependency, you can do the following:
- The first parameter value is “xiaofang”.
- The value of the second parameter is 18
- The third argument has the value Boy, which is a bean dependency
You can have multiple constructor parameter values, and they’re in order, and if they’re in the wrong order, then the types of the parameters might not match, and you’ll get an error, so the list in Java can be used to store constructor parameters, and it’s ordered, right? Because parameter values can be direct values such as primitive types, Strings, maps, and so on, or they can be bean dependencies, Object is the only way to represent them
With Object, there is another problem: how do bean dependencies distinguish them
You can define a datatype to represent bean dependencies. When constructing bean instances, the bean factory iterates over the parameters to determine if they are of its own datatype and, if so, replaces them with the dependent bean instance
If the parameter values are collections, arrays, and also have bean dependencies, again, they need to be iterated over and replaced
Define a class BeanReference
The BeanReference class is used to specify which Bean depends on it
/ * * *@className: BeanReference
* @description: the * that describes bean dependencies in dependency injection@date: 2021/4/6 *, nay@author: jinpeng.sun
*/
public class BeanReference {
/** beanName */
private String beanName;
public BeanReference(String beanName) {
super(a);this.beanName = beanName;
}
/** Get beanName */
public String getBeanName(a) {
return this.beanName; }}Copy the code
The BeanDefinition interface and its implementation classes
Once the construct parameters are defined, you need to inject them in the Bean factory, first adding the interface to get the construct parameters in the BeanDefinition interface, and then implementing it in the implementation class
BeanDefinition adds the following two interfaces:
/** Gets the constructor argument */List<? > getConstructorArgumentValues();/** Sets the constructor argument */
void setConstructorArgumentValues(List
constructorArgumentValues);
Copy the code
Add the following implementation code to the GeneralBeanDefinition implementation class:
// Construct a set of parameters
privateList<? > constructorArgumentValues;@Override
publicList<? > getConstructorArgumentValues() {return constructorArgumentValues;
}
@Override
public void setConstructorArgumentValues(List
constructorArgumentValues) {
this.constructorArgumentValues = constructorArgumentValues;
}
Copy the code
At this point, we can get the construct parameter dependency, and here is the injection to implement the construct parameter dependency
DefaultBeanFactory class increment method
As you can see from the above, the first thing you need to do is convert the constructor parameter references in the bean definition to real values. Add a method to the DefaultBeanFactory class to do this
/** Gets the construction parameter value */
private Object[] getConstructorArgumentValues(BeanDefinition bd) throws Exception {
return this.getRealValues(bd.getConstructorArgumentValues());
}
/** Get the actual parameter value */
privateObject[] getRealValues(List<? > args)throws Exception {
// If the argument is null, return null
if (CollectionUtils.isEmpty(args)) {
return null;
}
// Define an array with the same length as the set of values passed
Object[] values = new Object[args.size()];
int i = 0;
Object v = null;
// Iterate over the parameter values to replace the instance on which the bean depends
for (Object rv : args) {
if (rv == null) {
v = null;
} else if (rv instanceof BeanReference) {
//TODO gets the referenced bean dependent instance
v = doGetBean(((BeanReference) rv).getBeanName());
} else if (rv instanceof Object[]) {
//TODO handles bean references in arrays
} else if (rv instanceof Collection) {
//TODO handles bean references in the collection
} else if (rv instanceof Properties) {
//TODO handles bean references in Properties
} else if (rv instanceof Map) {
//TODO handles bean references in the Map
} else {
//TODO basic type
v = rv;
}
values[i++] = v;
}
return values;
}
Copy the code
Five: construct parameter injection implementation
Once you have parameters, how do you know which is a constructor and which is a factory method?
Methods can be overloaded; Parameters may define interfaces and superclasses, while arguments are implemented by concrete subclasses
Reflection provides the following API to get constructors and base methods:
Judgment logic:
- The first method is used to do an exact match lookup based on the type of the argument, and the next step is used if none is found
- Get all the constructors and iterate through them, filtering through the number of arguments and comparing parameter types with argument types
Once the constructor or factory method is identified, the constructor or factory method can be cached for a prototype bean, that is, a multi-instance bean, and fetched directly from the cache the next time it is fetched
Add the following interface to the BeanDefinition interface:
/** get the constructor */Constructor<? > getConstructor();/** Sets the constructor */
void setConstructor(Constructor
constructor);
/** Get factory method */
Method getFactoryMethod(a);
/** Set the factory method */
void setFactoryMethod(Method factoryMethod);
Copy the code
The GeneralBeanDefinition implementation class implements:
// constructor
privateConstructor<? > constructor;// Factory method
private Method factoryMethod;
@Override
publicConstructor<? > getConstructor() {return constructor;
}
@Override
public void setConstructor(Constructor
constructor) {
this.constructor = constructor;
}
@Override
public Method getFactoryMethod(a) {
return factoryMethod;
}
@Override
public void setFactoryMethod(Method factoryMethod) {
this.factoryMethod = factoryMethod;
}
Copy the code
The next step is to implement the find constructor or factory method
/** find constructor */
private Constructor determineConstructor(BeanDefinition bd, Object[] args) throws Exception {
Constructor ct = null;
// The parameter is null
if (args == null) {
return bd.getBeanClass().getConstructor(null);
}
// Define an array of parameters of the size of the construction parameter set passedClass<? >[] paramTypes =new Class[args.length];
// For the prototype bean, the bean instance can be fetched from the cache from the second start
ct = bd.getConstructor();
if(ct ! =null) {
return ct;
}
//1. Get the constructor according to the constructor parameter type
int i = 0;
for (Object p : args) {
paramTypes[i++] = p.getClass();
}
ct = bd.getBeanClass().getConstructor(paramTypes);
//2. Get all the constructors
if (ct == null) { Constructor<? >[] cts = bd.getBeanClass().getConstructors();// Determine the number of arguments, and then the parameters and arguments
outer: for (Constructor c : cts) {
// Get the arguments in the constructorClass<? >[] parameterTypes = c.getParameterTypes();if (parameterTypes.length == args.length) {
for (int j =0; i< parameterTypes.length; j++) {
if(! parameterTypes[i].isAssignableFrom(args[j].getClass())) {continue outer;
}
}
ct = c;
breakouter; }}}if(ct ! =null) {
// For prototype beans, cache them
if(bd.isProtoType()) { bd.setConstructor(ct); }}else {
throw new Exception("No constructor found:" + bd);
}
return ct;
}
Copy the code
Modify the method of building bean instances through constructors:
/** Build bean */ with constructor
private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
Object object = null;
if (CollectionUtils.isEmpty(bd.getConstructorArgumentValues())) {
// If the constructor parameter value is empty, no arguments are passed
object = bd.getBeanClass().newInstance();
} else {
// If the obtained parameter value is empty, no parameter is passed
Object[] args = getConstructorArgumentValues(bd);
if (args == null) {
object = bd.getBeanClass().newInstance();
} else {
// call the method to implement dependency injection for the construction parameters
returndetermineConstructor(bd, args).newInstance(args); }}return object;
}
Copy the code
After the constructor method is written, follow the logic above to implement the static factory and the method that the factory method gets the bean instance
private Method determineFactoryMethod(BeanDefinition bd, Object[] realArgs, Class
type) throws Exception {
if (type == null) {
type = bd.getBeanClass();
}
// Get the factory method name
String factoryMethodName = bd.getFactoryMethodName();
if (realArgs == null) {
return type.getMethod(factoryMethodName, null);
}
Method m = null;
// For the prototype bean, the bean instance can be fetched from the cache from the second start
m = bd.getFactoryMethod();
if(m ! =null) {
return m;
}
//1. Exact matching method according to parameter type
Class[] paramTypes = new Class[realArgs.length];
int i = 0;
for (Object p : realArgs) {
paramTypes[i++] = p.getClass();
}
try {
m = type.getMethod(factoryMethodName, paramTypes);
} catch (Exception e) {
// Do nothing
m = null;
}
//2. Get all the methods and iterate over them
if (m == null) {
// Determine the number of arguments, and then the parameters and arguments
outer: for (Method m0 : type.getMethods()) {
// If the method name is different, continue to iterate directly
if(! m0.getName().equals(factoryMethodName)) {continue ;
}
// Get all the arguments to find the methodClass<? >[] parameterTypes = m0.getParameterTypes();if (parameterTypes.length == realArgs.length) {
for (int j =0; j< parameterTypes.length; j++) {
if(! parameterTypes[j].isAssignableFrom(realArgs[j].getClass())) {continue outer;
}
}
m = m0;
breakouter; }}}if(m ! =null) {
// For the prototype bean, cache the method below
if(bd.isProtoType()) { bd.setFactoryMethod(m); }}else {
throw new Exception("No corresponding method can be found:" + bd);
}
return m;
}
Copy the code
Next, modify the methods of getting beans from static factories and member factories
/** Build beans from static factories */
private Object createBeanByStaticFactoryMethod(BeanDefinition bd) throws Exception { Class<? > type = bd.getBeanClass(); Object[] realArgs = getRealValues(bd.getConstructorArgumentValues()); Method method = determineFactoryMethod(bd, realArgs, type); Object object = method.invoke(type, realArgs);return object;
}
Copy the code
/** Build beans from member factories */
private Object createBeanByFactoryBean(BeanDefinition bd) throws Exception {
String factoryBeanName = bd.getFactoryBeanName();
Object factoryBean = getBean(factoryBeanName);
Object[] realArgs = getRealValues(bd.getConstructorArgumentValues());
Method method = determineFactoryMethod(bd, realArgs, factoryBean.getClass());
Object object = method.invoke(factoryBean, realArgs);
return object;
}
Copy the code
Six: construct parameter dependency tests
Several classes used for testing:
public interface Boy {
void sayLove(a);
void play(a);
}
Copy the code
public class Lad implements Boy {
private String name;
private Girl girl;
private Money money;
public Lad(String name) {
this.name = name;
}
public Lad(String name, Girl gf) {
this.name = name;
this.girl = gf;
System.out.println("Constructor with Girl argument called");
}
public Lad(String name, MagicGirl gf) {
this.name = name;
this.girl = gf;
System.out.println("Constructor with MMagicGirll argument called");
}
public Lad(String name, Money m) {
this.name = name;
this.money = m;
System.out.println("Constructor with Money argument called");
}
public Girl getFriend(a) {
return girl;
}
public void setFriend(Girl girl) {
this.girl = girl;
}
@Override
public void sayLove(a) {
if (girl == null) {
System.out.println("It's so sad to have no one!" + hashCode());
} else {
System.out.println("I love you, honey!+ girl); }}@Override
public void play(a) {
if (money == null) {
System.out.println("How to play without money!" + hashCode());
} else {
System.out.println("Have fun with money!"+ money); }}public void init(a) {
System.out.println("God give me a date!");
}
public void destroy(a) {
System.out.println("Since ancient times love spare hate, this hate continuous no unique period!"); }}Copy the code
public interface Girl {}Copy the code
public class MagicGirl implements Girl {
private String name;
private Boy friend;
public MagicGirl(a){}
public MagicGirl(String name) {
this.name = name;
}
public Boy getFriend(a) {
return friend;
}
public void setFriend(Boy friend) {
this.friend = friend;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(a) {
return "MagicGril{" +
"name='" + name + '\' ' +
'} '; }}Copy the code
public interface Money {
void pay(a);
}
Copy the code
public class Renminbi implements Money {
@Override
public void pay(a) {
System.out.println("Payment was successfully made using RMB."); }}Copy the code
public class BoyFactory {
public static Boy getBean(String name, Money money) {
return newLad(name, money); }}Copy the code
public class BoyFactoryBean {
public Boy buildBoy(String name, Girl girl) {
return newLad(name, girl); }}Copy the code
Constructor injection test:
static PreBuildBeanFactory bf = new PreBuildBeanFactory();
/** Constructor injection test */
@Test
public void testConstructorDI(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(Lad.class);
// Set the constructor parameters
List<Object> args = new ArrayList<>();
args.add("Sun Wukong");
args.add(new BeanReference("magicGirl"));
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("swk", definition);
definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(MagicGirl.class);
// Set the constructor parameters
args = new ArrayList<>();
args.add(Bone fairy);
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("magicGirl", definition);
bf.preInstantiateSingletons();
Lad lad = (Lad) bf.getBean("swk");
lad.sayLove();
}
Copy the code
Execution Result:
Static factory injection test:
/** Static factory injection test */
@Test
public void testStaticFactoryDI(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(BoyFactory.class);
// Set the method name
definition.setFactoryMethodName("getBean");
// Set the constructor parameters
List<Object> args = new ArrayList<>();
args.add("Cowboy");
args.add(new BeanReference("rmb"));
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("nl", definition);
definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(Renminbi.class);
bf.registerBeanDefinition("rmb", definition);
bf.preInstantiateSingletons();
Boy boy = (Boy) bf.getBean("nl");
boy.play();
}
Copy the code
Execution Result:
Member factory injection test:
/** Member factory injection test */
@Test
public void testFactoryMethodDI(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
String fBeanName = "boyFactoryBean";
// Set up the factory bean
definition.setFactoryBeanName(fBeanName);
// Set the method name
definition.setFactoryMethodName("buildBoy");
// Set the constructor parameters
List<Object> args = new ArrayList<>();
args.add("Pig Eight Quit");
args.add(new BeanReference("xlv"));
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("zbj", definition);
definition = new GenericBeanDefinition();
definition.setBeanClass(BoyFactoryBean.class);
bf.registerBeanDefinition("boyFactoryBean", definition);
definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(MagicGirl.class);
bf.registerBeanDefinition("xlv", definition);
bf.preInstantiateSingletons();
Boy boy = (Boy) bf.getBean("zbj");
boy.sayLove();
}
Copy the code
Execution Result:
Loop dependent processing
Can we loop dependencies when we build objects?
Write an example to test it:
public class NiuLang {
private ZhiNv zhiNv;
public NiuLang(ZhiNv zhiNv) {
this.zhiNv = zhiNv; }}Copy the code
public class ZhiNv {
private NiuLang niuLang;
public ZhiNv(NiuLang niuLang) {
this.niuLang = niuLang; }}Copy the code
The test class:
@Test
public void testCycleDI(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(NiuLang.class);
// Set the constructor parameters
List<Object> args = new ArrayList<>();
args.add(new BeanReference("zv"));
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("nl", definition);
definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(Renminbi.class);
// Set the constructor parameters
args = new ArrayList<>();
args.add(new BeanReference("nl"));
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("zv", definition);
bf.preInstantiateSingletons();
}
Copy the code
Running results:
As you can see, there is an error
If you build instance objects with cyclic dependencies, you get stuck in a zombie situation, so this is not allowed
You can add a record of the Bean being built to the factory implementation class, add it to the record while the Bean is being built, and delete the record when the build is complete. If there is a dependency, it depends on whether the dependency is under construction. If so, it forms a circular dependency and throws an exception
/** The bean being created */
private Set<String> buildingBeans = Collections.newSetFromMap(new ConcurrentHashMap());
Copy the code
Add the following code to the doGetBean method:
Set<String> beans = buildingBeans;
// Check for loop dependencies
if (beans.contains(beanName)) {
throw new Exception(beanName + "Cyclic dependency" + beans);
}
beans.add(beanName);
Copy the code
// Delete the bean instance after it is created
beans.remove(beanName);
// Processing of singleton beans
if (bd.isSingleton()) {
beanMap.put(beanName, bean);
}
Copy the code
Run the result again:
Attribute dependence
Attribute dependency is when an attribute is dependent on a value
If you need to describe a property dependency, that is, the property name + value, you can define a class to represent the property dependency
If multiple attributes are stored using a List, attribute dependencies are handled in much the same way as constructing parameter values
public class Girl {
private String name;
private Integer age;
private Boy boy;
}
Copy the code
One: the definition of attribute dependencies
Define the PropertyValue class to represent property dependencies
* * *@className: PropertyValue
* @description: Property value type, used for property dependency injection *@date: 2021/4/7 09:11
* @author: jinpeng.sun
*/
public class PropertyValue {
private String name;
private Object value;
public PropertyValue(String name, Object value) {
super(a);this.name = name;
this.value = value;
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public Object getValue(a) {
return value;
}
public void setValue(Object value) {
this.value = value; }}Copy the code
The BeanDefinition interface and its implementation classes
The BeanDefinition class adds the following two interfaces:
/** Gets the attribute value */
List<PropertyValue> getPropertyValues(a);
/** Sets the property value */
void setPropertyValues(List<PropertyValue> propertyValues);
Copy the code
Add the corresponding implementation code to the GeneralBeanDefinition implementation class:
/ / property values
private List<PropertyValue> propertyValues;
@Override
public List<PropertyValue> getPropertyValues(a) {
return propertyValues;
}
@Override
public void setPropertyValues(List<PropertyValue> propertyValues) {
this.propertyValues = propertyValues;
}
Copy the code
Three: DefaultBeanFactory class implementation attribute dependency
/** Attributes depend on */
private void setPropertyDIValues(BeanDefinition bd, Object bean) throws Exception {
// Get the value of the attribute
if (CollectionUtils.isEmpty(bd.getPropertyValues())) {
return;
}
// Iterate over all attribute values
for (PropertyValue pv : bd.getPropertyValues()) {
String name = pv.getName();
if (StringUtils.isBlank(name)) {
continue; } Class<? > classz = bean.getClass();// Get attributes
Field p = classz.getDeclaredField(name);
p.setAccessible(true);
Object rv = pv.getValue();
Object v = null;
if (rv == null) {
v = null;
} else if (rv instanceof BeanReference) {
v = doGetBean(((BeanReference) rv).getBeanName());
} else if (rv instanceof Object[]) {
//TODO handles bean references in arrays
} else if (rv instanceof Collection) {
//TODO handles bean references in the collection
} else if (rv instanceof Properties) {
//TODO handles bean references in Properties
} else if (rv instanceof Map) {
//TODO handles bean references in the Map
} else {
// Basic typev = rv; } p.set(bean, v); }}Copy the code
Property dependencies are called after the bean instance is created but before the bean is initialized, so you need to change the doGetBean method
@Override
public Object getBean(String beanName) throws Exception {
// Get the bean definition
BeanDefinition bd = beanDefinitionMap.get(beanName);
Object bean = doGetBean(beanName);
// Attribute injection
setPropertyDIValues(bd, bean);
// The bean's life cycle
if (StringUtils.isNotBlank(bd.getInitMethodName())) {
doInitMethod(bean,bd);
}
return doGetBean(beanName);
}
public Object doGetBean(String beanName) throws Exception {
Object bean = beanMap.get(beanName);
if(bean ! =null) {
return bean;
}
// Get the bean definition
BeanDefinition bd = beanDefinitionMap.get(beanName);
Objects.requireNonNull(bd, "Can't find ["+beanName+"] bean definition information"); Class<? > type = bd.getBeanClass();// Check for loop dependencies
Set<String> beans = this.buildingBeans;
if (beans.contains(beanName)) {
throw new Exception(beanName + "Cyclic dependency" + beans);
}
beans.add(beanName);
if(type ! =null) {
if (StringUtils.isBlank(bd.getFactoryMethodName())) {
// Build the bean through the constructor
bean = createBeanByConstructor(bd);
} else {
// Build beans from static factoriesbean = createBeanByStaticFactoryMethod(bd); }}else {
// Build beans from member factories
bean = createBeanByFactoryBean(bd);
}
// Delete the instance after it is created
beans.remove(beanName);
// Handle singleton beans
if (bd.isSingleton()) {
beanMap.put(beanName, bean);
}
return bean;
}
Copy the code
Four: attribute dependence test
/** Attribute injection test */
@Test
public void testPropertyDI(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(Lad.class);
// Set the constructor parameters
List<Object> args = new ArrayList<>();
args.add("Sun Wukong");
args.add(new BeanReference("bgj"));
definition.setConstructorArgumentValues(args);
bf.registerBeanDefinition("swk", definition);
definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(MagicGirl.class);
// Set the parameter values for property injection
List<PropertyValue> propertyValues = new ArrayList<>();
propertyValues.add(new PropertyValue("name".Bone fairy));
propertyValues.add(new PropertyValue("friend".new BeanReference("swk")));
definition.setPropertyValues(propertyValues);
bf.registerBeanDefinition("bgj", definition);
bf.preInstantiateSingletons();
MagicGirl magicGirl = (MagicGirl) bf.getBean("bgj");
System.out.println(magicGirl.getName() + ":" + magicGirl.getFriend());
magicGirl.getFriend().sayLove();
}
Copy the code
Execution Result:
That’s the end of writing Spring DI, and I hope this article has been helpful to you!