Following Toby's Spring 3.1 in Spring Boot : Chapter 1 Objects and Dependencies - 1.4 Inversion of Control (IoC)
Dev environment - OS : mac - STS : 4.0.1 - MySQL : Server version: 8.0.13 Homebrew - Framework : proceeding with a jar only, keeping dependencies as minimal as possible Related links - Korean Spring User Group homepage http://www.ksug.org/ - Korean Spring User Group Q&A https://groups.google.com/forum/#!topic/ksug/13vB4tCFqrI - 2017 Spring Camp https://www.youtube.com/playlist?list=PLdHtZnJh1KdZ6NDO9zc9hF4tONDLTSEUV |
Chapter 1 Objects and Dependencies
1.4 Inversion of Control (IoC)
The term is 'Inversion of Control'. It's a concept that's been around well before Spring. Reflecting on it, the book suggests we improve UserDao a bit further.
1.4.0 Previously~
So far we've been refactoring the awful DAO. But honestly there's one thing we glossed over — that fellow we just made and crowned 'client', UserDaoTest. For UserDao to stand on its own, UserDaoTest inherited all of that earlier burden. But, here's the thing.
UserDaoTest's main() was originally created for testing while building the awful DAO — it wasn't really, really, really meant to be what it ended up as.
So the class we newly made as a 'client' is now playing both the role of client AND the role of testing whether UserDao works.
1.4.1 Object Factory
So let's separate responsibilities or concerns that are different in nature. I suspect this is where IoC gets used... let's take a look.
OK then, what we'll separate this time is
(1) creating the objects of UserDao and the ConnectionMaker implementation class
(2) building the relationship in which those two objects connect and get used together.
1. Enter, the factory
It decides how an object is created and returns that object. (But don't confuse it with the Abstract Factory or Factory Method design patterns — so the book says, though I don't know enough yet to be confused. ;> )
Create the factory class DaoFactory to take on the separated responsibility.
1) Code
(1) DaoFactory
package springbook.user.dao;
import java.sql.SQLException;
import springbook.user.domain.User;
public class UserDaoTest {
/**
* Toby's Spring 3.1 example walkthrough
* Chapter 1 - 03 DAO extension — introducing interfaces + restructuring around runtime object relationships
*
* Create a class that plays a client-only role: without touching UserDao at all,
* every consumer can extend the DB connection feature to their satisfaction.
*
* @since 2019.02.20
* @author Byun Chan-woo http://normalstory.tistory.com
* @param args
* @throws SQLException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws ClassNotFoundException, SQLException {
/**
* Toby's Spring 3.1 example walkthrough
* Chapter 1 - 04 Inversion of Control (IoC) — separating the client role
*
* Move the previous ConnectionMaker code -> into DaoFactory
*
* Then, through the newly delegated factory's userDao() method,
* obtain the initialized userDao object
*
* @since 2019.02.21
*/
UserDao dao = new DaoFactory().userDao(); // <- this is the only part that changed ~~~
User user = new User();
user.setId("whiteship8"); // <- this is the PK, so it needs to be changed often
user.setName("Baek Gi-seon");
user.setPassword("married");
dao.add(user);
System.out.println(user.getId() + " registration successful");
User user2 = dao.get(user.getId());
System.out.println(user2.getName());
System.out.println(user2.getPassword());
System.out.println(user2.getId()+" lookup successful");
}
}
(2) The newly created class that plays the factory role —
the code of the DaoFactory class that, in place of the original class, sets up the userDao object for UserDaoTest.
package springbook.user.dao; // <- the package declaration tells you where the newly created class lives.
/**
* Toby's Spring 3.1 example walkthrough
* Chapter 1 - 04 Inversion of Control (IoC) — separating the client role
*
* @since 2019.02.21
* @author Byun Chan-woo http://normalstory.tistory.com
*/
public class DaoFactory {
/**
* The factory class's method
* decides how to create the userDao-type object and how to set it up.
* The code below is exactly the code that used to live in UserDaoTest — since it inherited that role.
* @return userDao
*/
public UserDao userDao() {
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
}
2) Result: success ~
export_1.4.1 Object Factory :
study_spring190221_1.zip
Download
2) If you already imported files from a previous post, please delete or rename the previous project before importing again. |
2. The factory as a blueprint (in the sense of 'qualification' ;)
1) Structure
2) Interpretation
(1) UserDao and ConnectionMaker handle the core logic — the components that actually carry the logic.
DaoFactory takes on the role of composing these applications and defining their relationships — the blueprint that lays out the structure and relationships of the components.
(2) When shipping to company D, you deliver UserDao, ConnectionMaker along with DaoFactory.
The core code of UserDao is safely preserved, and by shipping the DaoFactory code, the DB-connection approach can be freely extended.
What's more meaningful is that we've separated the objects that serve as components from the object that decides the application structure.
1.4.2 Using the Object Factory
It's not over yet.
The free extensibility of DaoFactory has opened up the possibility of a new annoyance.
package springbook.user.dao; // <- the package declaration tells you where the newly created class lives.
/**
* Toby's Spring 3.1 example walkthrough
* Chapter 1 - 04 Inversion of Control (IoC) — using an object factory
*
* @since 2019.02.21
* @author Byun Chan-woo http://normalstory.tistory.com
*/
public class DaoFactory {
/**
* A new challenge that emerged from the factory class's extensibility
*/
public UserDao userDao() {
ConnectionMaker connectionMaker = new DConnectionMaker();
UserDao userDao = new UserDao(connectionMaker);
return userDao;
}
public AccountDao accountDao() {
return new accountDao(new DConnectionMaker()); // same form as the code above, just collapsed to a single line.
}
public MessageDao messageDao() {
return new messageDao(new DConnectionMaker());
}
}
As more Daos get added to the factory, new DConnectionMaker() — the part instantiating the ConnectionMaker implementation class — keeps appearing over and over (= code duplication). It's the same pattern of reasoning we saw in the very first example where we pulled getConnection() out of the awful DAO. In other words, extensibility has opened the door to code like the following.
package springbook.user.dao;
/**
* Toby's Spring 3.1 example walkthrough
* Chapter 1 - 04 Inversion of Control (IoC) — separating the client role
*
* @since 2019.02.21
* @author Byun Chan-woo http://normalstory.tistory.com
*/
public class DaoFactory {
/**
* The factory class's method
* decides how to create the userDao-type object and how to set it up.
* @return userDao
*/
public UserDao userDao() {
// ConnectionMaker connectionMaker = new DConnectionMaker();
// (below) extracted into a standalone method
UserDao userDao = new UserDao(connectionMaker()); // convert the variable into a method call
return userDao;
}
/**
* Extract into a standalone method so the many individual factory methods do not have to write it out one by one,
*/
public ConnectionMaker connectionMaker() {
return new DConnectionMaker(); // the connectionMaker-type object-creation code, separated to remove duplication
}
}
1.4.3 Flipping the Control Relationship by Handing Over Control
Finally. The master of reversals, IoC ; D Handing over control can be described as flipping the structure of the program's control flow.
1. Not reversed
Think back to the first awful-DAO example: the test main() method inside UserDao ( the user or the entry point ) directly creates the objects of the classes inside it and calls the methods of those objects. Likewise, UserDao itself picks which ConnectionMaker implementation it uses, creates that object when needed, and so on — the structure where every kind of work is controlled by the calling side.
2. Reversed
Inversion of control flips all of this. Here, an object doesn't choose the object it's going to use. It doesn't create that object either. It also doesn't know where it came from or where it's going. (How much like a human... ah, life ;>)
It delegates all control authority to some other entity, not to itself. So,
except for the entry point like main() that kicks off the program, every project ends up being decided and built by a special object that has been handed the control authority.
Case 1. The roles of servlets, JSPs, EJBs — things that run inside a container — already embody the IoC concept. None of them have a main() method you can directly execute; instead, a container that holds control authority over them creates the servlet class's objects at the right moment and calls the methods inside.
Case 2. Frameworks are another representative technology that applies IoC.
- Wrong view: a pre-made semi-finished product, a collection of abstract libraries ready to be extended.
- Helpful view: 1) Library: the application code directly controls the application flow. It uses the library actively only when needed. (active)
2) Framework: the application code is used by the framework. (passive)
- Right view: clear inversion of control is applied. The application code runs passively within the skeleton the framework has laid out.
Case 3. We've done it ourselves already. Remember the second example — the subclass inheriting from the abstract UserDao? That subclass implements getConnection(). But at that moment, getConnection() has no idea when it will be used. In other words, it hands its control over to the upper template method and simply gets called (passively) when needed — that is exactly inversion of control. It's not decided inside the subclass. You just implement the 'make a DB connection' feature, and the template method of the superclass UserDao — add(), get(), etc. — calls into it when necessary. So in this example the template method can be said to be a design pattern that solves the problem using the concept of inversion of control.
Case 4. Not done yet. IoC is also used in DaoFactory. The control authority to decide the ConnectionMaker implementation and create the object used to belong to UserDao; but once the factory class was created, UserDao — and not just UserDao itself but also the objects it uses — ends up passively receiving whatever DaoFactory produces and supplies.
Naturally, the process we took to separate concerns, split responsibilities, and build a flexible, extensible structure turns out to have been the work of applying IoC. (I was wondering when it would show up... turns out what we've been doing was IoC.)
3. What inversion of control means
For inversion of control, we need an entity that plays the role our DaoFactory did. Even without Spring — said to be the representative IoC framework — you can build IoC-style design and code using concepts you'd find even in design patterns. ( Ah, so this is what Spring is!! )
All you need is an entity that can govern the creation, relationship setup, usage, and lifecycle management of application components — a framework or a container. The DaoFactory we built earlier could be called the most basic IoC container, or IoC framework, at the object level.
Heart racing — from the next example, let's get to Spring IoC. This is going to be good, right? ;D
