在上一篇文章中,我简单地解释了 spring 是什么、bin 是什么以及上下文。现在是时候尝试一下这一切是如何运作的了。 我会在Intellij Idea企业版中自己做。但我的所有示例也应该适用于免费的 Intellij Idea 社区版。如果您在屏幕截图中看到我有某种您没有的窗口,请不要担心,这对于这个项目来说并不重要:) 首先,让我们创建一个空的 Maven 项目。我在文章中展示了如何做到这一点(阅读直到“是时候将我们的 Maven 项目变成一个 Web 项目了。 ”,之后它已经展示了如何创建一个 Web 项目,我们现在不需要这个)让我们在src/main 文件夹中创建它 /java是一些包(在我的例子中,我将其称为“
在左侧窗口中,您可以看到项目的结构以及包和类
现在我们来看一个更好玩的问题。通过多态性和接口:)让 我们创建一个接口并创建 7个
ru.javarush.info.fatfaggy.animals
”,您可以将其命名为任何您想要的名称,只是不要忘记在正确的位置将其替换为您的名称)。让我们创建一个类,Main
在其中创建一个方法
public static void main(String[] args) {
...
}
然后打开 pom.xml 文件并在其中添加一个部分dependencies
。现在我们进入Maven 存储库并在那里查找最新稳定版本的spring 上下文,并将我们获得的内容粘贴到 部分中dependencies
。我在本文中更详细地描述了这个过程(请参阅“在 Maven 中连接依赖项”部分)。然后 Maven 本身会找到并下载必要的依赖项,最后你应该得到类似这样的东西:
Main
。中间的窗口显示了我的 pom.xml 的样子。我还在那里添加了一个属性部分,在其中我向 Maven 指示了我在源代码中使用的 Java 版本以及要编译成的版本。这只是为了让我在启动时不会收到正在使用旧版本 Java 的警告。你可以做到,你不能)在右侧窗口中 - 你可以看到,即使我们只连接了 spring 上下文 - 它自动添加了 core、bean、aop 和表达式。可以单独连接每个模块,在内存中注册每个模块的依赖项并明确指示版本,但目前我们对现在的选项感到满意。现在让我们创建一个包entities
(实体)并在其中创建 3 个类:Cat
, Dog
, Parrot
。让每个动物都有一个名字(private String name
,你可以在那里硬编码一些值),并且 getter/setter 是公共的。现在转到类中Main
并main()
在方法中编写如下内容:
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());
}
首先,我们创建一个上下文对象,并在构造函数中为它指定需要扫描是否存在 Bean 的包的名称。也就是说,Spring将遍历这个包并尝试找到带有特殊注释的类,让Spring知道这是一个bean。之后,它创建这些类的对象并将它们放置在其上下文中。之后我们从这个上下文中得到一只猫。当寻址上下文对象时,我们要求它给我们一个bean(对象),并指出我们需要什么类的对象(这里,顺便说一下,你不仅可以指定类,还可以指定接口)。之后Spring将这个类的对象返回给我们,我们将其保存到一个变量中。接下来,我们要求 Spring 为我们提供一个名为“dog”的 bean。当Spring创建一个类对象时,Dog
它会给它一个标准的名称(如果没有显式指定所创建的bean的名称),这是该对象的类的名称,只有一个小写字母。因此,既然我们的类被称为Dog
,那么这样一个 bean 的名字将是“dog”。如果我们在那里有一个对象BufferedReader
,那么 Spring 会给它默认的名称“bufferedReader”。由于在这种情况下(在 Java 中)无法确切确定这样的对象是什么类,因此只需返回某个对象Object
,然后我们手动将其转换为我们需要的类型Dog
。明确指示类别的选项更方便。好吧,在第三种情况下,我们通过类和名称获得一个 bean。可能只是存在一种情况,在上下文中,一个类有多个 bean,为了指示我们需要哪个特定 bean,我们指示它的名称。由于我们这里也明确标明了班级,所以我们不再需要演员了。 重要的!如果 Spring 根据我们指定的要求找到了多个 bean,它将无法确定给我们哪个 bean,并且会抛出异常。因此,尽量准确地向他表明你需要哪个垃圾箱,这样就不会出现这种情况。如果 Spring 根据您的条件在其上下文中没有找到单个 bean,它也会抛出异常。好吧,然后我们只需在屏幕上显示动物的名称,以确保这些实际上正是我们需要的对象。但是如果我们现在运行该程序,我们将看到 Spring 发誓它无法在其上下文中找到我们需要的动物。发生这种情况是因为他没有创造这些豆子。正如我已经说过的,当 Spring 扫描类时,它会在那里查找“它的”Spring 注释。如果它没有找到它,那么它就不会认为这些类是他需要创建其 Bean 的类。@Component
要解决此问题,只需在动物类中的类前面 添加注释即可。
@Component
public class Cat {
private String name = "Barsik";
...
}
但这还不是全部。如果我们需要显式地向 Spring 指示该类的 bean 应该有一个特定的名称,则可以在注释后的括号中指示该名称。例如,为了让 Spring 为parrot-kesha
parrot bean 提供我们需要的名称(main
稍后我们将从中接收到这只鹦鹉),我们需要执行以下操作:
@Component("parrot-kesha")
public class Parrot {
private String name = "Kesha";
...
}
这就是自动配置的 全部要点。您编写类,用必要的注释标记它们,并向 Spring 指示包含您的类的包,Spring 通过该包查找注释并创建此类类的对象。顺便说一句,Spring不仅会搜索注释@Component
,还会搜索从该注释继承的所有其他注释。例如,@Controller
、@RestController
、@Service
和@Repository
其他,我们将在以后的文章中介绍。现在让我们尝试做同样的事情,但是使用 java 配置。首先,让我们@Component
从类中删除注释。为了使任务复杂化,我们假设这些不是我们自己编写的类,我们可以轻松修改、添加一些内容,包括注释。就好像这些类被打包在某个图书馆中一样。在这种情况下,我们无法以任何方式编辑这些类以使它们被 spring 接受。但我们需要这些类的对象!这里我们需要 java 配置来创建这样的对象。首先,我们创建一个包(例如)configs
,并在其中创建一个常规 Java 类(例如),MyConfig
并用注释对其进行标记@Configuration
@Configuration
public class MyConfig {
}
现在我们需要稍微调整main()
在方法中创建上下文的方式。我们可以直接在配置中指定我们的类:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class);
如果我们有几个不同的类,我们在其中创建 bean,并且我们想要一次连接其中的几个,我们只需在其中用逗号分隔即可指示它们:
ApplicationContext context =
new AnnotationConfigApplicationContext(MyConfig.class, MyAnotherConfig.class);
好吧,如果我们有太多,并且我们想一次连接它们,我们只需在此处指出包含它们的包的名称:
ApplicationContext context =
new AnnotationConfigApplicationContext("ru.javarush.info.fatfaggy.animals.configs");
在这种情况下,Spring将遍历这个包并找到所有带有注释标记的类@Configuration
。好吧,如果我们有一个非常大的程序,其中配置被分为不同的包,我们只需用逗号分隔配置来指示包的名称:
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");
好吧,或者是对它们来说更常见的包的名称:
ApplicationContext context =
new AnnotationConfigApplicationContext("ru.javarush.info.fatfaggy.animals");
您可以按照自己的意愿进行操作,但在我看来,第一个选项(只需指定带有配置的类)最适合我们的程序。在创建上下文时,Spring会搜索那些被注解标记的类@Configuration
,并在自身中创建这些类的对象。之后它将尝试调用这些类中标有注释的方法@Bean
,这意味着这些方法将返回它已经放置在其上下文中的bean(对象)。好吧,现在让我们使用 java 配置在我们的类中创建 cat、dog 和 parrot beans。这很简单:
@Bean
public Cat getCat() {
return new Cat();
}
事实证明,我们自己手动创建了猫并将其交给 Spring,而他已经将我们的这个对象放入了他的上下文中。由于我们没有明确指定 bean 的名称,Spring 将为 bean 指定与方法名称相同的名称。在我们的例子中,猫的豆子的名字是“ getCat
”。但由于在main
-e 中我们仍然不是通过名称而是通过类来获取猫,所以在这种情况下,这个 bin 的名称对我们来说并不重要。以同样的方式用狗创建一个 bean,但请记住 Spring 会通过方法的名称来命名这样的 bean。要使用 parrot 显式命名我们的 bean,只需在注释后的括号中指明其名称@Bean
:
@Bean("parrot-kesha")
public Object weNeedMoreParrots() {
return new Parrot();
}
正如您所看到的,这里我指示了返回值的类型Object
,并将该方法称为任何名称。这不会以任何方式影响 bean 的名称,因为我们在此处显式设置了它。但最好不要突然指出返回值类型和方法名称,但或多或少要清楚一些。即使是为了你自己,当你在一年后打开这个项目时。:) 现在让我们考虑这样一种情况:创建一个 bean 时我们需要使用另一个 bean。例如,我们希望cat bean中猫的名字由鹦鹉的名字和字符串“-killer”组成。没问题!
@Bean
public Cat getCat(Parrot parrot) {
Cat cat = new Cat();
cat.setName(parrot.getName() + "-killer");
return cat;
}
这里 Spring 会看到,在创建这个 bean 之前,他需要将已经创建的 parrot bean 转移到这里。因此,他会构建一个对我们方法的调用链,以便首先调用创建鹦鹉的方法,然后将这只鹦鹉传递给创建猫的方法。这就是依赖注入起作用的地方:Spring 本身将所需的 parrot bean 传递给我们的方法。如果想法抱怨变量parrot
,请不要忘记将创建鹦鹉的方法中的返回类型从更改Object
为Parrot
。此外,Java 配置允许您在创建 bean 的方法中执行绝对任何 Java 代码。你真的可以做任何事情:创建其他辅助对象,调用任何其他方法,甚至那些没有用 spring 注释标记的方法,make 循环,条件 - 无论你想到什么!所有这些都无法使用自动配置来实现,更不用说使用 xml 配置了。
WeekDay
实现该接口的类:Monday
、、、、、、、、。让我们在接口中创建一个方法,该方法将返回相应类的星期几的名称。也就是说,该类将返回“ ”等。假设启动应用程序时的任务是将一个 bean 放置在与当前星期相对应的上下文中。并非所有类的所有 bean 都实现了该接口,而只是我们需要的那个。可以这样做: Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
String getWeekDayName()
Monday
monday
WeekDay
@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();
}
}
这里的返回值类型是我们的接口,该方法根据当前星期几返回接口实现类的真实对象。现在在方法中main()
我们可以这样做:
WeekDay weekDay = context.getBean(WeekDay.class);
System.out.println("It's " + weekDay.getWeekDayName() + " today!");
它告诉我今天是星期日:)我确信如果我明天运行该程序,上下文中将出现一个完全不同的对象。请注意,这里我们只是通过接口获取bean:context.getBean(WeekDay.class)
。Spring 将在其上下文中查看哪个 bean 实现了此类接口并返回它。好吧,事实证明,WeekDay
类型 的变量中有一个类型的对象Sunday
,而我们大家都已经熟悉的多态性是在使用该变量时开始的。:) 关于组合方法的一些话,其中一些 bean 是由 Spring 本身创建的,使用扫描包来查找带有注释的类的存在@Component
,而其他一些 bean 是使用 java 配置创建的。为此,让我们回到原始版本,当时类Cat
、Dog
和 都Parrot
用注释进行了标记@Component
。假设我们想在春天之前使用自动扫描包裹的方式为我们的动物创建垃圾箱entities
,但像我们刚才那样创建一个包含星期几的垃圾箱。您需要做的就是在类级别添加,我们在第一个注释MyConfig
中创建上下文时指定,并在括号中指示需要扫描的包以及自动创建的必要类的bean: main
@ComponentScan
@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();
}
}
}
事实证明,在创建上下文时,Spring 发现它需要处理该类MyConfig
。他进入其中,发现他需要扫描包“ ru.javarush.info.fatfaggy.animals.entities
”并创建这些类的 bean,之后他执行getDay()
该类中的一个方法MyConfig
并将该类型的 bean 添加WeekDay
到他的上下文中。在该方法中,main()
我们现在可以访问所需的所有 bean:动物对象和星期几 bean。如何确保 Spring 也获取一些 xml 配置 - 如果需要,请自行在 Internet 上 google 一下:) 摘要:
- 尝试使用自动配置;
- 在自动配置过程中,我们指定包含需要创建bean的类的包的名称;
- 这些类标有注释
@Component;
- spring 遍历所有此类并创建它们的对象并将它们放置在上下文中;
- 如果由于某种原因自动配置不适合我们,我们使用java配置;
- 在这种情况下,我们创建一个常规 Java 类,其方法将返回我们需要的对象,并用注释标记这样的类,
@Configuration
以防我们在创建上下文时扫描整个包而不是通过配置指定特定类; - 此类返回 bean 的方法用注释进行标记
@Bean
;
@ComponentScan
。
GO TO FULL VERSION