In the previous article, I briefly explained what Spring is and what beans and context are. Now it's time to try it out.
I'm going to do it using IntelliJ IDEA Enterprise Edition. But all my examples should also work in the free IntelliJ IDEA Community Edition. In the screenshots, if you see that I have some window that you don't have, don't worry — it's not important for this project :)
First, create an empty Maven project. I showed how to do this in the article at this link. Read up to the words "It's time for us to convert our Maven project into a web project." — after that, the article shows how to make a web project, but we don't need that right now.
In the src/main/java folder, create a package (in my case, I called it "
In the window on the left, you can see the project structure with the package and the
![Spring for lazy people Foundation, basic concepts, and examples with code. Part 2 - 1](https://cdn.codegym.cc/images/article/9ed9d8a8-f06e-47a7-bd1f-f398a22af313/1024.jpeg)
en.codegym.info.fatfaggy.animals
. You can call it whatever you want. Just don't forget to replace my package name with your package name in all the right places. Now create the Main
class and add a method
public static void main(String[] args) {
...
}
After that, open the pom.xml file and add the dependencies
section. Now go to the Maven repository and find the Spring context for the latest stable version. Put what we find into the dependencies
section. I described this process in more detail in this other CodeGym article (see the section entitled "Connecting dependencies in Maven").
Then Maven itself will find and download the necessary dependencies. In the end, you should get something like this:
![Spring for lazy people Foundation, basic concepts, and examples with code. Part 2 - 2](https://cdn.codegym.cc/images/article/e2adf5de-d162-4f9c-9b66-1d21e5ae2d5f/1024.jpeg)
Main
class.
The middle window shows how pom.xml looks for me. I also added a properties section to it. This section tells Maven which version of Java I am using in my source files and which version to compile. This is just so that IDEA doesn't warn me that I'm using an old version of Java. This is optional :)
The right window makes it clear that even though we only connected the spring-context module, it automatically pulled in the spring-core, spring-beans, spring-aop and spring-expression modules. We could have connected each module separately, writing out a dependency for each module with the explicit version in the pom.xml file, but for now we're happy with things as they are.
Now create the entities
package and create 3 classes in it: Cat
, Dog
, Parrot
. Let's give each animal a name (private String name
— you can hardcode some values there). The getters/setters are public.
Now we move on to the Main
class and the main()
method, and we write something like this:
public static void main(String[] args) {
// Create an empty Spring context that will look for its own beans based on the annotations in the specified package
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals.entities");
Cat cat = context.getBean(Cat.class);
Dog dog = (Dog) context.getBean("dog");
Parrot parrot = context.getBean("parrot-polly", Parrot.class);
System.out.println(cat.getName());
System.out.println(dog.getName());
System.out.println(parrot.getName());
}
First, we create a context object, telling the constructor which package to look in to find beans. In other words, Spring will go through this package and try to find classes marked with special annotations that indicate they are beans. Then it creates the objects of these classes and puts them in the context.
After that, we get a cat from this context. We call on the context object to give us a bean (object), indicating the class of the object we want (by the way, we can also specify interfaces, not just classes). After that, Spring returns an object of the requested class, which we then save in a variable.
Next, we ask Spring to get us a bean called "dog". When Spring creates a Dog
object, it gives the object a standard name (unless the created bean has been explicitly assigned a name), which is the class name but with an initial lowercase letter. In this case, our class is called Dog
, so the bean's name is be "dog". If we needed a BufferedReader
object there , then Spring would name it "bufferedReader". And because Java can't be 100% certain which class we want, it returns an Object
object, which we then manually cast to the desired type, i.e. Dog
. The option where the class is indicated explicitly is more convenient.
The third option is to get a bean by class name and by bean name. It's possible that the context may have several beans of a single class. In order to indicate the particular bean we need, we indicate its name. Because we also explicitly indicate the class here, we no longer have to perform a cast.
IMPORTANT! If Spring finds several beans that match our requirements, then it can't determine which bean to give us, so it will throw an exception. Accordingly, to avoid this situation, you should try to be as specific as possible in telling Spring which bean you need. If Spring searches its context and fails to find a single bean that matches our requirements, then it will also throw an exception.
Finally, we simply display the names of our animals to verify that we really got the objects we need.
But if we run the program now, we'll see that Spring is unhappy — it cannot find the animals we need in its context. This is because it hasn't created these beans. As I said previously, when Spring scans classes, it looks for its own Spring annotations. And if Spring doesn't find these annotations, then it doesn't think that these classes correspond to beans that it needs to create.
Fixing this simply requires adding the @Component
annotation in front of each of our animal classes.
@Component
public class Cat {
private String name = "Oscar";
...
}
But there's more. If we need to explicitly tell Spring that the bean for this class should have a specific name, we indicate the name in parentheses after the annotation. For example, to tell Spring to give the name "parrot-polly
" to the parrot bean, which is the name we'll use to get this parrot in the main
method, we should do something like this:
@Component("parrot-polly")
public class Parrot {
private String name = "Polly";
...
}
This is the whole point of automatic configuration. You write your classes, mark them with the necessary annotations, and tell Spring the package that has your classes. This is the package that framework will run through to find annotations and create objects of these classes.
By the way, Spring doesn't only look for @Component
annotations, but also all other annotations that inherit this one. For example, @Controller
, @RestController
, @Service
, @Repository
, and more, which we will introduce in future articles.
Now we'll try to do the same thing using Java-based configuration.
To get started, remove the @Component
annotations from our classes. To make things more challenging, imagine that we didn't write these classes, so we can't easily modify them, which means we can't add annotations. It's like these classes are packaged in some library. In this case, there's no way for us to edit these classes so that they are recognized by Spring. But we need objects of these classes!
Here we need Java-based configuration to create the objects.
To get started, create a package with a name like configs
. In this package, create an ordinary Java class, something like MyConfig
, and mark it with the @Configuration
annotation.
@Configuration
public class MyConfig {
}
Now we need to tweak the main()
method, changing how we create the context. We can either explicitly indicate which class has our configuration:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class);
If we have several different classes that create beans and we want to connect several of them simultaneously, we simply indicate them all there, separated by commas:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class, MyAnotherConfig.class);
And if we have too many of them and we want to connect them all simultaneously, then we simply indicate the name of the package they are contained in:
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals.configs");
In this case, Spring will go through the package and find all the classes marked with the @Configuration
annotation.
Well, and if we have a really big program where the configurations are divided into different packages, then we simply indicate a comma-delimited list of names of the packages that contain the configurations:
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals.database.configs",
"en.codegym.info.fatfaggy.animals.root.configs",
"en.codegym.info.fatfaggy.animals.web.configs");
Or the name of a package that is common for them all:
ApplicationContext context =
new AnnotationConfigApplicationContext("en.codegym.info.fatfaggy.animals");
You can do it however you want, but it seems to me that the very first option, which simply indicates a class with the configurations, will suit our program best.
When creating a context, Spring looks for classes marked with the @Configuration
annotation and will create its own objects of these classes. It attempts to call methods marked with the @Bean
annotation, which means that these methods return beans (objects) that Spring will add to the context.
And now we'll create beans for a cat, dog, and parrot in our class with Java-based configuration. This is pretty simple to do:
@Bean
public Cat getCat() {
return new Cat();
}
Here we manually create our cat and hand it to Spring, which then holds our object in its context. Since we did not explicitly give a name to our bean, Spring will give it the same name as the name of the method. In our case, the cat bean will be called "getCat
". But because we use the class, not the name, to get the cat bean in the main
method, the bean's name isn't important to us.
Similarly, create a dog bean, keeping in mind that Spring will give the method name to the bean.
To explicitly name our parrot bean, we simply indicate its name in parentheses after the @Bean
annotation:
@Bean("parrot-polly")
public Object weNeedMoreParrots() {
return new Parrot();
}
As you can see, here I indicated an Object
return type and gave the method an arbitrary name. This does not affect the bean's name, because we explicitly specified the name here. Still, it's better to indicate a more or less meaningful return value and method name. Do this if for no other reason than to do yourself a favor when you reopen the project in a year. :)
Now consider the situation where we need one bean to create another bean.
For example, suppose we want the name of the cat in the cat bean to be the parrot's name plus the string "-killer". No problem!
@Bean
public Cat getCat(Parrot parrot) {
Cat cat = new Cat();
cat.setName(parrot.getName() + "-killer");
return cat;
}
Here Spring will see that in order to create this bean, the framework needs to pass in the previously created parrot bean. Accordingly, it will arrange the necessary chain of method calls: first, the parrot-creating method is called and then the framework passes the new parrot to the cat-creating method. Here's where dependency injection comes into play: Spring itself passes the required parrot bean to our method. If IDEA gets upset about the parrot
variable, don't forget to change the parrot-creating method's return type from Object
to Parrot
.
In addition, Java-based configuration lets you run absolutely any Java code in your bean-creating methods. You can really do anything: create other auxiliary objects, call any other methods, even those not marked with Spring annotations, create loops, Boolean conditions — whatever comes to mind!
This is not all possible with automatic configuration, and even less so with XML configuration.
Now let's consider a problem that's a bit more fun. Polymorphism and interfaces :)
We'll create a WeekDay
interface and create 7 classes that implement this interface: Monday
, Tuesday
, Wednesday
, Thursday
, Friday
, Saturday
, Sunday
.
We'll give the interface a String getWeekDayName()
method, which will return the name of the day of the week for the corresponding class. In other words, the Monday
class will return "Monday
", etc.
Upon starting the application, suppose our task is to put a bean corresponding to the current day of the week into the context. Not beans for all the classes that implement the WeekDay
interface — only the one bean that we need. You can do that about like this:
@Bean
public WeekDay getDay() {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
switch (dayOfWeek) {
case MONDAY: return new Monday();
case TUESDAY: return new Tuesday();
case WEDNESDAY: return new Wednesday();
case THURSDAY: return new Thursday();
case FRIDAY: return new Friday();
case SATURDAY: return new Saturday();
default: return new Sunday();
}
}
Here the return type is our interface. The method returns a bona fide object of one of the classes that implement the interface, depending on the current day of the week.
Now we can do the following in the main()
method:
WeekDay weekDay = context.getBean(WeekDay.class);
System.out.println("Today is " + weekDay.getWeekDayName() + "!");
For me, the program tells me it is Sunday :) I'm confident that if I run the program tomorrow, the context will contain a completely different object.
Note that we're getting the bean simply using the interface: context.getBean(WeekDay.class)
. Spring will search its context for the bean that implements the interface, and will return it. Then it turns out that our WeekDay
variable ends up with a Sunday object, and the familiar concept of polymorphism applies as we work with this variable. :)
Now a few words about the combined approach, in which some beans are created automatically by Spring, some by scanning packages for classes with the @Component
annotation, and others by Java-based configuration.
As we consider this, we'll return to the original version, where the Cat
, Dog
, and Parrot
classes were marked with the @Component
annotation.
Suppose we want to create beans for our animals by having Spring automatically scan the entities
package, but we also want to create a bean with the day of the week, like we just did.
All you need to do is add the @ComponentScan
annotation at the level of the MyConfig
class, which we indicate when creating the context in main()
, and indicate in parentheses the package that needs to be scanned and create beans of the necessary classes automatically:
@Configuration
@ComponentScan("en.codegym.info.fatfaggy.animals.entities")
public class MyConfig {
@Bean
public WeekDay getDay() {
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
switch (dayOfWeek) {
case MONDAY: return new Monday();
case TUESDAY: return new Tuesday();
case WEDNESDAY: return new Wednesday();
case THURSDAY: return new Thursday();
case FRIDAY: return new Friday();
case SATURDAY: return new Saturday();
default: return new Sunday();
}
}
}
When creating the context, Spring sees that it needs to process the MyConfig
class. It enters the class and sees that it needs to scan the "en.codegym.info.fatfaggy.animals.entities
" package and create beans of those classes, after which it executes the MyConfig
class's getDay()
method and adds a WeekDay
bean to the context. In the main()
method, we now have access to all the beans we need: both animal objects and a bean with the day of the week.
If you ever need to make Spring also pick up some XML configuration files, you can do your own web search to find an explanation :)
Summary:
- Try to use automatic configuration
- During automatic configuration, indicate the name of the package that contains the classes whose beans need to be created
- These classes are marked with the
@Component
annotation - Spring runs through all these classes, creates objects, and puts them in the context;
- If automatic configuration doesn't suit us for some reason, we use Java-based configuration
- In this case, we create an ordinary Java class whose methods return the objects we need. We mark this class with the
@Configuration
annotation if we're going to scan the entire package rather than indicate a specific class with the configuration when creating the context - The methods of this class that return beans are marked with the
@Bean
annotation
@ComponentScan
annotation.