回顾
回忆一下我们之前是如何存储和使用Bean对象的, 首先我们需要在spring配置文件写入Bean对象, 或者说是spring容器中注入Bean对象:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user1" class="org.example.User"></bean> <bean id="user2" class="org.example.User"></bean> </beans>
然后在启动类中new出一个上下文对象, 要么使用BeanFactory, 要么使用ApplicationContext, 然后调用上下文对象中的getBean方法, 来获取到对应的Bean对象.
但是我每次使用都要去在spring配置文件中写入bean标签, 然后写上对应id和类. 这些步骤过于繁琐, 于是spring的作者就创造出了一种更简单的方式, 那就是直接在对应的类下面写上注解, 注解中填入id, 就省去了写路径的痛苦.
我们现在就可以直接通过一行注解, 来代替我们之前要写一行配置的尴尬了,
不过在写注解之前需要一点准备工作.
首先在这之前我们应该去了解一下获取Bean对象的方法, 也就是getBean方法
getBean()方法的使用
spring容器提供了五种获取Bean的方法, 可以根据Bean的name来获取Bean, 也可以根据classType 来获取Bean对象, 所有的根据Bean的name来获取Bean的方法, 最后底层都会调用下面的doGetBean方法来获取Bean对象
根据name来获取对象
spring 容器提供了三种根据Bean 的name来获取Bean的方法;
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml")); User user = (User) beanFactory.getBean("user"); User user1 = (User) beanFactory.getBean("user", User.class); User user2 = (User) beanFactory.getBean("user", new OrderUser());
- getBean("user") 是根据Bean的name 来获取Bean对象
- getBean("user", User.class) 是根据Bean的name来获取Bean, 然后判断Bean是否属于User类
- getBean("user", new OrderUser());则是使用自定义的方法来生成Bean对象
上面三种方法都会调用doGetBean方法来生成Bean对象, 下面是doGetBean方法的原码:
protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { // name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx // name有可能传入进来的是别名,那么beanName就是id String beanName = transformedBeanName(name); Object beanInstance; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // 如果sharedInstance是FactoryBean,那么就调用getObject()返回对象 beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); }
真正的BeanName来自这个transformedBeanName(name) 方法;
下面是这个方法的具体实现:
protected String transformedBeanName(String name) { return canonicalName(BeanFactoryUtils.transformedBeanName(name)); }
顺藤摸瓜. 先来看看BeanFactoryUtils.transformedBeanName(name) 的实现:
public static String transformedBeanName(String name) { Assert.notNull(name, "'name' must not be null"); if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) { return name; } return transformedBeanNameCache.computeIfAbsent(name, beanName -> { do { beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()); } while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)); return beanName; }); }
什么是BeanFactory.FACTORY_BEAN_PREFIX?
翻阅原码可以知道这个是一个字符串对象, 值为"&".
我们再来看看String的subString方法:
结合原码, 也就是说beanName的生成为:
beanName = beanName.subString(1);
里层是一个do while循环, 也就是先去掉一个字符, 然后循环查看beanName的前面是否有"&"字符, 如果有就一直去掉这&字符.
如果传进来的name从一开始就不是&字符开头的字符串, 那么就直接返回当前的name.
总结来说就是 BeanFactoryUtils.transformedBeanName(name) 是去掉name前面的所有的&字符
接下来回头看看这个canonicalName方法:
protected String transformedBeanName(String name) { return canonicalName(BeanFactoryUtils.transformedBeanName(name)); }
看看这个方法的具体实现:
public String canonicalName(String name) { String canonicalName = name; // Handle aliasing... String resolvedName; do { resolvedName = this.aliasMap.get(canonicalName); if (resolvedName != null) { canonicalName = resolvedName; } } while (resolvedName != null); return canonicalName; }
解析:
- String name 为上面一层函数BeanFactoryUtils.transformedBeanName(name) 传过来的去掉开头的所有的&字符的字符串.
- 此函数里面首先使用canonicalName字符串对象将这个去掉&的字符串给存起来了.
- 创建了一个字符串变量: resolvedName
- 随后进入dowhile循环, 首先是调用了this.aliasMap.get(canonicalName);
- aliasMap是一个hashMap, 这里调用get方法拿到key = canonicalName, 如果对应value值不为空, 那么就将这个值赋值给canonicalName, 然后返回这个值canonicalName.
Bean的别名存放在一个aliasMap中,其中KEY=别名,VALUE=beanName/别名,根据别名从aliasMap中拿到的可能是真正的beanName,也可能还是一个别名,所以用do-while循环,直到拿出来的名字从aliasMap再找不到对应的值,那么该名字就是真正的beanName了
对bean 进行定义的时候,除了可以使用id 命名,为了提供多个别名,使用alias来指定,这些所有的名称都指向同一个bean:
<bean class="User1" name="user1"/> <alias name="user1" alias="user2,user3"/>
再谈getBean()
我们明明就可以根据一个String name来锁定一个Bean对象, 为什么后面还需要传入第二个参数 XXX.class??
首先我们如果传入xxx.class, 我们在获取Bean对象的时候, 是返回一个Object类型的对象, 这个时候还需要我们进行强制类型转换成我们需要的类型, 但是这个时候可能会出错. 也就是说, 如果将其转换成了一个不是我们所需要的类, 那么在运行期间就会抛出异常.
我们第二个参数传入一个class, 此方法更安全,因为我们可以编译阶段就发现错误而不是在运行阶段。
(1) 配置扫描路径
想要将对象成功存储到容器中, 我们需要先配置一下存储对象的扫描路径, 只有被扫描到的包下的类, 添加注解才能被成功的识别到并被保存到容器中.
首先我们拿下面这个这个目录结构为例:
其中User类的代码如下:
package org.example; public class User { public void sayHi() { System.out.println("hello, how are you?"); } }
Student的代码如下:
package org.select; public class Student { public void sayHi() { System.out.println("hello student!! "); } }
下面我们首先添加扫描路径, 我们扫描select包下的所有的类, 于是就在spring配置文件中写入其扫描路径:
<content:component-scan base-package="org.select"></content:component-scan>
将其访问spring配置文件的beans标签中:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <content:component-scan base-package="org.select"></content:component-scan> </beans>
-- 其实添加这个扫描路径 就是为了更加精确的扫描出需要的类的所在的包, 为了更佳的性能
(2) 添加注解
想要通过添加注解的方式来存储Bean对象, 那么首先就需要去了解一下spring的注解:
① spring注解简介
Spring的注解是一种用于简化配置和开发的方式,它可以帮助开发人员更轻松地使用Spring框架。下面是几个常用的Spring注解的介绍:
- @Autowired:用于自动装配依赖关系。当Spring容器需要为一个属性注入一个bean时,它会查找与该属性类型匹配的bean,并将其自动注入到属性中。
- @Qualifier:用于指定要注入的bean的名称。当有多个与属性类型匹配的bean时,可以使用@Qualifier注解来指定要注入的bean的名称。
- @Component:用于将一个类标记为Spring容器的组件。被标记为@Component的类将被Spring自动扫描并注册为bean。
- @Controller:用于标记一个类作为Spring MVC的控制器。被标记为@Controller的类将处理HTTP请求并返回相应的视图。
- @Service:用于标记一个类作为业务逻辑层的组件。被标记为@Service的类通常包含业务逻辑,并被其他组件调用。
- @Repository:用于标记一个类作为数据访问层的组件。被标记为@Repository的类通常用于访问数据库或其他持久化存储。
- @Configuration:用于标记一个类作为Spring的配置类。被标记为@Configuration的类通常包含了一些用于配置Spring容器的bean定义。
- @Bean:用于在配置类中定义一个bean。被标记为@Bean的方法将返回一个对象,并将其注册为Spring容器的bean。
- @Value:用于注入属性值。可以将@Value注解应用于属性上,从而将属性值从配置文件中注入到属性中。
下面是一个使用@Autowired和@Qualifier注解的示例:
@Component public class MyComponent { @Autowired @Qualifier("myBean") private MyBean myBean; // ... }
接下来我们仔细看看该如何使用...!!!
② 对类注解的使用
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml")); Student student = beanFactory.getBean("student", Student.class); student.sayHi();
输出:
使用spring容器手动添加Bean对象和扫描注解Bean对象是可以混用的.
③ 注解Bean对象的命名问题
使用注解访问Bean对象, 那么getBean传入的第一个参数开头字母直接小写即可, 但是如果首字母和第二个字母都是大写, 那么开头一个字母小写是错误的, 这个时候直接使用原来的类名即可.
其源码如下:
public static String decapitalize(String name) { if (name == null || name.length() == 0) { return name; } // 如果第⼀个字⺟和第⼆个字⺟都为⼤写的情况,是把 bean 的⾸字⺟也⼤写存储了 if (name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){ return name; } // 否则就将⾸字⺟⼩写 char chars[] = name.toCharArray(); chars[0] = Character.toLowerCase(chars[0]); return new String(chars); }
举个例子, 有三个类, 他们添加了注解, 然后使用getBean方法取出去Bean对象, 如下:
其中第二种就会找不到Bean对象.
④ 方法加Bean注解
下面我们创建一个Test1类, 此类中有两个字段, 同时实现了其toString方法, 下面我们将通过添加Bean注解的方法来将这个类注入到容器:
package org.example2; /** * 普通的文章实体类 */ public class Test1 { private int age; private String name; @Override public String toString() { return "Test1{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
但是@Bean注解必须配合五大类注解一起使用
package org.select; /** * 普通的文章实体类 */ public class Test1 { private int age; private String name; @Override public String toString() { return "Test1{" + "age=" + age + ", name='" + name + '\'' + '}'; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
添加Bean注解和五大类注解:
package org.example2; import org.select.Test1; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; @Controller public class RetTest1 { @Bean public Test1 returnTest() { Test1 test1 = new Test1(); test1.setAge(18); test1.setName("张三"); return test1; } }
文章目录结构:
添加扫描路径:
<content:component-scan base-package="org.example2"></content:component-scan>
配置上下文, 使用getBean获取Test1 的对象
ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml"); // BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("springConfig.xml")); RetTest1 retTest1 = context.getBean("retTest1", RetTest1.class); retTest1.sayHi(); Test1 test1 = context.getBean("test1", Test1.class); System.out.println(test1.toString());
-- 输出结果如下:
可以发现, 一个类的方法添加了@Bean注解, 这个@Bean注解可以让被标记的方法返回的对象存储进入容器, 同时@Bean需要配合五大类注解进行, 所以被五大类注解标记的类对象同样也会被存入容器.
需要注意的是, 使用@Bean注解的时候, 这里如果去使用BeanFactory去获取上下文的话, 就会找不到@Bean注解的方法返回的类.
还有一点就是, 最后@Bean注解的方法, 在获取这个类的时候, getBean中的参数是@Bean注解的方法名, 而不是类名.
如果将ApplicationContext 获取的上下文对象换成 BeanFactory, 就会显示如下:
原因定位:
使用`new XmlBeanFactory(new ClassPathResource("bean.xml"))`实例化出来的对象是无法读取Spring注解配置文件的,因为`XmlBeanFactory`只能解析XML格式的配置文件,而无法解析注解配置。而`ClassPathXmlApplicationContext`可以同时解析XML和注解配置文件,因此使用`new ClassPathXmlApplicationContext("bean.xml")`可以成功读取Spring注解配置文件。
所以我们在IDEA中使用BeanFactory的时候可以看到一个删除线:
此删除线的意思是 IDEA 不推荐使用这个方法, 而不是说不能用, 不推荐使用的原因是此方法已经过时, 有更好, 更安全的方法替代. 例如 ApplicationContext.
(3) @Bean 注解的重命名
以下是@Bean注解的源码, 我们可以参考参考
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package org.springframework.context.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.springframework.beans.factory.annotation.Autowire; import org.springframework.core.annotation.AliasFor; @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { @AliasFor("name") String[] value() default {}; @AliasFor("value") String[] name() default {}; /** @deprecated */ @Deprecated Autowire autowire() default Autowire.NO; boolean autowireCandidate() default true; String initMethod() default ""; String destroyMethod() default "(inferred)"; }
在使用@Bean注解的时候, 可以通过name 属性来对Bean进行重新命名, 例如将下面一个名为myBean的Bean对象重新命名为newName:
@Bean(name = "newName") public MyBean myBean() { return new MyBean(); }
或者是直接在里面传入字符串, 即name = 此字符串
此外,还可以使用@Bean
注解的value
属性来进行重命名,例如:
@Bean(name = "newName") public MyBean myBean() { return new MyBean(); }
我们总结一下这三种方式:
@Bean支持指定多个别名
需要注意的是, 不管是value 还是 name 来命名这个Bean注解, 都不能再使用原来的方法名来获取这个Bean对象了.
不管是value还是name. 它们都是一个字符串的数组, 可以传入多个别名:
@Bean( value = {"newName","newName2","newName3"}) public Test1 test1() { Test1 test1 = new Test1(); test1.setAge(18); test1.setName("张三"); return test1; }
但是这几个名字newName, newName2,newName3都是指的同一个类.
需要注意的一个问题是, 如果我连续有三个方法或者是更多的方法都用的同一个Bean别名, 如下:
@Bean("getTest1") public Test1 test1() { Test1 test1 = new Test1(); test1.setAge(18); test1.setName("张三"); return test1; } @Bean("getTest1") public Test1 test2() { Test1 test1 = new Test1(); test1.setAge(17); test1.setName("李四"); return test1; } @Bean("getTest1") public Test1 test3() { Test1 test1 = new Test1(); test1.setAge(19); test1.setName("王五"); return test1; }
这个时候我再去访问这个getTest1这个Bean对象, 将会输出哪一个??
-- 输出:
从结果上来看是输出的第一个, 其实这个是和它的加载的顺序时有关系的, 其中我们可以使用@Order注解来设定我们@Bean注解的加载顺序
如果多个Bean的名称相同, 那么程序执行不会报错, 但是第一个Bean之后的对象不会被存放到容器中, 也就是只有在第一次创建Bean的时候, 会将对象和Bean名称关联起来, 后续再有相同名称的Bean存储的时候, 容器会自动忽略.
同时如果我们的方法里面传入了参数, 那么在编译期间也会抛出异常:
@Bean("getTest1") public Test1 test1(int age) { Test1 test1 = new Test1(); test1.setAge(age); test1.setName("张三"); return test1; }
或者版本更高一点的编译器会在编译前就出现错误提示:
(4) 获取Bean对象 -- 对象装配
获取Bean对象, 也叫作对象装配, 是把对象取出来放到某个类中, 有时候页脚对象注入
对象装配的实现方法有以下三种:
- 属性注入
- 构造方法注入
- Setter注入
属性注入
属性注入是使用@Autowired实现的, 将Service类注入到Controller中, Service类中的代码实现如下:
首先创建User类:
package com.java.demo.User; public class User { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "age=" + age + ", name='" + name + '\'' + '}'; } }
创建UserService类:
@Service public class UserService { public User getUser(Integer id) { User user = new User(); user.setAge(id); user.setName("my" + id); return user; } }
创建UserController类:
@Controller public class UserController { @Autowired private UserService userService; public User getUser(Integer id) { return userService.getUser(id); } }
生成启动类:
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml"); UserController userController = context.getBean(UserController.class); System.out.println(userController.getUser(1).toString()); } }
解释: 我们首先配置好spring上下文对象之后, 就去获取这个userController的Bean对象, 然后调用其getUser方法 ,但是你可能会问, userController的getUser方法里面是UserService的getUser方法,但是这个对象里面的UserService字段userService未赋值, 怎么能调用其getUser方法啊, 这其实就是Autowired注解的作用, 他自动将UserService的Bean对象赋值给这个UserService字段, 然后调用.
随后在userService的对象中调用getUser(1)
最后返回User, 调用这个User的toString方法. 生成的结果如下:
属性注入优缺点
属性注入使用非常简单, 但是也存在着一些问题:
(a) 无法注入被final修饰的变量
图中显示, 变脸没有被初始化, 这个时候如果我们在这个类中加上这个变量的构造方法:
@Autowired private final UserService userService; public UserController() { userService = new UserService(); }
就不会出现编译前异常.
(b) 通用性问题: 只是用与ioc容器
(c) 更容易违背单一性设计元素, 但是用起来更简单.
setter注入
创建类UserService:
public class UserService { public UserRepository userRepository; @Autowired public void setUserRepository(UserRepository userRepository) { this.userRepository = userRepository; } public void sayHi() { System.out.println("UserService says hi"); userRepository.add(); } }
创建类UserRepository类:
@Repository public class UserRepository { public int add() { System.out.println("userRepository add"); return 1; } }
添加main方法:
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml"); UserService userService = context.getBean("userService", UserService.class); userService.sayHi(); } }
此时UserService类中的UserRepository字段会根据@Autowired修饰的setter方法自动注入Bean对象.
结果输出--
setter注入优缺点
(a) 无法注入被final修饰的变量 .
(b) setter中每一次都只设置一个属性, 遵守设置的单一性设计原则.
(c) setter注入的对象可以被修改.
构造方法注入 (官方推荐)
创建UserRepository类:
@Repository public class UserRepository { public int add() { System.out.println("userRepository add"); return 1; } }
创建UserService类:
@Service public class UserService { private UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public void sayHi() { System.out.println("sayHi in UserService: " + userRepository.add()); } }
添加启动项:
public class Main { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml"); UserService userService = context.getBean("userService", UserService.class); userService.sayHi(); } }
-- 输出
此时此刻,我将UserService类中的构造方法上面的@Autowired注解给注释掉:
@Service public class UserService { private UserRepository userRepository; // @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public void sayHi() { System.out.println("sayHi in UserService: " + userRepository.add()); } }
启动, 发现仍然可以输出结果:
为什么? 其实这是官方推荐的注入方法,所以被spring官方特别照顾的注入方式, 标准的方法是要加的.
如果当前类中只存在一个构造方法时, @Autowired是可以省略的.
如果有多个构造方法, 仍然注释掉@Autowired :
// @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public UserService(UserRepository userRepository, Integer i) { this.userRepository = userRepository; }
此时启动的话是会报错的:
释放掉这个 @Autowired :
@Service public class UserService { private UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; } public UserService(UserRepository userRepository, Integer i) { this.userRepository = userRepository; } public void sayHi() { System.out.println("sayHi in UserService: " + userRepository.add()); } }
问题消失:
使用构造方法去注入一个对象, 即使这个对象是被final修饰的, 仍然可以注入:
private final UserRepository userRepository; @Autowired public UserService(UserRepository userRepository) { this.userRepository = userRepository; }
构造方法优缺点
优点
- 可以注入一个被final修饰的变量
- 注入的对象不会被修改, 因为构造方法只会加载一次
- 构造方法注入可以保证注入对象完全初始化
- 构造方法注入通用性更好
缺点
- 写法比属性注入复杂
- 使用构造方法注入, 无法解决循环依赖问题.
(5) 另外一种注入关键字: @Resource
在进行类注入的时候,除了可以使用@Autowired 注解之外, 还可以使用@Resource进行注入, 如下:
@Controller public class UserController { @Resource private UserService userService; public User getUser(Integer id) { return UserService.getUser(id); } }
@Autowired 和 @Resource 的区别
- 出身不同, @Autowired 来自spring, 而@Resource来自JDK注解
- 使用时设置的参数不同, 相比于@Autowired来说, @Resource支持更多的参数设置, 例如name, 根据名词获取Bean
- @Autowired 可以用于setter注入, 构造方法注入, 属性注入, 但是@Resource只能用于setter注入, 和属性注入, 不能使用构造方法注入.
- IDEA兼容性不同, 使用@Autowired 可能在idea专业版可能会出现误报的问题.
(6) 同一个变量多个@Bean注入报错
对于同一个对象进行注入的时候, 但是找到了多个Bean对象, 此时spring就不知道注入哪一个,就会产生报错信息, 代码如下;
@Component public class Users { @Bean public User user1() { User user = new User(); user.setAge(18); user.setName("张三"); return user; } @Bean public User user2() { User user = new User(); user.setAge(20); user.setName("李四"); return user; } }
在另外一个类中获取User对象, 如下:
public class UserController { @Resource private User user; public User getUser() { return user; } }
main方法:
public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("springConfig.xml"); UserController userController = context.getBean("userController", UserController.class); }
-- 输出
找到了两个Bean对象. 抛出此异常.
分析: User类中有两个方法都是使用了@Bean注解, 在UserController类中使用@Resource进行属性注入, 此时就会根据@Bean来获取到这个User对象,但是这里有两个Bean对象, 到底赋值哪一个?
原因: 非唯一的Bean对象
解决方案:
- @Resource的name值去定义, 例如 @Resource(name = "user1")
- 使用@Qualifier注解定义其使用的Bean对象的名称
解决方案的实例:
(a) 使用@Resource 的name注解:
@Controller public class UserController { @Resource(name = "user1") private User user; public User getUser() { return user; } }
(b) 使用@Qualifier注解:
@Controller public class UserController { @Resource @Qualifier(value = "user1") private User user; public User getUser() { return user; } }
总结
- 将对象存储到spring中的方法:
- 使用类注解: @Controller, @Service, @Repository, @Configuration, @Component
- 使用方法注解: @Bean, 必须配合上面的类注解来使用
- Bean命名的规则, 首字母和第二个字母都非大写, 获取Bean的用首字母小写,如果首字母和第二个字母都是大写, 那么直接使用欧原来的Bean名获取Bean对象.
- 从spring中获取对象
- 属性注入
- setter注入
- 构造方法注入(官方推荐)
- 注入的关键字还有
- @Autowired
- @Resource
- @Resource和@Autowired的区别
- 解决同一变量不同的Bean对象的问题
- 使用@Resource(name = "xxx")
- 使用@Qualifier(value = "xxx")