Hello again! As you may know, this post is part of my Spring 5 Fundamentals series, where I will be going through this Pluralsight course, Spring Framework: Spring 5 Fundamentals by Bryan Hansen. If you haven’t read my intro post to this series, click here. If you missed the previous post, it was about how to use Spring in our application using Java, which you’ll need to go through before starting on this one, which will be about scopes and autowiring.
Scopes
There are five scopes available in Spring for us to configure a bean inside our application.
- Singleton – The default configuration
- Prototype – A new bean per request
- Request*
- Session*
- Global*
* Only valid in web-aware Spring projects
Singleton Java Config
A singleton class pattern restricts an instance of a class to only one object. This prevents duplication of objects that should not have multiple instances. As mentioned before, singletons are the default scope of a bean should you not assign it one. This means there will be just one instance of this bean per Spring container or application context.
To add a scope to a bean, we simply need to annotate it using @Scope(value = “scopeTypeHere”).
In our AppConfig file, we’ll add a scope to the first bean by adding the following line below our @Bean annotation. While we can simply type value = “singleton”, it is better practice to use a predefined string to avoid potential mistakes. After making sure to import the appropriate libraries, you should be able to see the definition choices below.
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
Our whole AppConfig file should look like this now.
import org.therenaissance.repository.HibernateSpeakerRepositoryImpl;
import org.therenaissance.repository.SpeakerRepository;
import org.therenaissance.service.SpeakerService;
import org.therenaissance.service.SpeakerServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.beans.factory.config.BeanDefinition;
@Configuration
public class AppConfig {
// Using constructor injection
@Bean(name = "speakerService")
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
public SpeakerService getSpeakerService() {
SpeakerServiceImpl service = new SpeakerServiceImpl(getSpeakerRepository());
return service;
}
@Bean(name = "speakerRepository")
public SpeakerRepository getSpeakerRepository() {
return new HibernateSpeakerRepositoryImpl();
}
}
As a test to make sure our bean is a singleton, we can go back into our Application file and add the following lines. When we run this, it will print out the addresses of our service objects.
public class Application {
public static void main(String args[]) {
ApplicationContext appContext = new AnnotationConfigApplicationContext(AppConfig.class);
SpeakerService service = appContext.getBean("speakerService", SpeakerService.class);
SpeakerService service2 = appContext.getBean("speakerService", SpeakerService.class);
System.out.println(service);
System.out.println(service2);
System.out.println(service.findAll().get(0).getFirstName());
}
}
However, look at what happens when we do that. It prints out the same address! This is because as a singleton, we refer to the same bean instead of creating another instance of one, so our singleton declaration has worked! Hooray!
org.therenaissance.service.SpeakerServiceImpl@636be97c
org.therenaissance.service.SpeakerServiceImpl@636be97c
Dog
Process finished with exit code 0
Prototype Java Config
The prototype pattern guarantees a unique instance per request. With this scope type, each time we request a bean from a container, we’re guaranteed a unique instance. Basically, this is the opposite of a singleton; however, configuring this scope into our bean is practically the same. To see how this works, why don’t change our scope for the getSpeakerService bean into a prototype instead of a singleton.
@Bean(name = "speakerService")
@Scope(value = BeanDefinition.SCOPE_PROTOTYPE)
public SpeakerService getSpeakerService() {
SpeakerServiceImpl service = new SpeakerServiceImpl(getSpeakerRepository());
return service;
}
We’ll run our Application file again, and let’s look at the results. Now we see that addresses are no longer the same, but unique, confirming our scope change has worked!
org.therenaissance.service.SpeakerServiceImpl@50b472aa
org.therenaissance.service.SpeakerServiceImpl@31368b99
Dog
Process finished with exit code 0
Before moving onto the next step, let’s change our scope back to a singleton.
@Bean(name = "speakerService")
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
public SpeakerService getSpeakerService() {
SpeakerServiceImpl service = new SpeakerServiceImpl(getSpeakerRepository());
return service;
}
There we go. 🙂
Web Scopes
Web scopes will be covered more in my Spring MVC series because we have to set up an entire web application to see how they interact with an object.
Again, the other three scopes are the:
- Request scope – Returns a bean per HTTP request, which is similar to a prototype except that it’s for the lifecycle of a bean, which is short, but longer than a prototype where it’s one instance per every time we ask a container for a bean.
- Session scope – Returns a single bean per HTTP session that will remain for as long as the user session is alive.
- GlobalSession scope – Returns a single bean per application. Once accessed, it will be alive for the duration of that application and not just for a single user visit to that app, until the server gets undeployed or rebooted.
Autowired
Autowiring is a technique to reduce the wiring up and configuration of code. This is known as “convention over configuration”.
To autowire our code using the Java configuration, we just need to add a component scan to our configuration file.
@ComponentScan({"org.therenaissance"})
To use autowiring, we simply mark a bean as autowired, using it by name or by instance type.
@Bean
Let’s take a look at a demo for autowiring.
To set up our project to use autowiring correctly, we’ll need to make some changes. Let’s open up SpeakerServiceImpl. We’ll start by adding a new constructor for no arguments, and adding some print statements so we know what’s running. Then we’ll add the import for autowiring as well as indicate that setRepository() is autowired so we can use setter injection.
package org.therenaissance.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.therenaissance.model.Speaker;
import org.therenaissance.repository.SpeakerRepository;
import java.util.List;
public class SpeakerServiceImpl implements SpeakerService {
private SpeakerRepository repository;
public SpeakerServiceImpl() {
System.out.println("SpeakerServiceImpl no args constructor");
}
// Using constructor injection
public SpeakerServiceImpl(SpeakerRepository repository) {
System.out.println("SpeakerServiceImpl repository constructor");
this.repository = repository;
}
@Override
public List<Speaker> findAll() {
return repository.findAll();
}
@Autowired
public void setRepository(SpeakerRepository repository) {
System.out.println("SpeakerServiceImpl setter");
this.repository = repository;
}
}
We will also replace the SpeakerServiceImpl constructor call in AppConfig with an empty parameter version.
import org.therenaissance.repository.HibernateSpeakerRepositoryImpl;
import org.therenaissance.repository.SpeakerRepository;
import org.therenaissance.service.SpeakerService;
import org.therenaissance.service.SpeakerServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.beans.factory.config.BeanDefinition;
@Configuration
public class AppConfig {
// Using constructor injection
@Bean(name = "speakerService")
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
public SpeakerService getSpeakerService() {
SpeakerServiceImpl service = new SpeakerServiceImpl();
return service;
}
@Bean(name = "speakerRepository")
public SpeakerRepository getSpeakerRepository() {
return new HibernateSpeakerRepositoryImpl();
}
}
Because we added @Autowired to our setRepository() method, when we run the app, a new SpeakerServiceImpl will be constructor without any arguments and the SpeakerRepository bean will automatically be injected into the setter. So we’ll see the log statement for setRepository even though we did not call it directly.
When we run Application, this is what we get:
SpeakerServiceImpl no args constructor
SpeakerServiceImpl setter
org.therenaissance.service.SpeakerServiceImpl@6379eb
org.therenaissance.service.SpeakerServiceImpl@6379eb
Dog
Process finished with exit code 0
To recap what we did:
- We changed getSpeakerService() in AppConfig to use no arguments when constructing a new SpeakerServiceImpl service.
- In our SpeakerServiceImpl file and created our no args constructor.
- Specified we wanted setRepository() to be autowired and injected.
Stereotype Annotations
To have our program fully autowired, we’ll need to remove our bean definitions in the AppConfig file. Let’s take a look at some stereotypes:
- @Component – same thing as @Bean
- @Repository – used to denote a class that is being used as a repository object
- @Service – where you would put your business logic
- @Controller – where you would create web or microservices for your application
Let’s now finish wiring up our app. We’ll go back to AppConfig and add the @ComponentScan annotation with the string syntax for an array of the package structures we want to scan for beans to autowire. Let’s also comment out both of our beans, so we’ll essentially have an empty AppConfig class.
import org.therenaissance.repository.HibernateSpeakerRepositoryImpl;
import org.therenaissance.repository.SpeakerRepository;
import org.therenaissance.service.SpeakerService;
import org.therenaissance.service.SpeakerServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Scope;
import org.springframework.beans.factory.config.BeanDefinition;
@Configuration
@ComponentScan({"org.therenaissance"})
public class AppConfig {
/*
// Using constructor injection
@Bean(name = "speakerService")
@Scope(value = BeanDefinition.SCOPE_SINGLETON)
public SpeakerService getSpeakerService() {
//SpeakerServiceImpl service = new SpeakerServiceImpl(getSpeakerRepository());
SpeakerServiceImpl service = new SpeakerServiceImpl();
return service;
}
@Bean(name = "speakerRepository")
public SpeakerRepository getSpeakerRepository() {
return new HibernateSpeakerRepositoryImpl();
}
*/
}
Now we’ll need to make changes to the SpeakerServiceImpl and HibernateSpeakerRepositoryImpl files. In SpeakerServiceImpl, we’ll give the class the @Service annotation, giving it the same name as our bean in the AppConfig file.
package org.therenaissance.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.therenaissance.model.Speaker;
import org.therenaissance.repository.SpeakerRepository;
import java.util.List;
@Service("speakerService")
public class SpeakerServiceImpl implements SpeakerService {
private SpeakerRepository repository;
public SpeakerServiceImpl() {
System.out.println("SpeakerServiceImpl no args constructor");
}
// Using constructor injection
public SpeakerServiceImpl(SpeakerRepository repository) {
System.out.println("SpeakerServiceImpl repository constructor");
this.repository = repository;
}
@Override
public List<Speaker> findAll() {
return repository.findAll();
}
// Using setter injection
@Autowired
public void setRepository(SpeakerRepository repository) {
System.out.println("SpeakerServiceImpl setter");
this.repository = repository;
}
}
And now for HibernateSpeakerRepositoryImpl, we will set the bean as a repository using the name we’ve given it in AppConfig.
package org.therenaissance.repository;
import org.springframework.stereotype.Repository;
import org.therenaissance.model.Speaker;
import java.util.List;
import java.util.ArrayList;
@Repository("speakerRepository")
public class HibernateSpeakerRepositoryImpl implements SpeakerRepository {
// A function to find all Speakers
@Override
public List<Speaker> findAll() {
// Create a list of Speakers
List<Speaker> speakers = new ArrayList<Speaker>();
// Create an initial Speaker
Speaker speaker = new Speaker();
speaker.setFirstName("Dog");
speaker.setLastName("Meat");
// Add this speaker to the list
speakers.add(speaker);
// Return list of all speakers
return speakers;
}
}
We can run Application again and see that we get the same thing in the console as we did previously. Thus, our refactor is complete!
SpeakerServiceImpl no args constructor
SpeakerServiceImpl setter
org.therenaissance.service.SpeakerServiceImpl@49139829
org.therenaissance.service.SpeakerServiceImpl@49139829
Dog
Process finished with exit code 0
Just to show a few more things, if we wanted to, we can add an @Scope annotation to our SpeakerServiceImpl class if needed (it isn’t necessary since it is a singleton by default in this case.) If we wanted to use a constructor injection instead of setter injection, we can move the @Autowired up the the SpeakerServiceImpl() constructor instead.
package org.therenaissance.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.therenaissance.model.Speaker;
import org.therenaissance.repository.SpeakerRepository;
import java.util.List;
@Service("speakerService")
public class SpeakerServiceImpl implements SpeakerService {
private SpeakerRepository repository;
public SpeakerServiceImpl() {
System.out.println("SpeakerServiceImpl no args constructor");
}
// Using constructor injection
@Autowired
public SpeakerServiceImpl(SpeakerRepository repository) {
System.out.println("SpeakerServiceImpl repository constructor");
this.repository = repository;
}
@Override
public List<Speaker> findAll() {
return repository.findAll();
}
// Using setter injection
//@Autowired
public void setRepository(SpeakerRepository repository) {
System.out.println("SpeakerServiceImpl setter");
this.repository = repository;
}
}
Again, running Application will print out “SpeakerServiceImpl repository constructor”, showing that it was successful!
SpeakerServiceImpl repository constructor
org.therenaissance.service.SpeakerServiceImpl@5af3afd9
org.therenaissance.service.SpeakerServiceImpl@5af3afd9
Dog
Process finished with exit code 0
Conclusion
And there we have it, we learned about the singleton and prototype scopes and how to configure them. And then we learned how to autowire our app using setter and constructor injection to save us some time and effort.
Next, we’ll be taking a look at Spring Configurations using XML configurations.