IOC analysis:
IOC– Inversion of control, also known as dependency inversion.
How to understand inversion of control?
Inversion: The retrieval of dependent objects is reversed, to be created by itself, instead of being fetched from the IOC container.Copy the code
Benefits:
1. The code is more concise, and there is no need to go to new objects needed. 2. Easy AOP enhancement (you can't AOP without IOC)Copy the code
What does an IOC container do?
Responsible for creating, managing class instances and providing instances to usersCopy the code
An example of a factory pattern is an IOC container, also known as a Bean factory
IOC Design and Implementation
What the IOC container does: Create and manage beans, which are a factory responsible for supplying Bean instances to the outside worldCopy the code
Bean: component, object of class
Q1: What behavior should it have (external interface)?
A: Provide the Bean instance externally, the getBean() methodCopy the code
Q2: Does this getBean() method require arguments? How many, and what type?
A: In the simple factory model, when the factory can create many types of products, it needs to tell the factory if it needs A certain type of productCopy the code
Q3: What is the type of return value of this getBean() method?
A: Various types of beans can only be Object<br><br>Copy the code
At this point we can program the BeanFactory interface
public interface BeanFactory { Object getBean(String name) throws Exception; }Copy the code
How does the Bean factory know how to create beans?
We can give it a definition registration interface, let the bean define the incoming bean factory, tell the factory what type of bean to createCopy the code
The Bean defines the registration interface
public interface BeanDefinitionRegistry {}
Copy the code
Q1: What methods should be defined in the bean definition registration interface?
Register to get the bean definitionCopy the code
Q2: How is the registered bean definition information distinguished?
Each Bean has a separate nameCopy the code
At this point we can codify the BeanDefinitionRegistry interface
Public interface BeanDefinitionRegistry {/** * Register Bean * @param beanName * @param beanDefinition * @throws Exception */ void registerBeanDefinition(String beanName,BeanDefinition beanDefinition) throws Exception; BeanDefinition getBeanDefinition(String beanName); BeanDefinition getBeanname (String beanName); * @param beanName * @return */ Boolean containsBeanDefinition(String beanName); }Copy the code
Bean definition
Q1: What is the purpose of the bean definition?
Tell the bean factory how to create a certain class of beanCopy the code
Q2: What are the ways to get an instance of a class?
2. Factory method (static, member method)Copy the code
Q3: What information does the bean factory need to get to help us create beans?
1. New constructor (need to know class name) 2. Static factory method (need to know factory class name, factory method name) 3. Member factory methods (need to know factory class name --> factory bean name, factory method name)Copy the code
Q4: Whether a new instance is created each time a bean instance is fetched from the bean factory
No, because there are cases that require singletonsCopy the code
Q5: Bean definitions are used by bean factories to create beans. What methods should the bean definition interface provide to bean factories?
GetBeanClass () 2. GetFactoryMethodName () 3. Get the factory bean name :getFactoryBeanName() 4 Is it a singleton :getScope(){isSingleton(); isPrototype(); }Copy the code
Q6: Class objects are given to the IOC container to manage. Is there anything else that might be done in the life cycle of a class object?
Some destruction logic (such as freeing resources) that may occur during initial destruction that may be required after creation is provided by the bean definition with user-customizable initialization and destruction methods (getInitMethodName(),getDestroyMethodName())Copy the code
Write the bean definition interface code:
public interface BeanDefinition { String SCOPE_SINGLETON = "singleton"; String SCOPE_PROTOTYPE = "prototype"; Class<? > getBeanClass(); String getScope(); boolean isSingleton(); boolean isPrototype(); String getFactoryBeanName(); String getFactoryMethodName(); String getInitMethodName(); String getDestoryMethodName(); Default Boolean validate(){// If the class is not specified, the factory bean or method is not specified (this.getBeanClass()==null){ if(StringUtils.isBlank(getFactoryBeanName())||StringUtils.isBlank(getFactoryMethodName())){ return false; If (this.getBeanclass ()! =null && StringUtils.isNotBlank(getFactoryBeanName())){ return false; } return true; }}Copy the code
With the interface in place, let’s write a generic bean definition (using the Lombok plug-in here)
import lombok.Data; import org.apache.commons.lang.StringUtils; @Data public class GeneralBeanDefinition implements BeanDefinition{ private Class<? > beanClass; private String scope = BeanDefinition.SCOPE_SINGLETON; private String factoryBeanName; private String factoryMethodName; private String initMethodName; private String destroyMethodName; public void setScope(String scope) { if (StringUtils.isNotBlank(scope)){ this.scope = scope; } } @Override public Class<? > getBeanClass() { return this.beanClass; } @Override public String getScope() { return this.scope; } @Override public boolean isSingleton() { return BeanDefinition.SCOPE_SINGLETON.equals(this.scope); } @Override public boolean isPrototype() { return BeanDefinition.SCOPE_PROTOTYPE.equals(this.scope); } @Override public String getFactoryBeanName() { return factoryBeanName; } @Override public String getFactoryMethodName() { return factoryMethodName; } @Override public String getInitMethodName() { return this.initMethodName; } @Override public String getDestoryMethodName() { return this.destroyMethodName; }}Copy the code
The next step is to implement a basic DefaultBeanFactory to get it working initially
1. Implement registration of definition information
Q1: How is bean definition information stored?
A:Map
Copy the code
Q2: Can bean definitions be named the same, and how can they be resolved?
A: Directly designed to not be the same nameCopy the code
2. Implement bean factories
Q1: How to store the bean you created for next retrieval?
Map
Copy the code
Q2: What do YOU need to do in the getBean method
Create the bean instance and initialize itCopy the code
With that in mind, let’s simply do a DefaultBeanFactory at this point
public class DefaultBeanFactory implements BeanFactory,BeanDefinitionRegistry, Closeable {// Common-logging package and log4J-API package can be used for private final Log Logger = logFactory.getlog (); Private Map<String,BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256); private Map<String,Object> beanMap = new ConcurrentHashMap<>(256); @Override public void registerBeanDefinition(String beanName, BeanDefinition BeanDefinition) throws Exception {// Check objects.requirenonNULL (beanName," beanName is required to register beans "); Objects.requirenonnull (beanDefinition," beanDefinition is required to register a bean "); // Check whether the incoming bean is valid. BeanDefinition. Validate ()){throw new Exception(" +beanName+"); } if (this.containsBeanDefinition(beanName)){ throw new Exception(" Bean definition with name ["+beanName+"] already exists,"+this.getBeanDefinition(beanName)); } this.beanDefinitionMap.put(beanName,beanDefinition); } @Override public BeanDefinition getBeanDefinition(String beanName) { return this.beanDefinitionMap.get(beanName); } @Override public Boolean containsBeanDefinition(String beanName) { return this.beanDefinitionMap.containsKey(beanName); } @Override public Object getBean(String name) throws Exception { return this.doGetBean(name); } // Use protected to ensure that only subclasses of DefaultBeanFactory can call this method protected Object doGetBean(String) Throws Exception{object.requirenonNULL (beanName,"beanName cannot be empty "); Object instance = beanMap.get(beanName); if (instance ! = null){ return instance; } BeanDefinition beanDefinition = this.getBeanDefinition(beanName); Objects. RequireNonNull (beanDefinition,"beanDefinition can't be empty "); Class<? > type = beanDefinition.getBeanClass(); // Since there are only 3 ways of doing this, and there is no need to extend or modify the code, there is no need to consider using the policy mode if (type! = null){ if (StringUtils.isBlank(beanDefinition.getFactoryMethodName())){ instance = this.createInstanceByConstructor(beanDefinition); } else { instance = this.createInstanceByStaticFactoryMethod(beanDefinition); } }else { instance = this.createInstanceByFactoryBean(beanDefinition); } this.doInit(beanDefinition,instance); if (beanDefinition.isSingleton()){ beanMap.put(beanName,instance); } return instance; } / / private construction methods to create objects Object createInstanceByConstructor (BeanDefinition BeanDefinition) throws IllegalAccessException, InstantiationException { try{ return beanDefinition.getBeanClass().newInstance(); } catch (SecurityException e){logger.error(" beanDefinition"+beanDefinition,e); throw e; }} / / static factory methods (temporarily don't consider parameters) private Object createInstanceByStaticFactoryMethod (BeanDefinition BeanDefinition) throws Exception{ Class<? > type = beanDefinition.getBeanClass(); Method method = type.getMethod(beanDefinition.getFactoryMethodName(),null); return method.invoke(type,null); } / / factory bean methods to create objects (temporary not consider parameters) private Object createInstanceByFactoryBean (BeanDefinition BeanDefinition) throws the Exception { Object factoryBean = this.doGetBean(beanDefinition.getFactoryBeanName()); Method method = factoryBean.getClass().getMethod(beanDefinition.getFactoryMethodName(),null); return method.invoke(factoryBean,null); } private void doInit(BeanDefinition BeanDefinition, Object instance) throws Exception{ if (StringUtils.isNotBlank(beanDefinition.getInitMethodName())){ Method method = instance.getClass().getMethod(beanDefinition.getInitMethodName(),null); method.invoke(instance,null); }} @override public void close() throws IOException {// Execute the singleton destruction method // iterate through the map to fetch all beans and then call the destruction method for each bean (Map.Entry<String,BeanDefinition> entry : this.beanDefinitionMap.entrySet()){ String beanName = entry.getKey(); BeanDefinition beanDefinition = entry.getValue(); if (beanDefinition.isSingleton() && StringUtils.isNotBlank(beanDefinition.getDestoryMethodName())){ Object instance = this.beanMap.get(beanName); try { Method method = instance.getClass().getMethod(beanDefinition.getDestoryMethodName(),null); method.invoke(instance,null); }catch (NoSuchMethodException|SecurityException|IllegalAccessException|IllegalArgumentException|InvocationTargetException e){ Logger. error(" Execution bean["+beanName+"] "+beanDefinition+" destruction method exception ",e); } } } } }Copy the code
Tips: Singleton thread safety is not guaranteed at this time!!
Extension DefaultBeanFactory
Thinking: Can we instantiate singleton beans ahead of time? What are the benefits?
A: Can be instantiated in advance, space for time method, slow start up fast use and thread safetyCopy the code
To implement the ability to instantiate a singleton bean ahead of time, the code is as follows
public class PreBuildBeanFactory extends DefaultBeanFactory{ private Log logger = LogFactory.getLog(getClass()); private List<String> beanNames = new ArrayList<>(); @Override public void registerBeanDefinition(String beanName,BeanDefinition beanDefinition) throws Exception{ super.registerBeanDefinition(beanName,beanDefinition); synchronized (beanNames){ beanNames.add(beanName); Public void preInstantiateSingletons() throws Exception{synchronized (beanNames){for (String name : beanNames){ BeanDefinition beanDefinition = this.getBeanDefinition(name); if (beanDefinition.isSingleton()){ this.doGetBean(name); if (logger.isDebugEnabled()){ logger.debug("preInstantiate:name="+name+" "+beanDefinition); } } } } } }Copy the code
Now that we have a preliminary IOC container, we can do a simple test
Write a bean1 and a bean1 factory, and a test method
Bean1.java
public class Bean1 { public void doSomething(){ System.out.println(System.currentTimeMillis()+" "+this); } public void init(){system.out.println ("bean1 init already executed "); } public void destroy(){system.out.println ("bean1 destroy already "); }}Copy the code
Bean1Factory.java
public class Bean1Factory { public static Bean1 getBean1(){ return new Bean1(); } public Bean1 getOtherBean1(){ return new Bean1(); }}Copy the code
Test case DefaultBeanFactoryTest
public class DefaultBeanFactoryTest { static DefaultBeanFactory defaultBeanFactory = new DefaultBeanFactory(); @Test public void testRegist() throws Exception{ GeneralBeanDefinition generalBeanDefinition = new GeneralBeanDefinition(); generalBeanDefinition.setBeanClass(Bean1.class); generalBeanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON); generalBeanDefinition.setInitMethodName("init"); generalBeanDefinition.setDestroyMethodName("destroy"); defaultBeanFactory.registerBeanDefinition("bean1",generalBeanDefinition); } @Test public void testRegistStaticFactoryMethod() throws Exception{ GeneralBeanDefinition generalBeanDefinition = new GeneralBeanDefinition(); generalBeanDefinition.setBeanClass(Bean1Factory.class); generalBeanDefinition.setFactoryMethodName("getBean1"); defaultBeanFactory.registerBeanDefinition("staticBean1",generalBeanDefinition); } @Test public void testRegistFactoryMethod() throws Exception{ GeneralBeanDefinition generalBeanDefinition = new GeneralBeanDefinition(); generalBeanDefinition.setBeanClass(Bean1Factory.class); String factoryBeanName = "factory"; defaultBeanFactory.registerBeanDefinition(factoryBeanName,generalBeanDefinition); generalBeanDefinition = new GeneralBeanDefinition(); generalBeanDefinition.setFactoryBeanName(factoryBeanName); generalBeanDefinition.setFactoryMethodName("getOtherBean1"); generalBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE); defaultBeanFactory.registerBeanDefinition("factoryBean",generalBeanDefinition); } @afterClass public static void testGetBean() throws Exception{system.out.println (" constructor method ···"); for (int i = 0; i<3; i++){ Bean1 bean1 = (Bean1) defaultBeanFactory.getBean("bean1"); bean1.doSomething(); } system.out.println (" static factory method method ···"); for (int i = 0; i<3; i++){ Bean1 bean1 = (Bean1) defaultBeanFactory.getBean("staticBean1"); bean1.doSomething(); } system.out.println (" factory method method ···"); for (int i = 0; i<3; i++){ Bean1 bean1 = (Bean1) defaultBeanFactory.getBean("factoryBean"); bean1.doSomething(); } defaultBeanFactory.close(); }}Copy the code
This test case runs as follows (note that we set up multiple cases with the factory method, so the bean should be different for each) :
The init of bean1 has been executed 1555370012087 MySpring.Bean1@5e8c92f4 1555370012088 MySpring.Bean1@5e8c92f4 1555370012088 MySpring.Bean1@5e8c92f4 Static factory method method ·· 1555370012088 MySpring.Bean1@50134894 1555370012088 MySpring.Bean1@50134894 1555370012088 MySpring.Bean1@50134894 Factory method method ·· 1555370012089 MySpring.Bean1@2957fcb0 1555370012089 MySpring.Bean1@1376c05c 1555370012090 MySpring.Bean1@51521cc1 Destroy has been performed for Bean1Copy the code