文章目录
获取 Spring 上下文对象的方式
首先获取 Bean 对象之前都需要先获取 Spring 的上下文对象,那么获取这个对象可以有以下方式
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
那么这两者之间有什么区别呢,现在新建两个 Bean 类运行起来后看结果:
1、ApplicationContext
2、BeanFactory
两者的运行结果是不一样的,通过 BeanFactory 实例出来的并不会将 Teacher 对象放入 Spring
ApplicationContext 和 BeanFeactory:
- ApplicationContext 会将 xml文件中所声明需要注册到Spring中的 Bean 一次性全部注册完成,而BeanFactory 则是在首次去获取某一个Bean 对象时才会去注册该对象
- ApplicationContext 比较废内存但是之后的读取会很快
- BeanFeactory 比较省内存但是效率较低
- ApplicationContext 是 BeanFeactory 的子类,ApplicationContext 还拥有独特的特性, 添加了对国际化支持、资源访问支持、以及事件传播等方面的支持
存储 Bean 对象的方式
基本的存储方式需要在 spring-config.xml 文件中去添加指定的 Bean 注册内容才行,这样就很麻烦。
类注解
配置扫描路径(必须)
首先在进行注解前需要先配置好路径,在 spring-config.xml 中添加下面的代码
<content:component-scan base-package="spring.demo"></content:component-scan>
base-package中添加的是一串路径,也就是声明这个路径下的包中的 Bean 是有可能需要存入到 Spring 中的。注意只是有可能,开始的时候并没有立即注册存放到 Spring 中。
@Controller(控制器存储)
验证用户请求的数据正确性,相当于“安保系统”
Student类
import org.springframework.stereotype.Controller;
@Controller // 将当前类存储到 Spring 中
public class Student {
public Student(){
System.out.println("Student init");
}
public void print(String str){
System.out.println(str);
}
}
启动类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import spring.demo.Student;
public class Start {
public static void main(String[] args) {
// 获取 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 获取 Bean 对象
Student student = (Student) context.getBean("student", Student.class);
// 使用 Bean 对象
student.print("hello world");
}
}
因为使用了注解后并没有指定id属性,这时需要将id属性写为类名的小驼峰形式,这是一个“约定”。
但是也有特例:
原类名如果首字母和第二个字母都是大写的话,id属性的名称就和原类名相同
@Service(服务)
编排和调度具体执行方法,相当于“客服中心”
Student类
@Service // 将当前类存储到 Spring 中
public class Student {
public Student(){
System.out.println("Student init");
}
public void print(String str){
System.out.println(str);
}
}
这样也同样可以执行程序
@Repository(持久层)
和数据库进行交互,是一个“执行者”(DAO)
Student类
@Repository // 将当前类存储到 Spring 中
public class Student {
public Student(){
System.out.println("Student init");
}
public void print(String str){
System.out.println(str);
}
}
这样也同样可以执行程序
@Component(工具)
主要是注解工具类
Student类
@Component // 将当前类存储到 Spring 中
public class Student {
public Student(){
System.out.println("Student init");
}
public void print(String str){
System.out.println(str);
}
}
这样也同样可以执行程序
@Configuration(项目中的一些配置)
Student类
@Configuration // 将当前类存储到 Spring 中
public class Student {
public Student(){
System.out.println("Student init");
}
public void print(String str){
System.out.println(str);
}
}
这样也同样可以执行程序
关于五大类注解
- 如果遇到同一个包中有 同名的类 那么可以在使用注解时使用例如 @Controller(value = “XXX”) 这种方式去分别,但是建议一个包中尽量不要有同名类
- 五大注解的关系:事实上五大注解都是基于 Component 实现的,也就是它们都是 Component 的“子类”,是对于 Component 的扩展。那么需要分出这么多类注解的原因就是让程序员看到类注解之后,就能直接了解当前类的用途
方法注解
方法注解就是在一个有返回值的方法上加上 @Bean 注解的方式,也就是说 Spring 会将有着 @Bean 注解的方法的返回值的对象存入
需要注意:
- @Bean 注解需要配合五大类注解使用
- 使用了@Bean 注解后,默认的 Bean 对象ID属性为该具有 @Bean 注解的方法名
- 可以使用 @Bean(name = {“XXX”})的形式去设置这个 Bean 对象的ID属性。并且这个重命名的 name 其实是⼀个数组,一个 Bean 可以有多个名字,例如 @Bean(name = {“XXX”, “XXX”})。并且 name= 是可以去掉的,例如 @Bean({“XXX”, “XXX”})
@Component
public class UserBeans {
@Bean({"user"})
public User getUser(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
public class Start {
public static void main(String[] args) {
// 获取 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 获取 Bean 对象
User user = context.getBean("user", User.class);
// 使用 Bean 对象
System.out.println(user.getName());
}
}
获取指定的 Bean 对象的方式
获取到上下文对象之后就可以通过调用该对象的 getBean方法去获取 Bean 对象
普通方式
首先常规的就是使用加载时设置的 id 属性去获取
Student student = (Student) context.getBean("student");
需要注意这种方式的返回值是 Object 类的,因此需要强转为 Bean 对象的类
第二种方式可以通过 Bean类的.class 加上 id 属性去获取
Student student = context.getBean("student", Student.class);
这样写法就比较优美
第三种方式可以直接通过 .class去获取,但是存在隐患
Student student = context.getBean(Student.class);
存在什么隐患呢,首先一个 Bean类是可以存放多个对象到 Spring 中的,也就是可以这样
<bean id="student" class="spring.demo.Student"></bean> <bean id="student2" class="spring.demo.Student"></bean>
那么如果还使用第三种方式就会出现,编译器不知道具体是要获取哪一个对象,就会报错
因此不建议使用这种方式
对象注入
在 Spring 中实现依赖注入的常见方式有以下 3 种:
- 属性注入(Field Injection);
- 构造方法注入(Constructor Injection);
- Setter 注入(Setter Injection)。
属性注入
属性注⼊是使⽤ @Autowired 实现的,例如下列代码
// UserService
@Service
public class UserService {
public void print(){
System.out.println("hello");
}
}
// UserController
@Controller
public class UserController {
@Autowired
private UserService userService;
public void print(){
userService.print();
}
}
// Start
public class Start {
public static void main(String[] args) {
// 获取 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 获取 Bean 对象
UserController user = context.getBean("userController", UserController.class);
// 使用 Bean 对象
user.print();
}
}
首先可以看到 UserService 和 UserController 这两个类都是加了类注解的,因此在程序运行后,这两个 Bean 都是会被存放到 Spring 中。因为在 UserController 类包含了一个 UserService 对象,并且加了 @Autowired 注解,所以这个 UserService 对象就不需要 new 出来,而是会自动从 Spring 中直接获取。这就是属性注入
属性注入的优点:
属性注入最大的优点就是实现和使用简单,只需要给变量上添加一个 @Autowired 注解,就可以在不 new 对象的情况下直接获得注入的对象
属性注入的缺点:
- 无法注入一个不可变的对象,也就是final 修饰的对象
- 只能适应于 IoC 容器
- 更容易违背单一设计原则
构造方法注入
构造方法注入是在类的构造方法中实现注入
// UserService
@Service
public class UserService {
public void print(){
System.out.println("hello");
}
}
// UserController
@Controller
public class UserController {
private UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
public void print(){
userService.print();
}
}
// Start
public class Start {
public static void main(String[] args) {
// 获取 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 获取 Bean 对象
UserController user = context.getBean("userController", UserController.class);
// 使用 Bean 对象
user.print();
}
}
当该类中只有一个构造方法的时候,可以省略 @Autowired 注解,但是有多个构造方法时就需要在需要注入的方法上加上 @Autowired 注解
构造方法注入的优点:
- 可注入不可变对象;
- 注入对象不会被修改;(构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况)
- 注入对象会被完全初始化;(注入的对象在使用之前会被完全初始化)
- 通用性更好。(可适用于任何环境,无论是 IoC 框架还是非 IoC 框架)
Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上 @Autowired 注解
// UserService
@Service
public class UserService {
public void print(){
System.out.println("hello");
}
}
// UserController
@Controller
public class UserController {
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void print(){
userService.print();
}
}
// Start
public class Start {
public static void main(String[] args) {
// 获取 Spring 的上下文对象
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
// 获取 Bean 对象
UserController user = context.getBean("userController", UserController.class);
// 使用 Bean 对象
user.print();
}
}
Setter注入的优点:
- 完全符合单一职责的设计原则,一个set方法只针对一个对象
Setter注入的缺点:
- 不能注入不可变对象;(final 修饰的对象)
- 注入的对象可被修改。(因为set方法的缘故,因此对象可以被随时随地修改)
@Resource:另⼀种注入关键字
@Controller
public class UserController {
@Resource
private UserService userService;
public void print(){
userService.print();
}
}
@Autowired 和 @Resource 的区别:
- @Autowired 来自于 Spring,而 @Resource 来自于 JDK 的注解
- 相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean,可以使用@Resource(name=“XXX”) 指定获取。而 @Autowired 需要配合 @Qualifier(value = “XXX”)
- @Resource 只能用于 Setter 注入和属性注入,不能用于构造函数注入
总结
属性注入的写法最简单,所以日常项目中使用的频率最高,但它的通用性不好;
而 Spring 官方推荐的是构造方法注入,它可以注入不可变对象,其通用性也更好。
如果是注入可变对象,那么可以考虑使用 Setter 注入