上期我们讲解了Spring的创建与使用,发现 将Bean 注册到容器 这一步中,如果Bean对象过多,在注册到容器时,我们有几个Bean对象就需要几行注册,在实际开发中这是非常麻烦的,我们需要有更简单的方法去实现这一过程,这便是本篇文章的主题------Spring简单 读和取。
一、存储Bean对象[读]🍭
在Spring中我们可以使用注解存储和读取Bean对象,而其中我们有两种注解类型可以实现这个功能。
- 类注解:@Controller(控制器存储)、@Service(服务存储) 、@Repository(仓库存储)、@Component(组件存储)、@Configuration(配置存储)。
- 方法注解:@Bean。
1、配置扫描路径🍉
但是在使用注解去进行存储和读取Bean对象之前,我们还需要进行配置扫描路径。在spring-config.xml中添加如下配置:
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="com.demo.component"></content:component-scan>
</beans>
2、类注解🍉
Ⅰ、@Controller(控制器存储)🍓
ArticleController类:
kotlin
package com.demo.component;
import org.springframework.stereotype.Controller;
@Controller// 将对象存储到 Spring 中
public class ArticleController {
public String sayHi(){
return "Hello word";
}
}
还是使用上篇讲的方法 去读取Bean对象:
typescript
import com.demo.component.ArticleController;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("articleController");
//3、使用Bean(可选)
System.out.println(articleController.sayHi());//输出Hello word
}
}
Ⅱ、@Service(服务存储)🍓
ArticleController类:
kotlin
package com.demo.component;
import org.springframework.stereotype.Service;
@Service// 将对象存储到 Spring 中
public class ArticleController {
public String sayHi(){
return "Hello word";
}
}
还是使用上篇讲的方法 去读取Bean对象:
typescript
import com.demo.component.ArticleController;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("articleController");
//3、使用Bean(可选)
System.out.println(articleController.sayHi());//输出Hello word
}
}
Ⅲ、@Repository(仓库存储)🍓
ArticleController类:
kotlin
package com.demo.component;
import org.springframework.stereotype.Repository;
@Repository// 将对象存储到 Spring 中
public class ArticleController {
public String sayHi(){
return "Hello word";
}
}
还是使用上篇讲的方法 去读取Bean对象:
typescript
import com.demo.component.ArticleController;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("articleController");
//3、使用Bean(可选)
System.out.println(articleController.sayHi());//输出Hello word
}
}
Ⅳ、@Component(组件存储)🍓
ArticleController类:
kotlin
package com.demo.component;
import org.springframework.stereotype.Component;
@Component// 将对象存储到 Spring 中
public class ArticleController {
public String sayHi(){
return "Hello word";
}
}
还是使用上篇讲的方法 去读取Bean对象:
typescript
import com.demo.component.ArticleController;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("articleController");
//3、使用Bean(可选)
System.out.println(articleController.sayHi());//输出Hello word
}
}
Ⅴ、@Configuration(配置存储)🍓
kotlin
package com.demo.component;
import org.springframework.context.annotation.Configuration;
@Configuration// 将对象存储到 Spring 中
public class ArticleController {
public String sayHi(){
return "Hello word";
}
}
还是使用上篇讲的方法 去读取Bean对象:
typescript
import com.demo.component.ArticleController;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("articleController");
//3、使用Bean(可选)
System.out.println(articleController.sayHi());//输出Hello word
}
}
大家一路看下来,可能会吐槽一下:为什么全都是一样的代码啊?这有什么区别啊😂!
为什么有这么多类注解?🍓
Spring框架有很多类注解是为了让开发者以更简洁、方便的方式来定义各种不同类型的Bean(如控制器、服务、存储库等),并且能够更容易地使用Spring的各种功能(如事务管理、缓存、安全性等)。虽然Spring框架中的注解很多,但 大多数都有特定的功能和用途,使得开发者可以根据需求选择合适的注解来使用,也可以让程序员看到类注解之后,就能直接了解当前类的用途,比如:
- @Controller(控制器):业务逻辑层,用来控制用户的行为,它用来检查用户参数的有效性。
- @Servie(服务): 服务层,调用持久化类实现相应的功能。[不直接和数据库交互的,它类似于控制中心]
- @Repository (仓库):持久层,是直接和数据库进行交互的。通常每一个表都会对应一个 @Repository。
- @Configuration(配置):配置层,是用来配置当前项目的一些信息。
- @Component (组件) : 公共工具类,提供某些公共方法。
程序的工程分层,调用流程如下:
五大类注解的联系🍓
直接看@Controller 、@Service 、@Repository 、@Configuration 等注解的源码:
@Service
@Repository
我们可以发现这些注解里面都有⼀个注解 @Component,说明它们是属于 @Component 的,是@Component的"子类"(其他源码也都类似,大家可以自行去查看查看他们的源码,理解更深刻哦!)。
3、Bean 的命名规则🍉
连续按两下 Shift 进行搜索或者通过下图方式去打开搜索框
在 Classes中搜索 BeanName ,打开我红色框选择的类,
划到最下面:
我们就找到了Bean对象的命名方法,它使用的是 JDK Introspector 中的 decapitalize 方法,源码如下:
scss
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);
}
看源码,可以发现获取Bean时 ,Bean的命名只有两种:
- 首字母和第二个字母都非大写,首字母小写来获取 Bean,
- 首字母和第二个字母都是大写,那么直接使用原 Bean 名来获取
类名为:ArticleController
正确命名方法:
错误命名方法:
类名为:AController
正确命名方法:
错误命名方法:
4、方法注解Bean🍉
类注解是添加到某个类上的,而方法注解是放到某个方法上的。
Ⅰ、方法注解要配合类注解使用🍓
Bean注解需要配合五大类注解使用。
ArticleController
arduino
package com.demo.component;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component// 将对象存储到 Spring 中
public class ArticleController {
private int id;
private String name;
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;
}
@Override
public String toString() {
return "ArticleController{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
@Bean//方法注解
public ArticleController acSet(){
ArticleController articleController=new ArticleController();
articleController.setId(1);
articleController.setName("java");
return articleController;
}
}
使用ArticleController中的acSet方法
typescript
import com.demo.component.ArticleController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("acSet");//命名规则和获取Bean一样
//3、使用Bean(可选)
System.out.println(articleController);
}
}
当我们把acSet方法的@Component注解删除时,就会报错:
因此,在使用Bean注解时需要配合使用五大类注解,才能将对象正常的存储到 Spring 容器中
Ⅱ、重命名 Bean🍓
可以通过设置 name 属性给 Bean 对象进行重命名操作。
将acSet方法重命名为ac,并运行代码:
我们可以注意到重命名的name名是使用大括号进行存储,其实这就是一个数组,一个 bean 可以有多个名字。
aS:
typescript
import com.demo.component.ArticleController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
//1、获取spring对象
ApplicationContext context=new ClassPathXmlApplicationContext("spring-config.xml");
//2、从Spring中取出Bean对象
ArticleController articleController=(ArticleController) context.getBean("aS");
//3、使用Bean(可选)
System.out.println(articleController);//输出:ArticleController{id=1, name='java'}
}
}
但是需要注意的是,如果进行了 重命名 原类名就无法再进行获取方法了!
二、获取 Bean 对象(对象装配)[取]🍭
获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注入。
1、依赖注入的常见方式 🍉
对象装配(对象注入)的实现方法以下 3 种:
- 属性注入
- 构造方法注入
- Setter 注入
刚刚好这里有一篇有关Spring依赖注入的文章,写得很好,我就不重复造轮子了。
面试突击77:Spring 依赖注入有几种?各有什么优缺点? - 掘金 (juejin.cn)
Ⅰ、三种注入优缺点分析🍓
- 属性注入的优点是简洁,使同方便;缺点是只能用于 IoC 容器,如果是非 IoC 容器不可用,并且只有在使用的时候才会出现 NPE(空指针异常)。
- 构造⽅法注入是 Spring 推荐的注入方式,它的缺点是如果有多个注⼊会显得比较臃肿,但出现这种情况你应该考虑一下当前类是否符合程序的单一职责的设计模式了,它的优点是通用性,在使用之前⼀定能把保证注⼊的类不为空。
- Setter 方式是 Spring 前期版本推荐的注入方式,但通用性不如构造方法,所有 Spring 现版本已经推荐使用构造方法注入的方式来进行类注入了。
2、@Resource:另一种注入关键字🍉
在进行类注入时,除了可以使用 @Autowired 关键字之外,我们还可以使用 @Resource 进行注入
@Autowired 和 @Resource 的区别:
- 出身不同:@Autowired 来自于 Spring,而@Resource 来自于 JDK 的注解;
- 使用时设置的参数不同:相比于 @Autowired 来说,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
- @Autowired 可用于 Setter 注入、构造函数注入和属性注入,而@Resource 只能用于 Setter 注入和属性注入,不能用于构造函数注入。
可以看到 @Resource是JDK自带的方法:
在构造函数注入时, @Resource 会报错:
其实在官方文档中并没有明确指出为什么构造方法不可以使用@Resource , 可能是官方类加载顺序的问题或者循环引用的问题。(可以评论区讨论,给出你的看法)
3、同一类型多个 @Bean 报错🍉
User
typescript
package com.demo.component;
public class User {
private int id;
private String name;
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;
}
@Override
public String toString() {
return "ArticleController{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
}
Users
java
package com.demo.component;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@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;
}
}
UserController
kotlin
package com.demo.Controller;
import com.demo.component.User;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
@Controller
public class UserController {
@Resource
private User user;
public User getUser(){
return user;
}
}
运行APP
就可以看到 没有唯一Bean定义 异常
同一类型多个 Bean 报错处理 🍓
解决同一个类型,多个 bean 的解决方案有以下两个:
- 使用@Resource(name="user1") 定义。
- 使用@Qualifier 注解定义名称。
使用@Resource(name="user1")
使用@Qualifier 注解定义名称