Controls the type of inversion
Inversion of control (IOC) aims to provide a simpler mechanism to set the dependencies of components and manage them throughout the life cycle. Generally, control inversion can be divided into two subtypes: dependency injection (DI) and dependency lookup (DL). These subtypes can be further decomposed into the specific implementation of IOC services
1. Dependency search
1.1 dependent pull
Dependency Pull refers to extracting dependencies from the registry as needed. The following code shows the Spring based Dependency Pull
public class DependencyPull { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring/app-context.xml"); ctx.getBean("renderer", MessageRenderer.class); } }
1.2 context dependent search
Contextual Dependency lookup (CDL) also belongs to the subtype of dependency lookup, which is somewhat similar to dependency pull. However, in CDL, the lookup is performed for the container for managing resources, which is usually provided by the application server or framework (Tomcat, JBoss, Spring), For example, the following code shows a container interface that provides a dependency lookup service
public interface Container { // Get the corresponding dependencies according to the key Object getDependency(String key); }
Let the components work through the CDL interface
public interface ManagedComponent { void performLookup(Container container); }
The component needs to implement this interface. When the Container is ready to pass dependencies to the component, it will call the performLookup() method of each component in turn, and then the component can use the Container interface to find the required dependencies
public class ContextualizedDependencyLookup implements ManagedComponent { private Dependency dependency; @Override public void performLookup(Container container) { this.dependency = (Dependency) container.getDependency("myDependency"); } @Override public String toString() { return dependency.toString(); } }
2. Dependency injection
2.1 constructor injection
Constructor dependency injection occurs when a dependency is provided in a component's constructor
public class ConstructorInjection { private Dependency dependency; public ConstructorInjection(Dependency dependency) { this.dependency = dependency; } @Override public String toString() { return dependency.toString(); } }
2.2 setter function injection
The Ioc container injects the dependencies of components through JavaBean style setter methods
public class SetterInjection { private Dependency dependency; public void setDependency(Dependency dependency) { this.dependency = dependency; } @Override public String toString() { return dependency.toString(); } }
In Spring, another injection type called field injection is also supported. This injection type will be introduced later when you learn to use @ Autowire annotation for automatic assembly
Inversion of control in Spring
1. Bean and BeanFactory
The core of Spring's dependency injection container is BeanFactory, which is responsible for managing components, including dependencies and their life cycle. If we want to obtain a component (Bean), we must create an instance that implements the BeanFactory interface and configure it
Although BeanFactory can be configured programmatically, it is more common to use a configuration file to configure it externally. Bean configuration can be represented by an instance of a class that implements the BeanDefinition interface. For any BeanFactory implementation class that implements the BeanDefinitionReader interface, you can use PropertiesBeanDefinitionReader or XmlBeanDefinitionReader to read BeanDefinition data from the configuration file
Define a set of interfaces:
public interface Oracle { String output(); } public class OracleImpl implements Oracle { @Override public String output() { return "hello world"; } }
Next, let's take a look at how Spring's BeanFactory is initialized and used to obtain Bean instances
public class XmlConfigWithBeanFactory { public static void main(String[] args) { // DefaultListableBeanFactory is one of the two main BeanFactory implementations provided by Spring DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader rdr = new XmlBeanDefinitionReader(factory); // Use XmlBeanDefinitionReader to read BeanDefinition information from XML file rdr.loadBeanDefinitions(new ClassPathResource("spring/xml-bean-factory-config.xml")); // Use the name oracle configured in the XML configuration file to get the bean Oracle oracle = (Oracle) factory.getBean("oracle"); System.out.println(oracle.getInfo()); } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="oracle" name="oracle" class="com.example.OracleImpl"/> </beans>
ApplicationContext interface is an extension of BeanFactory. In addition to DI services, it also provides other services such as transaction and AOP. When developing Spring based applications, it is recommended to interact with Spring through the ApplicationContext interface
2. Set Spring configuration
2.1 XML configuration
For XML configuration, you need to declare the basic namespace information provided by Spring required by the application. The configuration shown below only declares the namespace used to define the bean
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="provider" class="com.example.HelloWorldMessageProvider"/> <bean id="render" class="com.example.StandardOutMessageRender" p:messageProvider-ref="provider"/> </beans>
2.2 annotation configuration
To use Spring's annotation support in your application, you need to declare it in the XML configuration
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.example" /> </beans>
The < context: Component scan > tag tells Spring to scan the code to find the beans injected with @ Component and other annotations, and beans that support the use of @ Autowire and other annotations under the specified package (and all its sub packages)
2.3 Java configuration
The Configuration class uses @ Configuration annotation and contains methods annotated with @ bean. These methods are directly called by the IOC container to instantiate the bean, and the bean name is the same as the name of the method used to create it
@Configuration public class HelloWorldConfiguration { @Bean public MessageProvider provider() { return new HelloWorldMessageProvider(); } @Bean public MessageRender render() { StandardOutMessageRender render = new StandardOutMessageRender(); render.setMessageProvider(provider()); return render; } }
If you want to read configuration information from this class, you need a different ApplicationContext implementation
public class HelloWorldSpringAnnotated { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfiguration.class); MessageRender render = ctx.getBean("render", MessageRender.class); render.render(); } }
3. setter injection
To configure setter injection using XML configuration, you need to specify the < property > tag under the < bean > tag and inject a dependency for it
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="provider" class="com.example.HelloWorldMessageProvider"/> <bean id="render" class="com.example.StandardOutMessageRender"> <property name="messageProvider" ref="provider"/> </bean> </beans>
If you use annotations, you only need to add an @ Autowired annotation to the setter method
@Service("render") public class StandardOutMessageRender implements MessageRender { ... @Override @Autowired public void setMessageProvider(MessageProvider messageProvider) { this.messageProvider = messageProvider; } }
4. Constructor injection
public class ConfigurableMessageProvider implements MessageProvider { private String message; public ConfigurableMessageProvider(String message) { this.message = message; } @Override public String getMessage() { return null; } }
Using XML injection
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="oracle" name="oracle" class="com.example.OracleImpl"/> <!-- use <constructor-arg> sign --> <bean id="messageProvider" class="com.example.ConfigurableMessageProvider"> <constructor-arg value="hello world" /> </bean> <!-- use c Namespace --> <bean id="provider" class="com.example.ConfigurableMessageProvider" c:message="hello world"/> </beans>
Use annotation method
@Service public class ConfigurableMessageProvider implements MessageProvider { private String message; @Autowired public ConfigurableMessageProvider( @Value("hello world") String message) { this.message = message; } @Override public String getMessage() { return null; } }