目录
🧥1 配置扫描路径
🧤2 类注解实现 Bean 对象的存储
🩱2.1 五大类注解的使用
🎁2.2 五大类注解之间的关系
🎏2.3 Java 项目的标准分层
🎃3 方法注解实现 Bean 对象的存储
🎈3.1 Bean 注解必须配合五大类注解一起使用
✨3.2 重命名 @Bean 的几种方式
🎁4 依赖注入
在上一篇的文章的最后(Spring 的创建与使用),我介绍了往 Spring 里存储 Bean 的方式:在 spring_config 中添加一行 bean 注册内容如下:
采用这种写法的话,每一个类都必须在 spring-config 里写上这么一行。然而有另一种更加便捷的方式,可以在 spring-config 里只添加一行,便可以存储且获取同一路径下的所有对象。
1 配置扫描路径
与上述写法不同的是,不再使用 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"
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.java.demo"></content:component-scan>
</beans>
配置 bean 的扫描路径意味着,只有当前目录下的类才会扫描是否添加了注解。如果添加了注解,就将这些添加了注解的类存放到 IoC 容器中。
下面介绍两种更加简单存储 Bean 对象的方式
2 类注解实现 Bean 对象的存储
五大类注解:
1. @Controller【控制器】校验参数的合法性(安检系统)
2. @Service【服务】业务组装(客服中心)
3. @Repository【数据持久层】实际业务处理(实际办理的业务)
4. @Component【组件】工具类层(基础的工具)
5. @Configuration【配置层】配置
2.1 五大类注解的使用
package com.java.demo;
import org.springframework.stereotype.Controller;
@Controller
public class User {
public void sayHi(){
System.out.println("Hi User~");
}
}
package com.java.demo;
import org.springframework.stereotype.Service;
@Service
public class Student {
public void sayHi(){
System.out.println("Hi! Student!");
}
}
package com.java.demo;
import org.springframework.stereotype.Repository;
@Repository
public class ABCdefH {
public void sayHi(){
System.out.println("Hi! ABCdefH!");
}
}
package com.java.demo;
import org.springframework.stereotype.Component;
@Component
public class TEacher {
public void sayHi(){
System.out.println("Hi! TEacher!");
}
}
package com.java.demo;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Hello {
public void sayHi(){
System.out.println("Hello! !");
}
}
import com.java.demo.*;
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");
User user = context.getBean("user", User.class);
user.sayHi();
TEacher teacher = context.getBean("TEacher", TEacher.class);
teacher.sayHi();
ABCdefH abcdefh = context.getBean("ABCdefH", ABCdefH.class);
abcdefh.sayHi();
Hello hello = context.getBean("hello", Hello.class);
hello.sayHi();
Student student = context.getBean("student", Student.class);
student.sayHi();
}
}
输出:
Hi User~
Hi! TEacher!
Hi! ABCdefH!
Hello! !
Hi! Student!
类注解存储 Bean 命名时,有一个默认的命名规则:
如果首字母大写,第二个字母小写,那么 Bean 的名称就是类名小写:如上的 User、Student 以及 Hello 类。但如果不满足上述情况,那么 Bean 的名称就为原类名,如上述的 TEacher 以及 ABCdefH 类。
来看一下 Bean 生成名称的源代码:
public static String decapitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
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);
}
可以看到,五类注解方法功能上都是一样的,但为什么要分出五类来呢?为了后续代码的分层管理。
2.2 五大类注解之间的关系
依次点开五大类注解的源代码:
可以看出其他四类都是基于 Component 的,所以以后实在不知道用哪个注解比较好的话,就使用 Component 吧~
2.3 Java 项目的标准分层
3 方法注解实现 Bean 对象的存储
3.1 Bean 注解必须配合五大类注解一起使用
一起来看怎么样使用吧!
首先给定一个文章的实体类来:
package com.java.demo;
import java.time.LocalDateTime;
/*
* 普通的文章实体类
*/
public class ArticlesInfo {
private int id;
private String title;
private String content;
private LocalDateTime time;
@Override
public String toString() {
return "ArticlesInfo{" +
"id=" + id +
", title='" + title + '\'' +
", content='" + content + '\'' +
", time=" + time +
'}';
}
public void setId(int id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setContent(String content) {
this.content = content;
}
public void setTime(LocalDateTime time) {
this.time = time;
}
public int getId() {
return id;
}
public String getTitle() {
return title;
}
public String getContent() {
return content;
}
public LocalDateTime getTime() {
return time;
}
}
创建文章:
package com.java.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Controller;
import java.time.LocalDateTime;
@Controller
public class Articles {
@Bean
public ArticlesInfo articlesInfo(){
// 伪代码
ArticlesInfo articlesInfo = new ArticlesInfo();
articlesInfo.setId(1);
articlesInfo.setTitle("Know yourself");
articlesInfo.setContent("Keep Learning ~");
articlesInfo.setTime(LocalDateTime.now());
sayHi();
return articlesInfo;
}
public void sayHi(){
System.out.println("Hi~~~");
}
}
import com.java.demo.Articles;
import com.java.demo.ArticlesInfo;
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");
ArticlesInfo articles = context.getBean("articlesInfo", ArticlesInfo.class);
System.out.println(articles.toString());
}
}
输出:
ArticlesInfo{id=1, title='Know yourself', content='Keep Learning ~', time=2023-07-19T15:29:30.731}
需要注意的是,本身的类也会被存储到 Spring 中:
Articles articles1 = context.getBean("articles", Articles.class);
articles1.articlesInfo();
输出:
Hi~~~
可以发现,使用 Bean 对方法进行注解时,@Bean 的默认命名是方法名。
3.2 重命名 @Bean 的几种方式
@Bean("aaa")
@Bean(name = "bbb")
@Bean(value = "ccc")
@Bean 支持指定多个名称
@Bean(value = {"aaa", "bbb"})
需要注意的是,当 @Bean 重命名之后,默认使用方法名获取 Bean 对象的方式就不能用了。
另一个要注意的是,如果多个 Bean 使用相同的名称,程序执行不会报错。除了第一次使用某一 Bean 名称的方法之外(根据加载顺序),后面使用相同 Bean 名称的方法,都不会被存放到容器当中,会被自动忽略。
4 依赖注入
依赖注入与依赖查找的对比
· 依赖查找依靠 Bean 名称来进行查找
· @Autowired 依赖注入流程:首先根据 getType(从容器中)获取对象,如果只获取一个, 那么直接将此对象注入到当前属性上;如果获取多个对象,就会使用 getName(根据名 称)来进行匹配。
package com.java.demo;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository {
public int add(){
System.out.println("Do UserRepository add method");
return 1;
}
}
package com.java.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// Spring 2.0 使用属性注入的方式
@Autowired // DI (依赖注入)
private UserRepository userRepository;
public int add(){
System.out.println("Do UserService add method.");
// 1.传统写法
// UserRepository userRepository = new UserRepository();
// return userRepository.add();
// 2. Spring 1.0
// ApplicationContext context =
// new ClassPathXmlApplicationContext("spring-config.xml");
// UserRepository userRepository = context.getBean("userRepository", UserRepository.class);
// return userRepository.add();
//
// 3. Spring 2.0
return userRepository.add();
}
}
对着需要测试的类,右击,点击 Generate,再点 Test 生成一个测试类。通过测试类来进行测试。
package com.java.demo;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
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();
}
}
输出:
Do UserService add method.
Do UserRepository add method
可以看到使用属性注入,代码更加简洁了。
如果是下面这种情况,会出现什么问题呢?
package com.java.demo;
public class User {
private String name;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.java.demo;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class Users {
@Bean("user1")
public User user1(){
User user = new User();
user.setName("马冬梅");
return user;
}
@Bean("user2")
public User user2(){
User user = new User();
user.setName("王五");
return user;
}
}
同类型的 Bean 放入了同一个 Spring 当中,如果是 属性注入,该如何获取呢?
采用下面两种方式:
1. 将属性的名字与 Bean 的名字一一对应,是 user1 还是 user2,而不是写成的 user。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService2 {
// 属性注入
@Autowired
private User user1;
public void sayHi(){
System.out.println(user1.toString());
}
}
2. @Autowired 配合 @Qualifier 一起使用
package com.java.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@Service
public class UserService2 {
// 属性注入
@Autowired
@Qualifier("user1")
private User user;
public void sayHi(){
System.out.println(user.toString());
}
}
package com.java.demo;
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 sayHi() {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
UserService2 user = context.getBean("userService2",UserService2.class);
user.sayHi();
}
}