到了学习 JavaEE 这块要有一个思想,实现一个功能的时候,先考虑下有没有实现对应功能的注解.
在 Spring 中想要更简单的存储和读取对象的核心是使用注解,也就是我们接下来要学习 Spring 中的相关注解,来存储和读取 Bean 对象
1.存储 Bean 对象
之前我们存储 Bean 时,需要在 spring-config 中添加一行 bean 注册内容才行,如下图所示
而现在我们只需要一个注解就可以替代之前要写一行配置的要求
1.前置工作
在 spring-config.xml 添加如下配置:
<?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="">
</content:component-scan>
</beans>
base-package 写自己的内容
2.添加注解存储 Bean 对象
想要将对象存储在 Spring 中,有两种注解类型可以实现:
-
类注解:@Controller、@Service、@Repository、@Component、@Configuration
-
方法注解:@Bean
1.@Controller(控制器)
校验参数的合法性(类似于安检系统)
package com.wjh.demo;
import org.springframework.stereotype.Controller;
/**
* @projectName: test-2023-11-22
* @package: com.wjh.demo
* @className: User
* @author: 王嘉辉
* @description:
* @date: 2023/11/22 19:16
* @version: 1.0
*/
@Controller("userinfo")
public class User {
public void sayHello() {
System.out.println("Hi,User!");
}
}
import com.wjh.demo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @projectName: test-2023-11-22
* @package: PACKAGE_NAME
* @className: App
* @author: 王嘉辉
* @description:
* @date: 2023/11/22 19:16
* @version: 1.0
*/
public class App {
public static void main(String[] args) {
//1.得到容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 对象
User user = context.getBean("userinfo",User.class);
//3.使用 Bean 对象
user.sayHello();
}
}
2.@Service(服务)
业务组装(相当于客服中心)
不做业务功能的实现
@Service
public class User {
public void sayHello() {
System.out.println("Hi,User!");
}
}
3.@Repository(数据持久层)
实际的业务处理(实际办理的业务)
@Repository
public class User {
public void sayHello() {
System.out.println("Hi,User!");
}
}
4.@Component(组件)
工具类层(基础的工具)
@Component
public class User {
public void sayHello() {
System.out.println("Hi,User!");
}
}
5.@Configuration(配置)
配置层(配置)
@Configuration
public class User {
public void sayHello() {
System.out.println("Hi,User!");
}
}
3.类注解存储 Bean 命名问题
默认类名首字母小写,就能获取到 Bean 对象
获取不到 Bean 对象
使用原类名,可以获取到 Bean 对象
我们可以在 Idea 中使用搜索关键字“beanName”
顺藤摸瓜,我们最后找到了 bean 对象的命名规则的方法:
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);
}
4. 为什么要这么多类注解
为什么需要这么多的类注解也是相同的原因,就是让程序员看到类注解之后,就能直接了解当前类的用途,比如:
-
@Controller:表示的是业务逻辑层;
-
@Servie:服务层;
-
@Repository:持久层;
-
@Configuration:配置层。
程序的工程分层,调用流程如下:
查看 @Controller / @Service / @Repository / @Configuration 等注解的源码发现:
其实这些注解里面都有一个注解 @Component,说明它们本身就是属于@Component 的“子类”。
5.方法注解 @Bean
注意事项:
在使用 @Bean 注解 的时候,必须要配合五大类注解一起使用
@Bean 获取时 @Bean 默认命名 = 方法名
package com.wjh.demo.model;
/**
* @projectName: test-2023-11-23
* @package: com.wjh.demo.service.model
* @className: ArticleInfo
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 19:28
* @version: 1.0
*/
import java.time.LocalDateTime;
/**
*普通的文章实体类
*/
public class ArticleInfo {
private int aid;
private String title;
private String content;
private LocalDateTime createTime;
public int getAid() {
return aid;
}
public void setAid(int aid) {
this.aid = aid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public LocalDateTime getCreateTime() {
return createTime;
}
public void setCreateTime(LocalDateTime createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "ArticleInfo{" +
"aid=" + aid +
", title='" + title + '\'' +
", content='" + content + '\'' +
", createTime=" + createTime +
'}';
}
}
package com.wjh.demo.service;
import com.wjh.demo.model.ArticleInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @projectName: test-2023-11-23
* @package: com.wjh.demo.service
* @className: Articles
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 19:33
* @version: 1.0
*/
@Component
public class Articles {
@Bean //将当前方法返回的对象存储到 IoC 容器
public ArticleInfo articleInfo() {
//伪代码
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setAid(1);
articleInfo.setTitle("今天周几?");
articleInfo.setContent("今天周四!!");
articleInfo.setCreateTime(LocalDateTime.now());
return articleInfo;
}
}
import com.wjh.demo.model.ArticleInfo;
import com.wjh.demo.service.UConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @projectName: test-2023-11-23
* @package: PACKAGE_NAME
* @className: App
* @author: 王嘉辉
* @description:
* @date: 2023/11/22 19:16
* @version: 1.0
*/
public class App {
public static void main(String[] args) {
//1.得到容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 对象
//User user = context.getBean("user",User.class);
//UserService userService = context.getBean("userService",UserService.class);
//Teacher teacher = context.getBean("teacher",Teacher.class);
//UConfig uConfig = context.getBean("UConfig",UConfig.class);
ArticleInfo articleInfo = context.getBean("articleInfo",ArticleInfo.class);
//3.使用 Bean 对象
//user.sayHello();
// userService.sayHi();
//teacher.sayHi();
//uConfig.sayHi();
System.out.println(articleInfo.toString());
}
}
6.重命名 Bean 的几种方式
方式一:
package com.wjh.demo.service;
import com.wjh.demo.model.ArticleInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @projectName: test-2023-11-23
* @package: com.wjh.demo.service
* @className: Articles
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 19:33
* @version: 1.0
*/
@Component
public class Articles {
@Bean("aaa") //将当前方法返回的对象存储到 IoC 容器
public ArticleInfo articleInfo() {
//伪代码
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setAid(1);
articleInfo.setTitle("今天周几?");
articleInfo.setContent("今天周四!!");
articleInfo.setCreateTime(LocalDateTime.now());
return articleInfo;
}
}
import com.wjh.demo.model.ArticleInfo;
import com.wjh.demo.service.UConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @projectName: test-2023-11-23
* @package: PACKAGE_NAME
* @className: App
* @author: 王嘉辉
* @description:
* @date: 2023/11/22 19:16
* @version: 1.0
*/
public class App {
public static void main(String[] args) {
//1.得到容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 对象
//User user = context.getBean("user",User.class);
//UserService userService = context.getBean("userService",UserService.class);
//Teacher teacher = context.getBean("teacher",Teacher.class);
//UConfig uConfig = context.getBean("UConfig",UConfig.class);
ArticleInfo articleInfo = context.getBean("aaa",ArticleInfo.class);
//3.使用 Bean 对象
//user.sayHello();
// userService.sayHi();
//teacher.sayHi();
//uConfig.sayHi();
System.out.println(articleInfo.toString());
}
}
方式二:
package com.wjh.demo.service;
import com.wjh.demo.model.ArticleInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @projectName: test-2023-11-23
* @package: com.wjh.demo.service
* @className: Articles
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 19:33
* @version: 1.0
*/
@Component
public class Articles {
@Bean(name = "bbb") //将当前方法返回的对象存储到 IoC 容器
public ArticleInfo articleInfo() {
//伪代码
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setAid(1);
articleInfo.setTitle("今天周几?");
articleInfo.setContent("今天周四!!");
articleInfo.setCreateTime(LocalDateTime.now());
return articleInfo;
}
}
import com.wjh.demo.model.ArticleInfo;
import com.wjh.demo.service.UConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @projectName: test-2023-11-23
* @package: PACKAGE_NAME
* @className: App
* @author: 王嘉辉
* @description:
* @date: 2023/11/22 19:16
* @version: 1.0
*/
public class App {
public static void main(String[] args) {
//1.得到容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 对象
//User user = context.getBean("user",User.class);
//UserService userService = context.getBean("userService",UserService.class);
//Teacher teacher = context.getBean("teacher",Teacher.class);
//UConfig uConfig = context.getBean("UConfig",UConfig.class);
ArticleInfo articleInfo = context.getBean("bbb",ArticleInfo.class);
//3.使用 Bean 对象
//user.sayHello();
// userService.sayHi();
//teacher.sayHi();
//uConfig.sayHi();
System.out.println(articleInfo.toString());
}
}
方法三:
package com.wjh.demo.service;
import com.wjh.demo.model.ArticleInfo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* @projectName: test-2023-11-23
* @package: com.wjh.demo.service
* @className: Articles
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 19:33
* @version: 1.0
*/
@Component
public class Articles {
@Bean(name = {"bbb","ccc"}) //将当前方法返回的对象存储到 IoC 容器
public ArticleInfo articleInfo() {
//伪代码
ArticleInfo articleInfo = new ArticleInfo();
articleInfo.setAid(1);
articleInfo.setTitle("今天周几?");
articleInfo.setContent("今天周四!!");
articleInfo.setCreateTime(LocalDateTime.now());
return articleInfo;
}
}
import com.wjh.demo.model.ArticleInfo;
import com.wjh.demo.service.UConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @projectName: test-2023-11-23
* @package: PACKAGE_NAME
* @className: App
* @author: 王嘉辉
* @description:
* @date: 2023/11/22 19:16
* @version: 1.0
*/
public class App {
public static void main(String[] args) {
//1.得到容器对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//2.得到 Bean 对象
//User user = context.getBean("user",User.class);
//UserService userService = context.getBean("userService",UserService.class);
//Teacher teacher = context.getBean("teacher",Teacher.class);
//UConfig uConfig = context.getBean("UConfig",UConfig.class);
ArticleInfo articleInfo = context.getBean("bbb",ArticleInfo.class);
//3.使用 Bean 对象
//user.sayHello();
// userService.sayHi();
//teacher.sayHi();
//uConfig.sayHi();
System.out.println(articleInfo.toString());
}
}
当使用 @Bean 重命名之后,此时在使用方法名获取 Bean 对象就不可行了
7.@Bean 的注意事项
如果多个 Bean 使用相同的名称,那么程序执行不会报错,但是第一个 Bean 之后的对象不会被存放到容器中,也就是只有在第一次创建 Bean 的时候会将对象和 Bean 的名称关联起来,后续再有相同名称的 Bean 存储时,容器会自动忽略
2.获取 Bean 对象
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注入。
对象装配(对象注入)的实现方法以下 3 种:
- 属性注入
- 构造方法注入
- Setter 注入
1.属性注入
package com.java.demo.dao;
import org.springframework.stereotype.Repository;
/**
* @projectName: Demo
* @package: com.java.demo.dao
* @className: UserRepository
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 21:38
* @version: 1.0
*/
@Repository
public class UserRepository {
public int add() {
System.out.println("Do UserRepository add method.");
return 1;
}
}
package com.java.demo.service;
import com.java.demo.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
/**
* @projectName: Demo
* @package: com.java.demo.service
* @className: UserService
* @author: 王嘉辉
* @description:
* @date: 2023/11/23 21:37
* @version: 1.0
*/
@Service
public class UserService {
//1.属性注入
@Autowired //DI (依赖注入)
private UserRepository userRepository;
public int add() {
System.out.println("Do UserService add method.");
/*//传统写法
UserRepository userRepository = new UserRepository();
return userRepository.add();*/
/*//Spring 1.0
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserRepository userRepository = context.getBean("userRepository",UserRepository.class);
return userRepository.add();*/
//Spring 2.0
return userRepository.add();
}
}
package com.java.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.swing.*;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
@org.junit.jupiter.api.Test
void add() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
依赖注入 VS 依赖查找
- 依赖查找依赖 Bean 名称的
- @Autowired 依赖注入流程:首先先根据 getType (从容器中) 获取对象,如果只获取到一个,那么直接将此对象注入到当前属性上,如果获取到多个对象,才会使用 getName(根据名称) 进行匹配.
@Component
public class Users {
@Bean
public User user1() {
User user = new User();
user.setId(1);
user.setName("Java");
return user;
}
@Bean
public User user2() {
User user = new User();
user.setId(2);
user.setName("MySQL");
return user;
}
}
@Controller
public class UserController4 {
// 注⼊
@Resource
private User user;
public User getUser() {
return user;
}
}
同类型的 Bean 存储到容器多个,获取时报错:
解决方案:
1.将属性的名字和 Bean 的名字对应上
2.@Autowired 配合 @Qualifier 一起使用
@Controller
public class UserController5 {
// 注⼊
@Autowired
@Qualifier(value = "user2")
private User user;
public User getUser() {
return user;
}
}
使用属性注入的优缺点:
优点:简洁,使用简单
缺点:
1.无法注入 final 修饰的变量
被 final 修饰的变量需要直接赋值或构造方法中进行赋值
它不符合 Java 中 final 的使用规范,所以就不能注入成功了
2.通用性问题:使用属性注入的方式只适用于 IoC 框架(容器)
3.设计原则问题:更容易违背单一设计原则。
使用属性注入的方式,因为使用起来很简单,所以开发者很容易在一个类中同时注入多个对象,而这些对象的注入是否有必要?是否符合程序设计中的单一职责原则?就变成了一个问题。
但可以肯定的是,注入实现越简单,那么滥用它的概率也越大,所以出现违背单一职责原则的概率也越大。
注意:这里强调的是违背设计原则(单一职责)的可能性,而不是一定会违背设计原则,二者有着本质的区别。
2. Setter 注入
Setter 注入和属性的 Setter 方法实现类似,只不过在设置 set 方法的时候需要加上@Autowired 注解
package com.java.demo.service;
import com.java.demo.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @projectName: test-2023-11-26
* @package: com.java.demo.service
* @className: UserService2
* @author: 王嘉辉
* @description:
* @date: 2023/11/26 19:52
* @version: 1.0
*/
@Service
public class UserService2 {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void sayHello() {
System.out.println("Do UserService2 sayHello");
userRepository.add();
}
}
package com.java.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
class UserService2Test {
@Test
void sayHello() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserService2 userService2 = context.getBean("userService2", UserService2.class);
userService2.sayHello();
}
}
Setter 注入的优缺点
优点:Setter 注入它完全符合单一职责的设计原则,因为每一个 Setter 只针对一个对象
缺点:
1.无法注入 一个 final 修饰的变量
2.Setter 注入的对象可以被修改,Setter 本来就是一个方法,既然是方法,就有可被多次调用和修改.
3.构造方法注入
标准的属性注入的写法
package com.java.demo.service;
import com.java.demo.dao.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @projectName: test-2023-11-26
* @package: com.java.demo.service
* @className: UserService3
* @author: 王嘉辉
* @description:
* @date: 2023/11/26 20:13
* @version: 1.0
*/
@Service
public class UserService3 {
private UserRepository userRepository;
@Autowired
public UserService3(UserRepository userRepository) {
this.userRepository = userRepository;
}
public void sayHi() {
System.out.println("Do UserService3 sayHi");
userRepository.add();
}
}
package com.java.demo.service;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import static org.junit.jupiter.api.Assertions.*;
class UserService3Test {
@Test
void sayHi() {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UserService3 userService3 = context.getBean("userService3", UserService3.class);
userService3.sayHi();
}
}
如果只有一个构造方法,那么 @Autowired 注解可以省略
但是如果类中有多个构造方法,那么需要添加上 @Autowired 来明确指定到底使用哪个构造方法
构造方法注入的优缺点:
优点:
1.可注入一个 final 修饰的变量
2.注入对象不会被修改
构造方法注入不会像 Setter 注入那样,构造方法在对象创建时只会执行一次,因此它不存在注入对象被随时(调用)修改的情况。
3.注入对象会被完全初始化
因为依赖对象是在构造方法中执行的,而构造方法是在对象创建之初执行的,因此被注入的对象在使用之前,会被完全初始化,这也是构造方法注入的优点之一。
4.通用性更好
构造方法和属性注入不同,构造方法注入可适用于任何环境,无论是 IoC 框架还是非 IoC 框架,构造方法注入的代码都是通用的,所以它的通用性更好。
缺点:
1.写法比属性注入复杂
2.使用构造方法注入,解决不了循环依赖的问题(Spring 的三级缓存解决)
4.@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 注入和属性注入,不能用于构造函数注入
-
使用@Autowired 在IDEA 专业版可能会误报,@Resource 不存在误报问题