I\'m a little confused as to how the inversion of control (IoC
) works in Spring
.
Say I have a service class called UserServic
Standard way:
@RestController
public class Main {
UserService userService;
public Main(){
userService = new UserServiceImpl();
}
@GetMapping("/")
public String index(){
return userService.print("Example test");
}
}
User service interface:
public interface UserService {
String print(String text);
}
UserServiceImpl class:
public class UserServiceImpl implements UserService {
@Override
public String print(String text) {
return text + " UserServiceImpl";
}
}
Output: Example test UserServiceImpl
That is a great example of tight coupled classes, bad design example and there will be problem with testing (PowerMockito is also bad).
Now let's take a look at SpringBoot dependency injection, nice example of loose coupling:
Interface remains the same,
Main class:
@RestController
public class Main {
UserService userService;
@Autowired
public Main(UserService userService){
this.userService = userService;
}
@GetMapping("/")
public String index(){
return userService.print("Example test");
}
}
ServiceUserImpl class:
@Component
public class UserServiceImpl implements UserService {
@Override
public String print(String text) {
return text + " UserServiceImpl";
}
}
Output: Example test UserServiceImpl
and now it's easy to write test:
@RunWith(MockitoJUnitRunner.class)
public class MainTest {
@Mock
UserService userService;
@Test
public void indexTest() {
when(userService.print("Example test")).thenReturn("Example test UserServiceImpl");
String result = new Main(userService).index();
assertEquals(result, "Example test UserServiceImpl");
}
}
I showed @Autowired
annotation on constructor but it can also be used on setter or field.
First, and most important - all Spring beans are managed - they "live" inside a container, called "application context".
Second, each application has an entry point to that context. Web applications have a Servlet, JSF uses a el-resolver, etc. Also, there is a place where the application context is bootstrapped and all beans - autowired. In web applications this can be a startup listener.
Autowiring happens by placing an instance of one bean into the desired field in an instance of another bean. Both classes should be beans, i.e. they should be defined to live in the application context.
What is "living" in the application context? This means that the context instantiates the objects, not you. I.e. - you never make new UserServiceImpl()
- the container finds each injection point and sets an instance there.
In your controllers, you just have the following:
@Controller // Defines that this class is a spring bean
@RequestMapping("/users")
public class SomeController {
// Tells the application context to inject an instance of UserService here
@Autowired
private UserService userService;
@RequestMapping("/login")
public void login(@RequestParam("username") String username,
@RequestParam("password") String password) {
// The UserServiceImpl is already injected and you can use it
userService.login(username, password);
}
}
A few notes:
applicationContext.xml
you should enable the <context:component-scan>
so that classes are scanned for the @Controller
, @Service
, etc. annotations.UserServiceImpl
should also be defined as bean - either using <bean id=".." class="..">
or using the @Service
annotation. Since it will be the only implementor of UserService
, it will be injected.@Autowired
annotation, Spring can use XML-configurable autowiring. In that case all fields that have a name or type that matches with an existing bean automatically get a bean injected. In fact, that was the initial idea of autowiring - to have fields injected with dependencies without any configuration. Other annotations like @Inject
, @Resource
can also be used.There are 3 ways you can create an instance using @Autowired
.
1. @Autowired
on Properties
The annotation can be used directly on properties, therefore eliminating the need for getters and setters:
@Component("userService")
public class UserService {
public String getName() {
return "service name";
}
}
@Component
public class UserController {
@Autowired
UserService userService
}
In the above example, Spring looks for and injects userService
when UserController
is created.
2. @Autowired
on Setters
The @Autowired
annotation can be used on setter methods. In the below example, when the annotation is used on the setter method, the setter method is called with the instance of userService
when UserController
is created:
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}
3. @Autowired
on Constructors
The @Autowired
annotation can also be used on constructors. In the below example, when the annotation is used on a constructor, an instance of userService
is injected as an argument to the constructor when UserController
is created:
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService= userService;
}
}
Depends on whether you want the annotations route or the bean XML definition route.
Say you had the beans defined in your applicationContext.xml
:
<beans ...>
<bean id="userService" class="com.foo.UserServiceImpl"/>
<bean id="fooController" class="com.foo.FooController"/>
</beans>
The autowiring happens when the application starts up. So, in fooController
, which for arguments sake wants to use the UserServiceImpl
class, you'd annotate it as follows:
public class FooController {
// You could also annotate the setUserService method instead of this
@Autowired
private UserService userService;
// rest of class goes here
}
When it sees @Autowired
, Spring will look for a class that matches the property in the applicationContext
, and inject it automatically. If you have more than one UserService
bean, then you'll have to qualify which one it should use.
If you do the following:
UserService service = new UserServiceImpl();
It will not pick up the @Autowired
unless you set it yourself.
In simple words Autowiring, wiring links automatically, now comes the question who does this and which kind of wiring. Answer is: Container does this and Secondary type of wiring is supported, primitives need to be done manually.
Question: How container know what type of wiring ?
Answer: We define it as byType,byName,constructor.
Question: Is there are way we do not define type of autowiring ?
Answer: Yes, it's there by doing one annotation, @Autowired.
Question: But how system know, I need to pick this type of secondary data ?
Answer: You will provide that data in you spring.xml file or by using sterotype annotations to your class so that container can themselves create the objects for you.