JavaRush /Java Blog /Random EN /Spring is for the lazy. Fundamentals, basic concepts and ...
Стас Пасинков
Level 26
Киев

Spring is for the lazy. Fundamentals, basic concepts and examples with code. Part 2

Published in the Random EN group
In the last article , I explained in a nutshell what spring is, what bins are and context. Now it's time to try how it all works. Spring is for the lazy.  Fundamentals, basic concepts and examples with code.  Part 2 - 1I will do it myself in Intellij Idea Enterprise Edition. But all my examples should also work in the free Intellij Idea Community Edition. Just if you see in the screenshots that I have some kind of window that you don’t have, don’t worry, it’s not critical for this project :) First, let’s create an empty Maven project. I showed how to do this in the article (read until the words “ It’s time to turn our maven project into a web project. ”, after that it already shows how to create a web project, and we don’t need this now) Let’s create it in the src/main folder /java is some package (in my case I called it “ ru.javarush.info.fatfaggy.animals”, you can name it whatever you want, just don’t forget to replace it with your name in the right places). And let's create a class Mainin which we will make a method
public static void main(String[] args) {
    ...
}
Then open the pom.xml file and add a section there dependencies. Now we go to the Maven repository and look for the spring context of the latest stable version there, and paste what we got inside the section dependencies. I described this process in a little more detail in this article (see the section " Connecting dependencies in Maven "). Then Maven itself will find and download the necessary dependencies, and in the end you should get something like this:
Spring is for the lazy.  Fundamentals, basic concepts and examples with code.  Part 2 - 2
In the left window you can see the structure of the project with the package and class Main. The middle window shows what my pom.xml looks like. I also added a properties section there , in which I indicated to Maven which version of Java I was using in the source code and which version to compile into. This is just so that I don’t get the idea of ​​a warning when starting up that an old version of Java is being used. You can do it, you can not) In the right window - you can see that even though we connected only the spring context - it automatically added core, beans, aop and expression. It was possible to connect each module separately, registering a dependency for each in the memory with an explicit indication of the version, but for now we are happy with the option as it is now. Now let's create a package entities(entities) and create 3 classes in it: Cat, Dog, Parrot. Let each animal have a name ( private String name, you can hardcode some values ​​there), and the getters/setters are public. Now go to the class Mainand main()write something like this in the method:
public static void main(String[] args) {
	// create an empty spring context that will search for its beans by annotations in the specified package
	ApplicationContext context =
		new AnnotationConfigApplicationContext("ru.javarush.info.fatfaggy.animals.entities");

	Cat cat = context.getBean(Cat.class);
	Dog dog = (Dog) context.getBean("dog");
	Parrot parrot = context.getBean("parrot-kesha", Parrot.class);

	System.out.println(cat.getName());
	System.out.println(dog.getName());
	System.out.println(parrot.getName());
}
First, we create a context object, and in the constructor we give it the name of the package that needs to be scanned for the presence of beans. That is, Spring will go through this package and try to find classes that are marked with special annotations that let Spring know that this is a bean. After which it creates objects of these classes and places them in its context. After which we get a cat from this context. When addressing the context object, we ask it to give us a bean (object), and indicate what class of object we need (here, by the way, you can specify not only classes, but also interfaces). After which Spring returns an object of this class to us, which we save into a variable. Next, we ask Spring to get us a bean called “dog”. When Spring creates a class object, Dogit will give it a standard name (if the name of the bean being created is not explicitly specified), which is the name of the object’s class, only with a small letter. Therefore, since our class is called Dog, the name of such a bean will be “dog”. If we had an object there BufferedReader, then Spring would give it the default name “bufferedReader”. And since in this case (in Java) there is no exact certainty of what class such an object will be, then simply a certain one is returned Object, which we then manually cast to the type we need Dog. The option with an explicit indication of the class is more convenient. Well, in the third case, we get a bean by class and name. There may simply be a situation where in the context there will be several beans of one class, and in order to indicate which particular bean we need, we indicate its name. Since we also clearly indicated the class here, we no longer have to cast. Important!If it turns out that Spring finds several beans according to the requirements that we specified to it, it will not be able to determine which bean to give us and will throw an exception. Therefore, try to indicate to him as precisely as possible which bin you need, so that such situations do not arise. If Spring does not find a single bean in its context according to your conditions, it will also throw an exception. Well, then we simply display the names of our animals on the screen to make sure that these are actually exactly the objects that we need. But if we run the program now, we will see that Spring is swearing that it cannot find the animals we need in its context. This happened because he did not create these beans. As I already said, when Spring scans classes, it looks for “its” Spring annotations there. And if it doesn’t find it, then it doesn’t perceive such classes as those whose beans he needs to create. To fix this, simply add an annotation @Componentin front of the class in our animal classes.
@Component
public class Cat {
	private String name = "Barsik";
	...
}
But that's not all. If we need to explicitly indicate to Spring that the bean for this class should have a specific name, this name can be indicated in parentheses after the annotation. For example, in order for Spring to give the name we need parrot-keshato the parrot bin, from which we mainwill later receive this parrot, we need to do something like this:
@Component("parrot-kesha")
public class Parrot {
	private String name = "Kesha";
	...
}
This is the whole point of automatic configuration . You write your classes, mark them with the necessary annotations, and indicate to Spring a package with your classes, through which it goes, looks for annotations and creates objects of such classes. By the way, Spring will search not only annotations @Component, but also all other annotations that are inherited from this one. For example, @Controller, @RestController, @Service, @Repositoryand others, which we will meet in further articles. Now let's try to do the same, but using java configuration . First, let's remove annotations @Componentfrom our classes. To complicate the task, let’s imagine that these are not our own self-written classes, which we can easily modify, add something, including annotations. It’s as if these classes are packed away in some library. In this case, we cannot edit these classes in any way so that they are accepted by Spring. But we need objects of these classes! Here we will need the java configuration to create such objects. To begin with, let's create a package, for example configs, and in it - a regular Java class, for example, MyConfigand mark it with an annotation@Configuration
@Configuration
public class MyConfig {
}
Now we need to slightly tweak main()the way we create the context in the method. We can either directly specify our class with configuration there:
ApplicationContext context =
	new AnnotationConfigApplicationContext(MyConfig.class);
If we have several different classes where we create beans and we want to connect several of them at once, we simply indicate them there separated by commas:
ApplicationContext context =
	new AnnotationConfigApplicationContext(MyConfig.class, MyAnotherConfig.class);
Well, if we have too many of them, and we want to connect them all at once, we simply indicate here the name of the package in which we have them:
ApplicationContext context =
	new AnnotationConfigApplicationContext("ru.javarush.info.fatfaggy.animals.configs");
In this case, Spring will go through this package and find all the classes that are marked with the annotation @Configuration. Well, in case we have a really large program where the configs are divided into different packages, we simply indicate the names of the packages with configs separated by commas:
ApplicationContext context =
	new AnnotationConfigApplicationContext("ru.javarush.info.fatfaggy.animals.database.configs",
		"ru.javarush.info.fatfaggy.animals.root.configs",
		"ru.javarush.info.fatfaggy.animals.web.configs");
Well, or the name of a package more common to all of them:
ApplicationContext context =
	new AnnotationConfigApplicationContext("ru.javarush.info.fatfaggy.animals");
You can do it as you wish, but it seems to me that the very first option, where you simply specify a class with configs, will suit our program best. When creating a context, Spring will search for those classes that are marked with the annotation @Configurationand create objects of these classes in itself. After which it will try to call methods in these classes that are marked with the annotation @Bean, which means that such methods will return beans (objects) that it has already placed in its context. Well, now let's create cat, dog and parrot beans in our class with java configuration. This is done quite simply:
@Bean
public Cat getCat() {
	return new Cat();
}
It turns out that we manually created our cat ourselves and gave it to Spring, and he already placed this object of ours in his context. Since we did not explicitly specify the name of our bean, Spring will give the bean the same name as the name of the method. In our case, the cat's bean will have the name " getCat". But since in main-e we still get the cat not by name, but by class, then in this case the name of this bin is not important to us. Make a bean with a dog in the same way, but keep in mind that Spring will name such a bean by the name of the method. To explicitly name our bean with the parrot, simply indicate its name in parentheses after the annotation @Bean:
@Bean("parrot-kesha")
public Object weNeedMoreParrots() {
	return new Parrot();
}
As you can see, here I indicated the type of the return value Object, and called the method anything at all. This does not affect the name of the bean in any way because we explicitly set it here. But it’s better to indicate the return value type and method name not out of the blue, but more or less clearly. Just even for yourself, when you open this project in a year. :) Now let's consider a situation where to create one bean we need to use another bean . For example, we want the name of the cat in the cat bean to consist of the name of the parrot and 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 before creating this bean, he will need to transfer the already created parrot bean here. Therefore, he will build a chain of calls to our methods so that the method for creating a parrot is called first, and then passes this parrot to the method for creating a cat. This is where the thing called dependency injection worked : Spring itself passed the required parrot bean to our method. If the idea complains about a variable parrot, don’t forget to change the return type in the method for creating a parrot from Objectto Parrot. In addition, Java configuration allows you to execute absolutely any Java code in methods for creating beans. You can really do anything: create other auxiliary objects, call any other methods, even those not marked with spring annotations, make loops, conditions - whatever comes to mind! All this cannot be achieved using automatic configuration, much less using xml configs. Now let's look at a more fun problem. With polymorphism and interfaces :) Let's create an interface WeekDayand create 7 classes that would implement this interface: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday. Let's create a method in the interface String getWeekDayName()that would return the name of the day of the week of the corresponding class. That is, the class Mondaywould return " monday", etc. Let's say the task when launching our application is to place a bean in the context that would correspond to the current day of the week. Not all beans of all classes that implement WeekDaythe interface, but only the one we need. It can be done something 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 value type is our interface, and the method returns real objects of the interface implementation classes depending on the current day of the week. Now in the method main()we can do this:
WeekDay weekDay = context.getBean(WeekDay.class);
System.out.println("It's " + weekDay.getWeekDayName() + " today!");
It told me that today is Sunday :) I am sure that if I run the program tomorrow, a completely different object will appear in the context. Please note that here we get the bean simply by the interface: context.getBean(WeekDay.class). Spring will look in its context to see which of its beans implements such an interface and will return it. Well, then it turns out that WeekDaythere is an object of type in a variable of type Sunday, and polymorphism, already familiar to all of us, begins when working with this variable. :) And a few words about the combined approach , where some of the beans are created by Spring itself, using scanning of packages for the presence of classes with an annotation @Component, and some other beans are created using the java config. To do this, let's go back to the original version, when the classes Cat, Dogand Parrotwere marked with an annotation @Component. Let's say we want to create bins for our animals using automatic scanning of the package entitiesby spring, but create a bin with the day of the week as we just did. All you need to do is add at the class level MyConfig, which we specify when creating the context in mainthe -th annotation @ComponentScan, and indicate in brackets the package that needs to be scanned and the beans of the necessary classes created automatically:
@Configuration
@ComponentScan("ru.javarush.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();
		}
	}
}
It turns out that when creating a context, Spring sees that it needs to process the class MyConfig. He goes into it and sees that he needs to scan the package " ru.javarush.info.fatfaggy.animals.entities" and create beans of those classes, after which he executes the method getDay()from the class MyConfigand adds the type bean WeekDayto his context. In the method, main()we now have access to all the beans we need: both the animal objects and the bean with the day of the week. How to make sure that Spring also picks up some xml configs - google it on the Internet yourself if necessary :) Summary:
  • try to use automatic configuration;
  • during automatic configuration, we indicate the name of the package containing the classes whose beans need to be created;
  • such classes are marked with an annotation@Component;
  • spring goes through all such classes and creates their objects and places them in the context;
  • if automatic configuration does not suit us for some reason, we use java configuration;
  • in this case, we create a regular Java class whose methods will return the objects we need, and mark such a class with an annotation @Configurationin case we scan the entire package rather than specify a specific class with configuration when creating the context;
  • methods of this class that return beans are marked with the annotation @Bean;
  • if we want to enable automatic scanning when using the java configuration, we use the annotation @ComponentScan.
If nothing is clear, then try reading this article in a couple of days. Well, or if you are at the early levels of Javarash, then perhaps it is a little early for you to learn spring. You can always return to this article a little later, when you feel more confident in programming in Java. If everything is clear, you can try to transfer some of your pet projects to Spring :) If something is clear, but something is not so much, please comment :) There are also suggestions and comments if I stepped somewhere or I wrote something stupid) In the next article we will take a deep dive into spring-web-mvc and make a simple web application using spring.
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION