IoC 详解

通过上⾯的案例,我们已经知道了SpringIoC和DI的基本操作,接下来我们来系统的学习SpringIoC和DI 的操作.

前⾯我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对 象。

也就是bean的存储.

1.Bean的存储

在之前的⼊⻔案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解 @Component ⽽Spring框架为了更好的服务web应⽤程序,提供了更丰富的注解.

共有两类注解类型可以实现:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration.

  2. ⽅法注解:@Bean.

接下来我们分别来看

1.1 @Controller(控制器存储)

使⽤@Controller存储bean的代码如下所⽰:

java 复制代码
@Controller // 将对象存储到 Spring 中 
public class UserController {
     public void sayHi(){
         System.out.println("hi,UserController...");
     }
}

如何观察这个对象已经存在Spring容器当中了呢? 接下来我们学习如何从Spring容器中获取对象

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
     public static void main(String[] args) {
         //获取Spring上下⽂对象 
         ApplicationContext context = 
SpringApplication.run(SpringIocDemoApplication.class, args);
         //从Spring上下⽂中获取对象 
         UserController userController = context.getBean(UserController.class);
         //使⽤对象 
         userController.sayHi();
     }
}

ApplicationContext翻译过来就是:Spring上下⽂ 因为对象都交给Spring管理了,所以获取对象要从Spring中获取,那么就得先得到Spring的上下 ⽂

关于上下⽂的概念

上学时,阅读理解经常会这样问:根据上下⽂,说⼀下你对XX的理解 在计算机领域,上下⽂这个概念,咱们最早是在学习线程时了解到过,⽐如我们应⽤进⾏线程切换的时 候,切换前都会把线程的状态信息暂时储存起来,这⾥的上下⽂就包括了当前线程的信息,等下次该 线程⼜得到CPU时间的时候,从上下⽂中拿到线程上次运⾏的信息

这个上下⽂,就是指当前的运⾏环境,也可以看作是⼀个容器,容器⾥存了很多内容,这些内容是当前 运⾏的环境

观察运⾏结果,发现成功从Spring中获取到Controller对象,并执⾏Controller的sayHi⽅法

如果把@Controller删掉,再观察运⾏结果

报错信息显⽰:找不到类型是:com.example.demo.controller.UserController的bean

获取bean对象的其他⽅式

上述代码是根据类型来查找对象,如果Spring容器中,同⼀个类型存在多个bean的话,怎么来获取呢?

ApplicationContext也提供了其他获取bean的⽅式,ApplicationContext获取bean对象的功能,是⽗ 类BeanFactory提供的功能.

java 复制代码
public interface BeanFactory {
 
 //以上省略... 
 
 // 1. 根据bean名称获取bean 
 Object getBean(String var1) throws BeansException;
 // 2. 根据bean名称和类型获取bean 
 <T> T getBean(String var1, Class<T> var2) throws BeansException;
 // 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean 
 Object getBean(String var1, Object... var2) throws BeansException;
 // 4. 根据类型获取bean 
 <T> T getBean(Class<T> var1) throws BeansException;
 // 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的
bean
 <T> T getBean(Class<T> var1, Object... var2) throws BeansException;
 
 //以下省略... 
}

常⽤的是上述1,2,4种,这三种⽅式,获取到的bean是⼀样的

其中1,2种都涉及到根据名称来获取对象.bean的名称是什么呢?

Springbean是Spring框架在运⾏时管理的对象,Spring会给管理的对象起⼀个名字. ⽐如学校管理学⽣,会给每个学⽣分配⼀个学号,根据学号,就可以找到对应的学⽣.

Spring也是如此,给每个对象起⼀个名字,根据Bean的名称(BeanId)就可以获取到对应的对象.

Bean命名约定

我们看下官⽅⽂档的说明:BeanOverview::SpringFramework

程序开发⼈员不需要为bean指定名称(BeanId),如果没有显式的提供名称(BeanId),Spring容器将为该 bean⽣成唯⼀的名称.

命名约定使⽤Java标准约定作为实例字段名.也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式 ⼤⼩写.

⽐如 类名:UserController,Bean的名称为:userController 类名:AccountManager,Bean的名称为: accountManager 类名:AccountService,Bean的名称为:accountService

也有⼀些特殊情况,当有多个字符并且第⼀个和第⼆个字符都是⼤写时,将保留原始的⼤⼩写.这些规则 与java.beans.Introspector.decapitalize(Spring在这⾥使⽤的)定义的规则相同.

⽐如 类名:UController,Bean的名称为:UController 类名:AManager,Bean的名称为: AManager

根据这个命名规则,我们来获取Bean.

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {

 public static void main(String[] args) {
     //获取Spring上下⽂对象 
     ApplicationContext context = 
SpringApplication.run(SpringIocDemoApplication.class, args);
     //从Spring上下⽂中获取对象 
     //根据bean类型, 从Spring上下⽂中获取对象 
    UserController userController1 = context.getBean(UserController.class);
     //根据bean名称, 从Spring上下⽂中获取对象 
     UserController userController2 = (UserController) 
context.getBean("userController");
     //根据bean类型+名称, 从Spring上下⽂中获取对象 
     UserController userController3 = 
context.getBean("userController",UserController.class);
 
     System.out.println(userController1);
     System.out.println(userController2);
     System.out.println(userController3);
 }
}

运⾏结果:

地址⼀样,说明对象是⼀个

获取bean对象,是⽗类BeanFactory提供的功能

ApplicationContextVSBeanFactory(常⻅⾯试题)

• 继承关系和功能⽅⾯来说:Spring容器有两个顶级的接⼝:BeanFactory和 ApplicationContext。其中BeanFactory提供了基础的访问容器的能⼒,⽽ ApplicationContext属于BeanFactory的⼦类,它除了继承了BeanFactory的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.

• 从性能⽅⾯来说:ApplicationContext是⼀次性加载并初始化所有的Bean对象,⽽ BeanFactory是需要那个才去加载那个,因此更加轻量.(空间换时间)

1.2 @Service(服务存储)

使⽤@Service存储bean的代码如下所⽰:

java 复制代码
@Service
public class UserService {
     public void sayHi(String name) {
       System.out.println("Hi," + name);
     }
 }

读取bean的代码:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
 public static void main(String[] args) {
 //获取Spring上下⽂对象 
 ApplicationContext context = 
SpringApplication.run(SpringIocDemoApplication.class, args);
 //从Spring中获取UserService对象 
 UserService userService = context.getBean(UserService.class);
 //使⽤对象 
 userService.sayHi();
 }
}

观察运⾏结果,发现成功从Spring中获取到UserService对象,并执⾏UserService的sayHi⽅法

同样的,把注解@Service删掉,再观察运⾏结果

1.3 @Repository(仓库存储)

使⽤@Repository 存储bean的代码如下所⽰:

java 复制代码
@Repository
public class UserRepository {
 public void sayHi() {
 System.out.println("Hi, UserRepository~");
 }
}

读取bean的代码:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
 public static void main(String[] args) {
 //获取Spring上下⽂对象 
 ApplicationContext context = 
SpringApplication.run(SpringIocDemoApplication.class, args);
 //从Spring上下⽂中获取对象 
 UserRepository userRepository = context.getBean(UserRepository.class);
 //使⽤对象 
 userRepository.sayHi();
 }
}

观察运⾏结果,发现成功从Spring中获取到UserRepository对象,并执⾏UserRepository的sayHi⽅法

同样的,把注解@Repository删掉,再观察运⾏结果

1.4 @Component(组件存储)

使⽤@Component存储bean的代码如下所⽰:

java 复制代码
@Component
public class UserComponent {
 public void sayHi() {
 System.out.println("Hi, UserComponent~");
 }
}

读取bean的代码:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
 public static void main(String[] args) {
 //获取Spring上下⽂对象 
 ApplicationContext context = 
SpringApplication.run(SpringIocDemoApplication.class, args);
 //从Spring上下⽂中获取对象 
 UserComponent userComponent = context.getBean(UserComponent.class);
 //使⽤对象 
 userComponent.sayHi();
 }
}

观察运⾏结果,发现成功从Spring中获取到UserComponent对象,并执⾏UserComponent的sayHi⽅ 法

同样的,把注解@Component删掉,再观察运⾏结果

1.5 @Configuration(配置存储)

使⽤@Configuration存储bean的代码如下所⽰:

java 复制代码
@Configuration
public class UserConfiguration {
 public void sayHi() {
 System.out.println("Hi,UserConfiguration~");
 }
}

读取bean的代码:

java 复制代码
@SpringBootApplicationpublic class SpringIocDemoApplication {
 public static void main(String[] args) {
 //获取Spring上下⽂对象 
 ApplicationContext context = 
SpringApplication.run(SpringIocDemoApplication.class, args);
 //从Spring上下⽂中获取对象 
 UserConfiguration userConfiguration = 
context.getBean(UserConfiguration.class);
 //使⽤对象 
 userConfiguration.sayHi();
 }
}

观察运⾏结果,发现成功从Spring中获取到UserConfiguration对象,并执⾏UserConfiguration的 sayHi⽅法

同样的,把注解@Configuration删掉,再观察运⾏结果

2 为什么要这么多类注解?

这个也是和咱们前⾯讲的应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.

• @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.

• @Servie:业务逻辑层,处理具体的业务逻辑.

• @Repository:数据访问层,也称为持久层.负责数据访问操作

• @Configuration:配置层.处理项⽬中的⼀些配置信息.

• 这和每个省/市都有⾃⼰的⻋牌号是⼀样的.

⻋牌号都是唯⼀的,标识⼀个⻋辆的.但是为什么还需要设置不同的⻋牌开头呢.

⽐如陕西的⻋牌号就是:陕X:XXXXXX,北京的⻋牌号:京X:XXXXXX,甚⾄⼀个省不同的县区也 是不同的,⽐如西安就是,陕A:XXXXX,咸阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样. 这样做的好处除了可以节约号码之外,更重要的作⽤是可以直观的标识⼀辆⻋的归属地.

程序的应⽤分层,调⽤流程如下:

类注解之间的关系

查看@Controller /@Service /@Repository /@Configuration 等注解的源码发 现:

其实这些注解⾥⾯都有⼀个注解@Component ,说明它们本⾝就是属于@Component 的"⼦类".

@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller ,@Service ,

@Repository 等.这些注解被称为 @Component 的衍⽣注解.

@Controller ,@Service 和@Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持 久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更 好的选择

⽐如杯⼦有喝⽔杯,刷⽛杯等,但是我们更倾向于在⽇常喝⽔时使⽤⽔杯,洗漱时使⽤刷⽛杯.

更多资料参考: https://docs.spring.io/spring-framework/reference/core/beans/classpathscanning.html#beans-stereotype-annotations

相关推荐
BENA ceic2 小时前
Java进阶-在Ubuntu上部署SpringBoot应用
java·spring boot·ubuntu
t***5442 小时前
如何在 Dev-C++ 中设置和使用 Clang 编译器
开发语言·c++
asdfg12589632 小时前
以生活例子理解编程中的“多态”
java·生活·多态
wsjsf2 小时前
智能代码审查助手的搭建
java·学习·ai编程
xuhaoyu_cpp_java2 小时前
MyBatis学习(二)
java·经验分享·笔记·学习·mybatis
石榴树下的七彩鱼2 小时前
智能抠图 API 多语言接入实战:从零到上线的 Python / Java / PHP / JS 完整教程(附避坑指南)
java·python·php·智能抠图·api接入·石榴智能·shiliuai
csbysj20202 小时前
Markdown 段落格式
开发语言
无限进步_2 小时前
C++ 继承机制完全解析:从基础原理到菱形继承问题
java·开发语言·数据结构·c++·vscode·后端·算法
SamDeepThinking3 小时前
适合中小型企业的出口入口网关微服务
java·后端·架构