I believe many friends are already familiar with Spring, and they are often asked about relevant knowledge of Spring in interviews, such as IOC, DI, AOP, etc. The following is a way to get familiar with and understand the relevant content of IOC by writing IOC
The IOC analysis
What is the IOC
Inversion of Control
What to make of this inversion of control?
Inversion: the retrieval of dependent objects has been reversed, from self-creation to retrieval (and auto-injection) from the IOC container; In other words, you don’t come to me, I come to you. The traditional way is that I control other objects inside the object. With IOC, IOC is a special container that creates and manages these objects
For example, we usually find a girlfriend or boyfriend, will try every means to inquire about their contact information ah, ah hobby and so on, these things are need us to do. IOC is like dating agencies, chat groups and so on. Then we can present our requirements to them, such as height, weight, appearance and so on. These intermediaries will provide a partner according to our requirements, and then we can fall in love with this person
What benefits can IOC bring
As you can see from the above brief description, IOC has the following benefits:
- The code is cleaner, you don’t need to new the objects you use, and you can decouple them
- Interface oriented programming decouples the user from the concrete, making it easy to extend and replace the implementers
- AOP enhancements can be easily implemented
What does an IOC container do
The IOC’s main job is to create and manage instances of these classes, which can then be made available to consumers
Whether the IOC container is an instance of the factory pattern
Yes, IOC is responsible for creating instance objects of the class, getting them if needed from the IOC container, which can also be called Bean factories, producing Bean instances
IOC Design and Implementation
What is needed to design an IOC
Given that the IOC container is a Bean factory, do you need an interface to the Bean factory that creates and retrieves these beans?
How do you know what the user-supplied bean looks like? Do you also need an interface to define these beans?
The Bean factory and the Bean definition interface are already available, so how does the Bean factory know how to create the Bean? Is it necessary to tell the Bean factory the information about the Bean definition
To sum up, designing IOC requires the following three elements:
1. Bean factory interface
2. Beans define interfaces
3. Registration interface defined by Bean
Defines the interface
One: Bean factory interface
It is mainly used to create and get Bean instances
/ * * *@ClassName BeanFactory
* @Description: Bean factory interface, responsible for creating and getting beans *@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public interface BeanFactory {
/** Get the bean */
Object getBean(String beanName) throws Exception;
}
Copy the code
The registration interface defined by the Bean
What methods are required in the registration interface defined by the Bean?
It is necessary to be able to register and retrieve Bean definition information, so the registered Bean definition information also needs to distinguish it, it is not necessary to give each Bean definition, let it have a unique name on the line
/ * * *@ClassName BeanDefinitionRegistry
* @Description: The registered interface for the Bean definition that acts as a bridge between the Bean definition and the Bean factory@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public interface BeanDefinitionRegistry {
/** Register Bean definition information, beanName is used to distinguish registered Bean definition */
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionRegisterException;
/** Get Bean information */
BeanDefinition getBeanDefinition(String beanName);
/** Whether the Bean definition is already registered */
boolean containsBeanDefinition(String beanName);
}
Copy the code
Custom exception classes:
/ * * *@ClassName BeanDefinitionRegisterException
* @Description: Custom exception class *@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public class BeanDefinitionRegisterException extends Exception {
public BeanDefinitionRegisterException(String message) {
super(message);
}
public BeanDefinitionRegisterException(String message, Throwable cause) {
super(message, cause); }}Copy the code
The Bean defines the interface
What is the Bean definition for? Is telling the Bean factory how to create a Bean of a particular class
There are several ways to get an instance of a class:
- New constructor
User user = new User()
Copy the code
- Factory method: Static factory
public class UserFactory {
public static User getUser(a) {
return newUser(); }}Copy the code
- Factory method: member method
public class UserFactory {
public User getUser(a) {
return newUser(); }}Copy the code
What information do Bean factories need to know to help us create beans?
- With the new constructor, you need to know the class name
- With static factory methods, you need to know the factory class name, factory method name
- With the member factory method, you need to know the factory bean name, factory method name
Do you need to create a new Bean every time you get an instance of a Bean from the Bean factory? Definitely not. Some of them just need singletons
The Bean definition information is needed to tell the Bean factory how to create the Bean, so the Bean definition needs to provide the Bean factory with some methods:
- Get the Bean’s Class name: getBeanClass():Class
- GetFactoryMethodName: getFactoryMethodName():String
- GetFactoryBeanName: getFactoryBeanName():String
- GetScope ():String, isSingleton(), isPrototype()
Is it enough to provide the above methods? What else is there in the life cycle of a class object?
- Does it require some initialization after object creation: getInitMethodName():String
- For example, some objects also need to do some special destruction logic (such as releasing resources) when they are destroyed: getDestroyMethodName():String
Provide the above initialization and destruction methods for the user to use, and in the case of the Bean factory, get those initialization and destruction methods
/ * * *@ClassName Beandefinition
* @Description: Bean defines interface *@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public interface BeanDefinition {
Singleton / * * * /
String SCOPE_SINGLETON = "singleton";
/** 多例 */
String SCOPE_PROTOTYPE = "prototype";
/** Get Bean */ via constructorClass<? > getBeanClass();/** Set beanClass */
void setBeanClass(Class
beanClass);
/** Get the Bean */ from the static factory
String getFactoryMethodName(a);
/** Sets the factory method name */
void setFactoryMethodName(String factoryMethodName);
/** Get the Bean */ from the member factory
String getFactoryBeanName(a);
/** Sets the factory Bean name */
void setFactoryBeanName(String factoryBeanName);
/** Get the range */
String getScope(a);
/** Set range */
void setScope(String scope);
/** is the singleton */
boolean isSingleton(a);
/** */
boolean isPrototype(a);
/** Get the initialization method */
String getInitMethodName(a);
/** Sets the initialization method */
void setInitMethodName(String initMethodName);
/** Get the destruction method */
String getDestroyMethodName(a);
/** Sets the destruction method */
void setDestroyMethodName(String destroyMethodName);
/** * validates that the Bean definition can be registered */ while being registered
default boolean validate(a) {
// No BeanClass is defined, or no factory method or factory bean is specified.
// This is playing me, having nothing is like asking for a date
if (getBeanClass() == null) {
if (StringUtils.isBlank(this.getFactoryMethodName())
|| StringUtils.isBlank(this.getFactoryBeanName())) {
return false; }}// The factory bean defined by the class is invalid
if(getBeanClass() ! =null && StringUtils.isNoneBlank(this.getFactoryBeanName())) {
return false;
}
return true;
};
}
Copy the code
Implementing an interface
Now that we have interfaces, is it time to implement them, to do something interesting?
In the first place? GenericBeanDefinition class to implement a GenericBeanDefinition
GenericBeanDefinition implementation of Bean definition
The implementation class for the Bean definition, which is relatively simple, does nothing more than get and set the Bean definition information
/ * * *@ClassName GenericBeanDefinition
* @Description: Implementation class * for the Bean definition@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public class GenericBeanDefinition implements BeanDefinition {
privateClass<? > beanClass;private String factoryMethodName;
private String factoryBeanName;
private String initMethodName;
private String destroyMethodName;
private String scope = BeanDefinition.SCOPE_SINGLETON;
@Override
publicClass<? > getBeanClass() {return beanClass;
}
@Override
public void setBeanClass(Class
beanClass) {
this.beanClass = beanClass;
}
@Override
public String getFactoryMethodName(a) {
return factoryMethodName;
}
@Override
public void setFactoryMethodName(String factoryMethodName) {
this.factoryMethodName = factoryMethodName;
}
@Override
public String getFactoryBeanName(a) {
return factoryBeanName;
}
@Override
public void setFactoryBeanName(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
@Override
public String getScope(a) {
return scope;
}
@Override
public void setScope(String scope) {
this.scope = scope;
}
@Override
public boolean isSingleton(a) {
return scope.equals(BeanDefinition.SCOPE_SINGLETON);
}
@Override
public boolean isPrototype(a) {
return scope.equals(BeanDefinition.SCOPE_PROTOTYPE);
}
@Override
public String getInitMethodName(a) {
return initMethodName;
}
@Override
public void setInitMethodName(String initMethodName) {
this.initMethodName = initMethodName;
}
@Override
public String getDestroyMethodName(a) {
return destroyMethodName;
}
@Override
public void setDestroyMethodName(String destroyMethodName) {
this.destroyMethodName = destroyMethodName;
}
@Override
public String toString(a) {
return "GenericBeanDefinition{" +
"beanClass=" + beanClass +
", factoryMethodName='" + factoryMethodName + '\' ' +
", factoryBeanName='" + factoryBeanName + '\' ' +
", initMethodName='" + initMethodName + '\' ' +
", destroyMethodName='" + destroyMethodName + '\' ' +
", scope='" + scope + '\' ' +
'} '; }}Copy the code
Two: Bean factory implementation DefaultBeanFactory
The next step is to implement the Bean factory and get it up and running
First, consider whether the bean definition information needs to be stored. Define a Map to cache the bean definition information
/** Bean defines cache */
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
Copy the code
Created beans also need to be stored for future retrieval
/** Bean cache */
private Map<String, Object> beanMap = new ConcurrentHashMap<>();
Copy the code
There are a few things you need to do in getBean to create the Bean instance, which can then be initialized
public class DefaultBeanFactory implements BeanFactory.BeanDefinitionRegistry {
@Override
public Object getBean(String beanName) throws Exception {
returndoGetBean(beanName); }}Copy the code
Next implement the doGetBean method:
From the above statement, you can see that there are three ways to create a Bean instance: through a constructor, through a static factory, and through a member factory method. The code looks like this:
private Object doGetBean(String beanName) throws Exception {
// Check whether the beanName object has been created
Object bean = beanMap.get(beanName);
if(bean ! =null) {
return bean;
}
BeanDefinition bd = beanDefinitionMap.get(beanName);
Objects.requireNonNull(bd, "Can't recruit ["+beanName+"> < span style =" max-width: 100%; clear: both;); Class<? > beanClass = bd.getBeanClass();if(beanClass ! =null) {
// Build objects using constructors
if (StringUtils.isBlank(bd.getFactoryMethodName())) {
bean = createBeanByConstructor(bd);
} else { // Build objects from static factoriesbean = createBeanByStaticFactory(bd); }}else { // Build objects from member factories
bean = createBeanByFactoryBean(bd);
}
// Start the bean's life cycle
if (StringUtils.isNotBlank(bd.getInitMethodName())) {
doInitMethod(bean, bd);
}
// Processing of singleton beans
if (bd.isSingleton()) {
beanMap.put(beanName, bean);
}
return bean;
}
Copy the code
Code logic: first go to beanMap to fetch Bean, if already exists directly return; Then, the bean definition information is obtained according to beanName, followed by a non-null judgment according to beanName if the bean definition can not be obtained; If beanClass is not null and the factory method name is null, then we know that the Bean is created based on the constructor; If the factory method is not empty, the Bean is created from the static factory; If the beanClass is empty, you can conclude that the Bean was created according to the member method
- Create the Bean through a constructor
You must first get the class name, then instantiate the Bean according to newInstance, and finally return it
/** Build the object with the constructor */
private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
// Get the class nameClass<? > type = bd.getBeanClass();// Instantiate the bean
Object bean = type.newInstance();
return bean;
}
Copy the code
- Create beans through static factories
Static factories create beans based on classes. Method name to create, first is also to get the class name, then is to get the factory method name, according to getMethod to get the method, and then call the method to instantiate
/** Build objects from static factories */
private Object createBeanByStaticFactory(BeanDefinition bd) throws Exception {
// Get the factory class nameClass<? > type = bd.getBeanClass();// Get the factory method name
String factoryMethodName = bd.getFactoryMethodName();
Method method = type.getMethod(factoryMethodName, null);
Object object = method.invoke(type, null);
return object;
}
Copy the code
- Create beans through member factories
Member factory to create Bean, first get factory Bean, then get factory method, finally getMethod according to getMethod, then call method to instantiate
/** Build objects from member factories */
private Object createBeanByFactoryBean(BeanDefinition bd) throws Exception {
// Get the factory bean name
String factoryBeanName = bd.getFactoryBeanName();
// Get the factory bean
Object factoryBean = getBean(factoryBeanName);
// Get the factory method
String factoryMethodName = bd.getFactoryMethodName();
Method method = factoryBean.getClass().getMethod(factoryMethodName, null);
Object object = method.invoke(factoryBean, null);
return object;
}
Copy the code
Here is the implementation of the Bean registration interface:
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException {
Objects.requireNonNull(beanName, "BeanName needs to be specified to register beans");
Objects.requireNonNull(beanDefinition, "BeanDefinition needs to be specified to register beans");
if(! beanDefinition.validate()) {throw new BeanDefinitionRegisterException("The name is ["+beanName+"】 beanName is not legal," + beanDefinition);
}
if (containsBeanDefinition(beanName)) {
throw new BeanDefinitionRegisterException("The name is ["+beanName+"] beanName has been registered," + beanName);
}
beanDefinitionMap.put(beanName, beanDefinition);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) {
return beanDefinitionMap.get(beanName);
}
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinitionMap.containsKey(beanName);
}
Copy the code
Code logic: BeanName is used to distinguish Bean definition information, so a non-null judgment is added. Bean definition information should also be judged whether it is empty. Then, according to the verification method in the Bean definition interface, the Bean definition information is judged whether it is legal. If the Container BeanDefinition method is used to determine if the Bean has been registered, put the beanDefinitionMap for the registered Bean definition
The destruction logic is implemented by implementing Closeable:
@Override
public void close(a) throws IOException {
// Execute the destruction method for the singleton Bean
for(Map.Entry<String, BeanDefinition> e : beanDefinitionMap.entrySet()) {
/ / get BeanName
String beanName = e.getKey();
// Get the Bean definition
BeanDefinition definition = e.getValue();
// If it is a singleton Bean and the destruction method is not empty, then the destruction method is executed
if(definition.isSingleton() && StringUtils.isNotBlank(definition.getDestroyMethodName())) {
/ / get a Bean
Object instance = beanMap.get(beanName);
if(instance == null) {continue; } Method m =null;
try {
m = instance.getClass().getMethod(definition.getDestroyMethodName(), null);
m.invoke(instance, null);
} catch(NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { ex.printStackTrace(); }}}}Copy the code
The whole code implementation:
/ * * *@ClassName DeafultBeanFactory
* @Description: Bean factory implementation class *@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public class DefaultBeanFactory implements BeanFactory.BeanDefinitionRegistry.Closeable {
/** Bean defines cache */
private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
/** Bean cache */
private Map<String, Object> beanMap = new ConcurrentHashMap<>();
@Override
public Object getBean(String beanName) throws Exception {
return doGetBean(beanName);
}
private Object doGetBean(String beanName) throws Exception {
// Check whether the beanName object has been created
Object bean = beanMap.get(beanName);
if(bean ! =null) {
return bean;
}
BeanDefinition bd = beanDefinitionMap.get(beanName);
Objects.requireNonNull(bd, "Can't recruit ["+beanName+"> < span style =" max-width: 100%; clear: both;); Class<? > beanClass = bd.getBeanClass();if(beanClass ! =null) {
// Build objects using constructors
if (StringUtils.isBlank(bd.getFactoryMethodName())) {
bean = createBeanByConstructor(bd);
} else { // Build objects from static factoriesbean = createBeanByStaticFactory(bd); }}else { // Build objects from member factories
bean = createBeanByFactoryBean(bd);
}
// Start the bean's life cycle
if (StringUtils.isNotBlank(bd.getInitMethodName())) {
doInitMethod(bean, bd);
}
// Processing of singleton beans
if (bd.isSingleton()) {
beanMap.put(beanName, bean);
}
return bean;
}
/** bean */
private void doInitMethod(Object bean, BeanDefinition bd) throws Exception {
Method method = bean.getClass().getMethod(bd.getInitMethodName(), null);
method.invoke(bean, null);
}
/** Build objects from member factories */
private Object createBeanByFactoryBean(BeanDefinition bd) throws Exception {
// Get the factory bean name
String factoryBeanName = bd.getFactoryBeanName();
// Get the factory bean
Object factoryBean = getBean(factoryBeanName);
// Get the factory method
String factoryMethodName = bd.getFactoryMethodName();
Method method = factoryBean.getClass().getMethod(factoryMethodName, null);
Object object = method.invoke(factoryBean, null);
return object;
}
/** Build objects from static factories */
private Object createBeanByStaticFactory(BeanDefinition bd) throws Exception {
// Get the factory class nameClass<? > type = bd.getBeanClass();// Get the factory method name
String factoryMethodName = bd.getFactoryMethodName();
Method method = type.getMethod(factoryMethodName, null);
Object object = method.invoke(type, null);
return object;
}
/** Build the object */ from the constructor
private Object createBeanByConstructor(BeanDefinition bd) throws Exception {
// Get the class nameClass<? > type = bd.getBeanClass();// Instantiate the bean
Object bean = type.newInstance();
return bean;
}
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionRegisterException {
Objects.requireNonNull(beanName, "BeanName needs to be specified to register beans");
Objects.requireNonNull(beanDefinition, "BeanDefinition needs to be specified to register beans");
if(! beanDefinition.validate()) {throw new BeanDefinitionRegisterException("The name is ["+beanName+"】 beanName is not legal," + beanDefinition);
}
if (containsBeanDefinition(beanName)) {
throw new BeanDefinitionRegisterException("The name is ["+beanName+"] beanName has been registered," + beanName);
}
beanDefinitionMap.put(beanName, beanDefinition);
}
@Override
public BeanDefinition getBeanDefinition(String beanName) {
return beanDefinitionMap.get(beanName);
}
@Override
public boolean containsBeanDefinition(String beanName) {
return beanDefinitionMap.containsKey(beanName);
}
@Override
public void close(a) throws IOException {
// Execute the destruction method for the singleton Bean
for(Map.Entry<String, BeanDefinition> e : beanDefinitionMap.entrySet()) {
String beanName = e.getKey();
BeanDefinition definition = e.getValue();
if(definition.isSingleton() && StringUtils.isNotBlank(definition.getDestroyMethodName())) {
Object instance = beanMap.get(beanName);
if(instance == null) {continue; } Method m =null;
try {
m = instance.getClass().getMethod(definition.getDestroyMethodName(), null);
m.invoke(instance, null);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
ex.printStackTrace();
}
}
}
}
}
Copy the code
Test the
public interface Boy {
void sayLove(a);
}
Copy the code
Lad implementation classes: mainly validation via new
public class Lad implements Boy {
@Override
public void sayLove(a) {
System.out.println("I love you, honey!+ hashCode());
}
// Initialize method
public void init(a) {
System.out.println("God give me a date!");
}
// Destruction method
public void destroy(a) {
System.out.println("Since ancient times love spare hate, this hate continuous no unique period!"); }}Copy the code
BoyFactory class: Validates static factory methods
public class BoyFactory {
public static Boy getBean(a) {
return newLad(); }}Copy the code
BoyFactoryBean implementation class: validates member factory methods
public class BoyFactoryBean {
public Boy buildBoy(a) {
return new Boy() {
@Override
public void sayLove(a) {
System.out.println("I love you, girl!"+ hashCode()); }}; }}Copy the code
The test class:
/ * * *@ClassName Test
* @Description: Test class *@Author TR
* @Date 2021/3/25
* @VersionV1.0 * /
public class TestDemo {
static DefaultBeanFactory factory = new DefaultBeanFactory();
/** Test constructor registration Bean */
@Test
public void testRegister(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(Lad.class);
// Set it to a singleton
definition.setScope(BeanDefinition.SCOPE_SINGLETON);
// Set the initialization method
definition.setInitMethodName("init");
// Set the destruction method
definition.setDestroyMethodName("destroy");
// Register the bean definition
factory.registerBeanDefinition("lad", definition);
}
/** Test the static factory registered Bean */
@Test
public void testRegisterStaticFactoryMethod(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
/ / set beanClass
definition.setBeanClass(BoyFactory.class);
// Set the factory method name
definition.setFactoryMethodName("getBean");
// Register the bean definition
factory.registerBeanDefinition("staticFactoryBoy", definition);
}
/** Test member method registration Bean */
@Test
public void testRegisterFactoryMethod(a) throws Exception {
GenericBeanDefinition definition = new GenericBeanDefinition();
// First get the factory Bean
definition.setBeanClass(BoyFactoryBean.class);
// Name of the factory Bean
String fBeanName = "boyFactoryBean";
// Register the factory Bean definition
factory.registerBeanDefinition(fBeanName, definition);
// Then set the factory method
definition = new GenericBeanDefinition();
// Set the name of the factory Bean
definition.setFactoryBeanName(fBeanName);
// Set the factory method
definition.setFactoryMethodName("buildBoy");
// Set this parameter to multiple
definition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
// Register the bean definition
factory.registerBeanDefinition("factoryBoy", definition);
}
@AfterClass
public static void testGetBean(a) throws Exception {
System.out.println("Constructor mode ------------");
for (int i = 0; i < 3; i++) {
Boy boy = (Boy) factory.getBean("lad");
boy.sayLove();
}
System.out.println("Static factory method approach ------------");
for (int i = 0; i < 3; i++) {
Boy ab = (Boy) factory.getBean("staticFactoryBoy");
ab.sayLove();
}
System.out.println("Factory method method ------------");
for (int i = 0; i < 3; i++) {
Boy ab = (Boy) factory.getBean("factoryBoy"); ab.sayLove(); } factory.close(); }}Copy the code
Output after execution:
You can see that the constructor gets a Bean that has the same hashCode, that is, a singleton; Member methods are set up for multiple instances and see that hashCode is different
This is the end of the handwritten IOC container. I hope this article will give you a deeper understanding of Spring’s IOC. Thank you for reading!
Links to other knowledge points in this article:
Nuwa creates a Thought-provoking Java design pattern: Factory Pattern
There is only one of your Java design patterns: the singleton pattern