Inversion of Control(IoC) is one of the most critical concepts in advanced Java programming design as well as the basics of the Spring framework. Therefore, understanding the IoC principle and actual implementation is essential. This tutorial will use a simple example to explain how IoC works in Java and discuss the advantages of using IoC.
1. A Simple Example of IoC
Let's assume we want to create a typical "3-tier architecture" program. Our first step is making a presentation layer using servlet:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
//新建服务层
HelloServiceImp service = new HelloServiceImp();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write(service.findList().toString());
}
}
This code is straightforward; we create a service component that will respond to our clients' requests by sending back sentences. The second step is to develop our data tier.
public class HelloDaoImp implements HelloDao {
@Override
public List<String> findList() {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
return list;
}
}
What we are doing here is retrieving a list from the database. For demonstration purposes, we simplify the code to return a list directly. Since the core part of implementing the IoC idea in this tutorial is based on the service layer, I will discuss how to achieve IoC in the next part.
2. Implementing IoC
Traditionally, to connect the data layer with the service layer, we need to "new" an instance of DAO in the service layer like this:
public class HelloServiceImp implements HelloService {
HelloDao dao = new HelloDaoImp();
@Override
public List<String> findList() {
return dao.findList();
}
}
However, what if we suddenly want to change our database from MySQL to Oracle? Or what if we are going to change the Dao? In these scenarios, we have to change the instance of Dao, which means we need to change our original code not only in Dao itself but in service layers also. Thus, the concept of IoC is helpful in such a situation. The basic idea of IoC is to provide upper classes the control of implementing and choosing classes it needs instead of applying the opposite direction. Here, we can create a control class named "BeanFactory" as an example.
helloDao=com.tencent.dao.imp.HelloDaoImp
This is the properties file "factory.properties" used in our FactoryBean class.
public class BeanFactory {
private static Properties properties;
private static Map<String,Object> cache = new HashMap<>();
static {
properties=new Properties();
try {
//从properties文件中返回我们需要的类
properties.load(BeanFactory.class.getClassLoader().getResourceAsStream("factory.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Object getDao(String name){
if (!cache.containsKey(name)) {
try {
String value = properties.getProperty(name);
Class clazz = Class.forName(value);
Object obj = clazz.getConstructor().newInstance();
cache.put(name,obj);
} catch (Exception e) {
e.printStackTrace();
}
}
return cache.get(name);
}
}
The Beanfactory layer will return the class we want from a reference source(XML, Properties...). Then in the service layer, we will create classes we want by using this factory instead of "new" an instance.
public class HelloServiceImp implements HelloService {
HelloDao dao = (HelloDao) BeanFactory.getDao("HelloDao");
@Override
public List<String> findList() {
return dao.findList();
}
}
The code above is our rewrite service layer. Using IoC like this, if we want to change the Dao layer, we can just change our properties file instead of modifying the Java programming code. This is the central idea of IoC.
Conclusion:
IoC is a principle that transfer the right to create and save objects to containers or framework. Here are some benefits of applying IoC from the Spring official website:
- decoupling the execution of a task from its implementation
- making it easier to switch between different implementations
- greater modularity of a program
- greater ease in testing a program by isolating a component or mocking its dependencies, and allowing components to communicate through contract