🎇个人主页
🎇所属专栏:Spring
🎇欢迎点赞收藏加关注哦!
IoC 简介
IoC 全称 Inversion of Control,即控制反转
控制反转是指控制权反转:获得依赖对象的过程被反转了
传统开发模式中,需要某个对象时,我们要自己通过 new 来创建对象, 现在无需自行创建, 而是把创建对象的任务交给容器, 程序中只需依赖注入 (Dependency Injection, 简称 DI) 就可以了。这个容器称为 IoC 容器
Spring 是一个 IoC 容器, 所以 Spring 有时也被称为 Spring 容器。作为容器, 它具备两个最基础的功能:存和取
Spring 容器管理的对象我们称为 Bean
。我们把 Bean
交给 Spring 管理, 由它负责对象的创建和销毁。我们的程序只需告诉 Spring,哪些需要存, 以及如何从 Spring 中取出对象
Bean 的存储
有两类注解类型可以实现存储
-
类注解:
@Controller
、@Service
、@Repository
、@Component
、@Configuration
-
方法注解:
@Bean
@Controller
(控制器存储)
@Controller // 将对象存储到 Spring 中
public class UserController {
public void Hello(){
System.out.println("Hello UserController");
}
}
要确认对象是否已经存到 Spring 容器中,我们可以试试能否从 Spring 中取出 Bean,那就得先得到 Spring 的上下文,即 ApplicationContext
@SpringBootApplication
public class SpringIoCDemoApplication {
public static void main(String[] args) {
//获取 Spring 上下文对象
ApplicationContext context = SpringApplication.run(SpringIoCDemoApplication.class, args);
//从上下文中获取 Bean
UserController userController = context.getBean(UserController.class);
//使用 Bean
userController.Hello();
}
}
观察运行结果:
如果把 @Controller
去掉的话,运行时会抛异常:
这个异常意思是说没有找到你要的 Bean,说明去掉注解后就没有 UserController
类的实例,说明没存进 Spring
上面代码是根据类型查找对象
ApplicationContext
也提供了其他获取 bean 的方式, ApplicationContext
获取 bean 对象的功能, 是父类 BeanFactory 提供的功能:
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(String name, Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//其他成员方法已省略
}
常用的是第 1、2、4 种,这三种方式获取到的bean是一样的。其中第一、二种根据名称获取对象。Spring 会给每个对象起一个名字, 根据 Bean 的名称(BeanId)就可以获取到对应的对象
命名规则
默认情况下是类名的小驼峰表示。如果类名的前两位均为大写,那么 Bean 的名称就是类名本身
举个例子
类名:UserController Bean 名称为:userController
类名: UController Bean的名称为: UController
@SpringBootApplication
public class SpringIoCDemoApplication {
public static void main(String[] args) {
//获取 Spring 上下文对象
ApplicationContext context = SpringApplication.run(SpringIoCDemoApplication.class, args);
UserController userController1 = context.getBean(UserController.class);
UserController userController2 = (UserController) context.getBean("userController");
UserController userController3 = context.getBean(UserController.class,"userController");
System.out.println(userController1);
System.out.println(userController2);
System.out.println(userController3);
}
}
运行后发现地址都是一样的,说明是同一个对象
@Service
(服务存储)
直接把上述代码的 @Controller
改为 @Service
@Service
public class UserController {
public void Hello() {
System.out.println("Hello UserController");
}
}
观察运行结果,发现成功从 Spring 中获取到 UserService
对象, 并执行 UserService
的 Hello 方法
同理,换成 @Repository
(仓库存储)、@Component
(组件存储)、@Configuration
(配置存储)还是可以得到一样的结果。这样看来的话,这些注解的作用是差不多的,那还为什么要弄这么多类注解?
类注解的分类
这个也是和我们前面讲的应用分层相呼应。目的是让程序员看到类注解之后,就能直接了解当前类的用途,类注解的用途如下:
-
@Controller:控制层, 接收请求, 处理请求并响应
-
@Servie:业务逻辑层, 处理具体的业务逻辑
-
@Repository:数据访问层,也称为持久层,负责数据访问操作
-
@Configuration:配置层. 处理项目中的一些配置信息
程序应用分层的调用流程如下:
观察 @Controller / @Service / @Repository / @Configuration 等注解的源码可以发现这些注解里面都有 @Component
@Component
是一个元注解,可以注解其他类注解。如 @Controller
、@Service
、@Repository
等. 这些注解称为 @Component
的衍生注解
@Controller , @Service 和 @Repository 用于更具体的用例,这三个分别在控制层, 业务逻辑层, 持久化层
方法注解 @Bean
方法注解 @Bean 要配合类注解才能将对象存储到 Spring 容器中
@Componen
public class BeanConfig {
@Bean
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio
//从Spring上下⽂中获取对象
User user = context.getBean(User.class);
//使⽤对象
System.out.println(user);
}
}
运行结果如下:
@Bean 注解的 Bean
的名称就是它的方法名,如果同个类有多个对象,我们通过 Bean
的名称来获取它们
@Component
public class BeanConfig {
@Bean
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
@Bean
public User user2(){
User user = new User();
user.setName("lisi");
user.setAge(19);
return user;
}
}
@SpringBootApplication
public class SpringIocDemoApplication {
public static void main(String[] args) {
//获取Spring上下⽂对象
ApplicationContext context = SpringApplication.run(SpringIocDemoApplicatio
//根据bean名称, 从Spring上下⽂中获取对象
User user1 = (User) context.getBean("user1");
User user2 = (User) context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
运行结果:
扫描路径
Bean 要想生效,还需要被 Spring 扫描
通过修改目录结构来测试 Bean 对象是否生效:
@SpringBootApplication
public class ApplicationControllerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ApplicationControllerApplication.class, args);
Student s1 = (Student) context.getBean("student1");
Student s2 = (Student) context.getBean("student2");
System.out.println(s1);
System.out.println(s2);
}
}
运行时抛出异常:没有找到 Bean 对象
使用五大注解声明的 bean,要想生效, 还需要配置扫描路径, 让 Spring 扫描到这些注解,通过 @ComponentScan
进行配置
@SpringBootApplication
@ComponentScan({"com.example.demo"})
public class ApplicationControllerApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(ApplicationControllerApplication.class, args);
Student s1 = (Student) context.getBean("student1");
Student s2 = (Student) context.getBean("student2");
System.out.println(s1);
System.out.println(s2);
}
}
配置之后再次运行,就可以正常跑起来了:
加了 @ComponentScan
注解的类,当它需要用到某个依赖时,它就会去 @ComponentScan
所指的路径中找
一开始没配置扫描路径也可以运行是因为:虽然没有显式配置 @ComponentScan
,但它实际上已经包含在了启动类声明注解 @SpringBootApplication
中了,默认扫描的范围是 SpringBoot 启动类所在包及其子包
建议把启动类放在我们希望扫描的包的路径下, 这样我们定义的 bean 就都可以被扫描到