经过前文的学习,我们已经可以实现基本的Spring存储和读取Bean了,但是在操作过程中却发现还是比较繁琐——存储一个对象就需要在xml中写入一次(远古方法)。因此本文将会使用注解这种方式实现更为简单的存储和读取(现代方法)
一:存储Bean
(1)准备工作:配置扫描路径
要想使用注解将对象存储的Spring中,需要配置一下Bean的扫描包路径,只有被配置的包下的所有类,添加了注解才能被正确识别并存储到Spring中
如下,项目包结构设置为这样
在spring-config.xml中添加如下配置,其中base-package
处填入扫描根路径
<?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="com.service"></content:component-scan>
</beans>
(2)利用注解存储Bean
要想将对象存储在Spring中,有两种注解类型可以实现
- 类注解:
@Controller
:控制器存储@Service
:服务存储@Repository
:仓库存储@Component
:组件存储@Configuration
:配置存储
- 方法注解: 将这个方法返回的对象存储到Spring中
@Bean
A:五大类注解
这五大类注解它们的功能是一模一样的,之所以要这样设置,其实是为了标识。具体来说,当程序员看到类注解后,就能直接了解当前类的用途,例如
@Controller
:业务逻辑层,用于控制用户行为,检测用户参数合法性@Service
:服务层,调用持久化类实现相应功能(不直接和数据库交互、类似于控制中心)@Repository
:持久层,是直接和数据库进行交互的,通常来说每一张表对应一个@Repository
@Component
:组件层,归属于公共工具类@Configuration
:配置层,是用来配置当前项目的一些信息
@Controller
(控制器存储):
package com.service;
import org.springframework.stereotype.Controller;
@Controller
public class StudyController {
public String SayHello(String name) {
return name + ":Hello!";
}
}
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
StudyController studyController = (StudyController) context.getBean(StudyController.class);
// 使用Bean
System.out.println(studyController.SayHello("ZhangSan"));
}
}
@Service
(服务存储):
package com.service;
import org.springframework.stereotype.Service;
@Service
public class StudyService {
public String SayHello(String name) {
return name + ":Hello!";
}
}
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
StudyService studyService = (StudyService) context.getBean(StudyService.class);
// 使用Bean
System.out.println(studyService.SayHello("Service"));
}
}
@Repository
(仓库存储):
package com.service;
import org.springframework.stereotype.Repository;
@Repository
public class StudyRepository {
public String SayHello(String name) {
return name + ":Hello!";
}
}
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
StudyRepository studyRepository = (StudyRepository) context.getBean(StudyRepository.class);
// 使用Bean
System.out.println(studyRepository.SayHello("Repository"));
}
}
@Component
(组件存储):
package com.service;
import org.springframework.stereotype.Component;
@Component
public class StudyComponent {
public String SayHello(String name) {
return name + ":Hello!";
}
}
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
StudyComponent studyComponent = (StudyComponent) context.getBean(StudyComponent.class);
// 使用Bean
System.out.println(studyComponent.SayHello("Component"));
}
}
@Configuration
(配置存储):
package com.service;
import org.springframework.context.annotation.Configuration;
@Configuration
public class StudyConfiguration {
public String SayHello(String name) {
return name + ":Hello!";
}
}
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
StudyConfiguration studyConfiguration = (StudyConfiguration) context.getBean(StudyConfiguration.class);
// 使用Bean
System.out.println(studyConfiguration.SayHello("Configuration"));
}
}
B:类注解之间的关系
通过查阅源码可以看到,@Controller / @Service / @Repository / @Configuration
均为@Component
的子类
// @Controller
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
...
C:Bean命名规则
- 之前获取Bean的方法只是临时展示
Bean命名规则:当使用五大类注解时,默认情况下获取Bean,只需将类名首字母小写即可。(注意如果类名首字母本身是小写也是没有问题的)
package com.service;
import org.springframework.stereotype.Controller;
@Controller
public class StudyController {
public String SayHello(String name) {
return name + ":Hello!";
}
}
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
StudyController studyController = (StudyController) context.getBean("studyController");
// 使用Bean
System.out.println(studyController.SayHello("Configuration"));
}
}
特殊情况:当对象首字母和第二个字母均为大写时,需要使用原类名才能正确获取,否则报错
- 报错:
- 究其原因是因为它调用的是JDK
introspector
中的decapitalize
方法
package com.service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
SController sController = (SController) context.getBean("SController");
// 使用Bean
System.out.println(sController.SayHello("Configuration"));
}
}
D:方法注解@Bean
方法注解@Bean:方法注解在使用必须配合类注解使用,否则将会出现错误。会将被注解的这个方法返回的对象存储Spring中,id
为方法名
// Java类
package com.model;
public class Student {
public int id;
public String name;
public int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.service;
import com.model.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Controller
public class StudentBeans {
@Bean
public Student students() {
Student student = new Student();
student.setAge(18);
student.setId(1);
student.setName("张三");
return student;
}
}
package com.service;
import com.model.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
// 创建Spring上下文
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 获取指定Bean
Student student = context.getBean("students", Student.class);
// 使用Bean
System.out.println(student);
}
}
类名不太可能重复,但是方法名重复的概率很大。如下,对于下面的情况,程序虽然不会报错,但是会出现歧义、不可控
因此,可以通过设置name
属性给Bean进行命名,并且一旦设置name
属性后,就不能使用之前的方法获取Bean了
注意
- 一个Bean可以有多个名字
name={}
可以省略
@Controller
public class StudentBeans {
@Bean(name={"s1", "s2"})
public Student students() {
Student student = new Student();
student.setAge(18);
student.setId(1);
student.setName("李四");
return student;
}
}
@Controller
public class StudentBeans {
@Bean({"s1", "s2"})
public Student students() {
Student student = new Student();
student.setAge(18);
student.setId(1);
student.setName("李四");
return student;
}
}
二:获取Bean(对象注入)
如下,一般来说项目会有三级结构,为了简化问题,在第三级结构中我们只创建controller
和service
两个层,目的将StudetService
注入到ControllerService
(1)属性注入
A:概述
属性注入式是使用@Autowired
实现
package com.demo.service;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public void sayHello () {
System.out.println("Hello, Service");
}
}
package com.demo.controller;
import com.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
// 使用属性注入的方式获取Bean
@Autowired
private StudentService studentService;
public void sayHello() {
studentService.sayHello();
}
}
package com;
import com.demo.controller.StudentController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController = context.getBean("studentController", StudentController.class);
studentController.sayHello();
}
}
B:优缺点分析
优点:
- 实现简单
- 使用简单
缺点:
①:使用属性注入无法注入一个不可变对象(final修饰)
- 这是因为在Java中final对象要么直接赋值,要么在构造方法中赋值
- 如果要注入一个不可变对象,可以使用构造方法注入
②:使用属性注入的方式只适用于 IoC 框架(容器),如果将属性注入的代码移植到其他非 IoC 的框架中,那么代码就无效了,所以属性注入的通用性不是很好
③: 由于属性注入使用非常简单,所以非常容易滥用,开发者很容易在一个类中同时注入多个对象
(2)Setter注入
A:概述
Setter注入要设置set方法,然后加上@AutoWired
注解
package com.demo.service;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public void sayHello () {
System.out.println("Hello, Service");
}
}
package com.demo.controller;
import com.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
private StudentService studentService;
// 使用Setter注入的方式获取Bean
@Autowired
public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
public void sayHello() {
studentService.sayHello();
}
}
package com;
import com.demo.controller.StudentController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController = context.getBean("studentController", StudentController.class);
studentController.sayHello();
}
}
B:优缺点分析
优点:Setter注入完全符合单一职责的设计原则,因为每一个Setter只针对一个对象
缺点:
①:不能注入不可变的对象(final修饰)
②:Setter注入提供了set方法,这意味着你可以在任何时候、任何地方通过调用set方法来改变注入对象
(3)构造方法注入
A:概述
构造方法注入是在类的构造方法中实现注入
- 注意:如果只有一个构造方法,那么
@Autowired
注解可以省略
package com.demo.service;
import org.springframework.stereotype.Service;
@Service
public class StudentService {
public void sayHello () {
System.out.println("Hello, Service");
}
}
package com.demo.controller;
import com.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@Controller
public class StudentController {
private final StudentService studentService;
// 使用构造方法注入
@Autowired
public StudentController (StudentService studentService) {
this.studentService = studentService;
}
public void sayHello() {
studentService.sayHello();
}
}
package com;
import com.demo.controller.StudentController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController = context.getBean("studentController", StudentController.class);
studentController.sayHello();
}
}
B:优缺点分析
优点:
①:可注入不可变对象
②:构造方法在对象创建时只会被执行一次,因此它不会存在注入对象被修改的情况
③:由于依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前会被完全初始化
④:构造方法注入可适用于任何环境(无论是否是IoC框架),构造方法注入的代码都是通用的
总之
- 属性注入写法最简单,日常使用频率最高,但通用性很差
- 官方推荐是构造方法注入,通用性很好
- 如果要注入可变对象,可以考虑使用Setter注入
三:补充
(1)@Resource
在执行Setter注入和属性注入的时候,还可以使用@Resource
package com.demo.controller;
import com.demo.service.StudentService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class StudentController {
@Resource
private StudentService studentService;
public void sayHello() {
studentService.sayHello();
}
}
@Autowired
和@Resource
区别如下
- 来源不同
@Autowired
来自于Spring@Resource
来自于JDK注解
- 使用时参数设置不同
@Resource
支持更多的参数设置,例如name
@Resource
只能用于Setter注入和属性注入,不能用于构造方法注入
(2)同一类型多个@Bean报错问题
对于下面的代码,当出现多个@Bean
,然后返回同一对象类型时程序会报错
package com.demo.model;
public class Student {
public int id;
public String name;
public int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.demo.service;
import com.demo.model.Student;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
@Controller
public class StudentBean {
@Bean
public Student student1() {
Student student = new Student();
student.setAge(18);
student.setId(1);
student.setName("张三");
return student;
}
@Bean
public Student student2() {
Student student = new Student();
student.setAge(20);
student.setId(2);
student.setName("李四");
return student;
}
}
package com.demo.controller;
import com.demo.model.Student;
import com.demo.service.StudentService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class StudentController {
@Resource
private Student student;
public void sayHello() {
System.out.println(student);
}
}
package com;
import com.demo.controller.StudentController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
StudentController studentController = context.getBean("studentController", StudentController.class);
studentController.sayHello();
}
}
解决这个问题有两种方法
①:使用@Resource(name="")
声明需要哪个Bean
package com.demo.controller;
import com.demo.model.Student;
import com.demo.service.StudentService;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class StudentController {
@Resource(name = "student2")
private Student student;
public void sayHello() {
System.out.println(student);
}
}
②:如果使用的是@Autowired
,那么可以使用@Qualifier(value="")
声明
package com.demo.controller;
import com.demo.model.Student;
import com.demo.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class StudentController {
@Autowired
@Qualifier(value = "student1")
private Student student;
public void sayHello() {
System.out.println(student);
}
}
评论区