Spring IoCDI 快速入门
1.什么是Spring IoC
Spring IoC(Inversion of Control,控制反转)是 Spring 框架的核心机制之一,指的是将对象的创建、依赖注入和生命周期管理的控制权从应用程序代码转移到框架或容器中。传统编程中,对象之间的依赖关系由开发者直接管理,而 IoC 通过容器自动完成依赖注入(DI),从而降低代码耦合度,提高可维护性。
在类上⾯添加 @RestController 和 @Controller 注解,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类.把对象交 给Spring管理,就是IoC思想。
IoC 的核心思想
对象不再主动创建或查找依赖,而是由容器负责实例化并注入所需的依赖。获得依赖对象的过程被反转了也就是说,当需要某个对象时,传统开发模式中需要⾃⼰通过new创建对象,现在不需要再进⾏创 建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊(DependencyInjection,DI)就可以了. 这个容器称为:IoC容器. Spring是⼀个IoC容器,所以有时Spring也称为Spring容器。
在生活中想汽车的自动驾驶,常规我们需要自己进行左转和右转,如果开启自动驾驶的话,就交给了汽车自己来控制。
IoC 介绍
我们通过一个列子来快速认识loC。汽车开发的过程中我们根据车子的轮胎设计底盘,再根据地盘设计我们的车身,相互依赖。
代码示例:
public class NewCarExample {public static void main(String[] args) {Car car = new Car();car.run();}/*** 汽车对象*/static class Car {private Framework framework;public Car() {framework = new Framework();System.out.println("Car init....");}public void run() {System.out.println("Car run...");}}/*** 车身类*/static class Framework {private Bottom bottom;public Framework() {bottom = new Bottom();System.out.println("Framework init...");}}/*** 底盘类*/static class Bottom {private Tire tire;public Bottom() {this.tire = new Tire();System.out.println("Bottom init...");}}/*** 轮胎类*/static class Tire {// 尺寸private int size;public Tire() {this.size = 17;System.out.println("轮胎尺寸:" + size);}}
}
这样的代码没啥问题,但是维护性却很差,如果轮胎的尺寸发生变化的话。因此我们进行修改由我们去传这个参数。代码如下:
public class NewCarExample {public static void main(String[] args) {Car car = new Car(20);car.run();}/*** 汽车对象*/static class Car {private Framework framework;public Car(int size) {framework = new Framework(size);System.out.println("Car init....");}public void run(){System.out.println("Car run...");}}/*** 车身类*/static class Framework {private Bottom bottom;public Framework(int size) {bottom = new Bottom(size);System.out.println("Framework init...");}}/*** 底盘类*/static class Bottom {private Tire tire;public Bottom(int size) {this.tire = new Tire(size);System.out.println("Bottom init...");}}/*** 轮胎类*/static class Tire {// 尺寸private int size;public Tire(int size){this.size = size;System.out.println("轮胎尺寸:" + size);}}
}
可以看到我们上面的代码输出顺序:
- 轮胎尺寸
- 底盘初始化
- 车身初始化
- 汽车初始化
- 汽车运行
当底层代码进行改动的时候我上层代码也要进行改动,那这样的话耦合性就太高了。我们实际开发过程中不是就讲究高内聚低耦合嘛。那么我们就该用上我们的loC思想了。我们把创建子类的思想改为注⼊传递的⽅式。
代码示例:
public class IocCarExample {public static void main(String[] args) {Tire tire = new Tire(20);Bottom bottom = new Bottom(tire);Framework framework = new Framework(bottom);Car car = new Car(framework);car.run();}static class Car {private Framework framework;public Car(Framework framework) {this.framework = framework;System.out.println("Car init....");}public void run() {System.out.println("Car run...");}}static class Framework {private Bottom bottom;public Framework(Bottom bottom) {this.bottom = bottom;System.out.println("Framework init...");}}static class Bottom {private Tire tire;public Bottom(Tire tire) {this.tire = tire;System.out.println("Bottom init...");}}static class Tire {private int size;public Tire(int size) {this.size = size;System.out.println("轮胎尺⼨:" + size);}}
}
现在的话我们的代码底层修改,整个调用链也不会发生改变了,到达解耦的目的。
什么是DI
DI(Dependency Injection,依赖注入)是一种设计模式,用于实现控制反转(IoC),通过外部容器将依赖对象注入到目标对象中,而非由目标对象主动创建依赖。其核心思想是解耦组件之间的依赖关系,提升代码的可维护性和可测试性。
在上面的代码中是通过构造函数的⽅式,把依赖对象注⼊到需要使⽤的对象中的。
2. IoC 详解
Spring是⼀个IoC(控制反转)容器,作为容器,那么它就具备两个最基础的功能:存和取。
Spring容器管理的主要是对象,这些对象,我们称之为"Bean".我们把这些对象交由Spring管理,Spring来负责对象的创建和销毁,我们程序只需要告诉Spring,哪些需要存,以及如何从Spring中取出对象
Spring容器中获取对象
用于观察对象是否在容器当中
@SpringBootApplication
public class IocApplication {public static void main(String[] args) {//获取spring上下文对象ApplicationContext context = SpringApplication.run(IocApplication.class, args);//获取user对象 方式一获取class对象的方式 这个方式针对与类只有一个对象的情况下//UserController userController = context.getBean(UserController.class);//方式二//UserController userController2 = (UserController) context.getBean("userController");//方式三//UserController userController3 = context.getBean("userController",UserController.class);//方式四//UserController userController4 = (UserController) context.getBean("userController",Object.class);//System.out.println(userController);//userController.hiUser();}}
Bean的存储
共有两类注解可以实现:
- 类注解:@Controller、@Service、@Repository、@Component、@Configuration
- ⽅法注解:@Bean
@Controller(控制器存储)
@Controller
public class UserController {public void hiUser(){System.out.println("do-hi-userController");}
}
运行结果:
如果把@Controller删掉,再观察运⾏结果:
报了No qualifying bean of type 'cn.demo.ioc.controller.UserController' available,就是说没有可用的合格的对象。
注意:
@Service(服务存储),@Repository(仓库存储),@Component(组件存储),@Configuration(配置存储)也和上述@Controller(控制器存储)使用方式相同。
那有人就说那为什么还需要用这么多注解啊用一个注解不就可以了嘛,这就是分层的重要性,就和三层架构一样的意义。每一层都有不同的职责。
- @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.
- @Service:业务逻辑层,处理具体的业务逻辑.
- @Repository:数据访问层,也称为持久层.负责数据访问操作
- @Configuration:配置层.处理项⽬中的⼀些配置信息
⽅法注解@Bean
类注解是添加到某个类上的,但是存在两个问题:
- 使⽤外部包⾥的类, 没办法添加类注解
- ⼀个类,需要多个对象,⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
@Controller
public class UserController {@Beanpublic UserInfo func(){UserInfo userInfo = new UserInfo();userInfo.setName("xiaohei");userInfo.setAge(18);return userInfo;}
}
获取Bean对象
UserInfo userInfo = context.getBean(UserInfo.class);System.out.println(userInfo);
结果如下:
定义多个对象
@Controller
public class UserController {@Beanpublic UserInfo func(){UserInfo userInfo = new UserInfo();userInfo.setName("xiaohei");userInfo.setAge(18);return userInfo;}@Beanpublic UserInfo func1(){UserInfo userInfo = new UserInfo();userInfo.setName("laohei");userInfo.setAge(88);return userInfo;}
}
运行结果:
我们可以看到期待只有一个Bean对象,这里却有俩个
根据名称获取Bean对象:
UserInfo userInfo1 =(UserInfo) context.getBean("func");UserInfo userInfo2 =(UserInfo) context.getBean("func1");System.out.println(userInfo1);System.out.println(userInfo2);
运行结果:
我们可以看待代码此时可以成功运行了。因此我们可以得到通过类调用的话值适应于只有一个对象的场景。
重命名Bean
可以通过设置name属性给Bean对象进⾏重命名操作,如下代码所⽰:
@Bean(name = {"userinfo","func"})
//可以省略为 @Bean({"userinfo","func"}) 再只有一个名称的时候@Bean(“userinfo")public UserInfo func(){UserInfo userInfo = new UserInfo();userInfo.setName("xiaohei");userInfo.setAge(18);return userInfo;}
根据重命名名称获取Bean对象:
UserInfo userInfo1 =(UserInfo) context.getBean("userinfo");System.out.println(userInfo1);
运行结果:
3.DI 详解
依赖注⼊是⼀个过程,是指IoC容器在创建Bean时,去提供运⾏时所依赖的资源,⽽资源指的就是对象。
关于依赖注⼊,Spring也给我们提供了三种⽅式:
- 属性注⼊(FieldInjection)
- 构造⽅法注⼊(ConstructorInjection)
- Setter 注⼊(SetterInjection
属性注⼊
属性注⼊是使⽤ @Autowired 实现的,将Service类注⼊到Controller类中
Service 类的实现代码如下:
@Service
public class UserService {public void hiUser(){System.out.println("do-hi-userService");}
}
Controller 类的实现代码如下:
@Controller
public class UserController {@AutowiredUserService userService;public void hiUser(){System.out.println("do-hi-userController");userService.hiUser();}
}
获取Controller中的⽅法:
//获取Controller中的⽅法UserController userController = context.getBean(UserController.class);userController.hiUser();
运行结果:
去掉@Autowired,再运⾏⼀下程序看看结果
构造⽅法注⼊
@Controller
public class UserController {private UserService userService;@Autowiredpublic UserController(UserService userService) {this.userService = userService;}public void hiUser(){System.out.println("do-hi-userController");userService.hiUser();}
}
Setter 注⼊
@Controller
public class UserController {private UserService userService;@Autowiredpublic void setUserService(UserService userService) {this.userService = userService;}public void hiUser(){System.out.println("do-hi-userController");userService.hiUser();}
}
@Autowired存在问题
@Controller
public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate UserInfo userInfo;public void hiUser(){System.out.println("do-hi-userController");userService.hiUser();System.out.println(userInfo);}
}
运⾏结果:
报错的原因是,⾮唯⼀的Bean对象。
解决方案一:
使⽤@Primary注解:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现
@Component
public class BeanConfig {@Primary@Bean(name = {"userinfo","func"})public UserInfo func(){UserInfo userInfo = new UserInfo();userInfo.setName("xiaohei");userInfo.setAge(18);return userInfo;}@Beanpublic UserInfo func1(){UserInfo userInfo = new UserInfo();userInfo.setName("laohei");userInfo.setAge(88);return userInfo;}}
解决方案二:
使⽤@Qualifier注解:指定当前要注⼊的bean对象。在@Qualifier的value属性中,指定注⼊的bean 的名称。
@Controller
public class UserController {@Autowiredprivate UserService userService;@Qualifier("func1")@Autowiredprivate UserInfo userInfo;public void hiUser(){System.out.println("do-hi-userController");userService.hiUser();System.out.println(userInfo);}
}
解决方案三:
使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。
@Controller
public class UserController {@Autowiredprivate UserService userService;@Resource(name = "func1")private UserInfo userInfo;public void hiUser(){System.out.println("do-hi-userController");userService.hiUser();System.out.println(userInfo);}
}
@Autowird与@Resource的区别
- @Autowired是spring框架提供的注解,⽽@Resource是JDK提供的注解
- @Autowired默认是按照类型注⼊,⽽@Resource是按照名称注⼊.相⽐于@Autowired 来说, @Resource ⽀持更多的参数设置,例如name设置,根据名称获取Bean