💐个人主页:初晴~
📚相关专栏:程序猿的春天
一、IOC(Inversion of Control)
1、概念
IOC(Inversion of Control,控制反转)是一种设计原则,它将对象的控制权从程序代码中转移到外部容器中。比如在类上⾯添加 @RestController 和 @Controller 注解,就是把这个对象交给Spring管理,Spring 框架启动时就会加载该类。把对象交给Spring管理,就是IoC思想。
2、具体分析
比如我们有一个需求是:造一辆车
(1)传统开发过程
先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底盘依赖轮⼦
代码如下:
public class CarExample {
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);
}
}
}
这样设计确实可以实现我们的需求。但是可维护性非常低。当我们需要更改某一个需求时,就很有可能会“牵一发而动全身”。
比如当我们需要定制各种尺寸的轮胎时,轮胎的尺寸就需要修改为变量:
但是由于这种开发方式的耦合调用关系,只改轮胎的代码肯定会导致其它依赖的程序出现报错,就需要继续修改:
由以上例子我们不难看出,传统的开发方式各个类的耦合程度过高,各个类直接相互依赖,当某个类需要发生改变时,整个调⽤链上的所有代码都需要修改
(2)IOC开发方式
我们可以先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计底盘,最后根据底盘来设计轮⼦。这时候,依赖关系就倒置过来了:轮⼦依赖底盘, 底盘依赖⻋⾝,⻋⾝依赖汽⻋:
如图,改进之后的控制权发生了翻转,不再是使用方创建并控制对象,而是把依赖对象注入到当前的对象,依赖对象的控制权不再由当前类控制,而是交由一个统一的类进行管理:
public class Main {
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();
}
}
public class Tire {
// 尺⼨
private int size;
public Tire(int size){
this.size = size;
System.out.println("轮胎尺⼨:" + size);
}
}
public class Bottom {
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
System.out.println("Bottom init...");
}
}
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
System.out.println("Framework init...");
}
}
public 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...");
}
}
3、优势
1、降低耦合度:
- 通过将对象的创建和依赖关系的管理交给外部容器,减少了代码之间的直接依赖,使得系统更加模块化。
2、提高代码可维护性:
- 由于对象之间的依赖关系是通过配置文件或注解定义的,修改这些依赖关系时不需要修改代码,只需要调整配置,这使得代码更容易维护。
3、提高代码的可重用性:
- 由于对象的创建和依赖关系管理被外部化,相同的对象可以在不同的上下文中重用,而不需要修改代码。
4、Spring中运用IOC
Spring 就是⼀种IoC容器,帮助我们来做了这些资源管理。一般就是通过注解的形式,将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。
(1)类注解:
- @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应
- @Servie:业务逻辑层,处理具体的业务逻辑
- @Repository:数据访问层,也称为持久层。负责数据访问操作
- @Configuration:配置层。处理项⽬中的⼀些配置信息
其实这几个注解的作用基本都是一致的,之所以要进行区分,主要是为了对类的用途进行分类,让代码结构更加清晰:
观察这几个注解的源码我们会发现:
它们底层都有一个 @Component 注解,这是一个元注解,是真正完成控制反转的注解。理论上,上述所有注解都可以用 @Component 直接代替。不过,为了让开发更加规范,还是建议运用对应的注解比较合适。
(2)方法注解
类注解是添加到某个类上的, 但是存在两个问题:
使⽤外部包⾥的类, 没办法添加类注解
⼀个类, 需要多个对象, ⽐如多个数据源
这种场景, 我们就需要使⽤⽅法注解 @Bean
需要注意的是,⽅法注解 @Bean 也要搭配 类注解进行使用:
@Component
public class BeanConfig {
@Bean()
public User user(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
运行结果:
在获取bean时也可以有两种方式:
- 根据类型获取bean
User user = context.getBean(User.class);
- 根据名称获取bean
User user1 = (User) context.getBean("user1");
还可以通过设置name属性来给 Bean 对象进行重命名操作:
@Component
public class BeanConfig {
//@Bean(name={"u1","user1"}) //完整写法
//@Bean({"u1","user1"}) //可省略“name=”
@Bean("u1") //当重命名参数只有一个时,还可省略{}
public User user1(){
User user = new User();
user.setName("zhangsan");
user.setAge(18);
return user;
}
}
二、DI(Dependency Injection)
1、概念
DI,即依赖注入(Dependency Injection),是一种实现控制反转(IOC)原则的技术手段。
容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。
可以这样理解:IOC是一种思想,而DI则是具体实现
2、实现方式
(1)属性注入
属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中
@Controller
public class UserController {
//注⼊⽅法1: 属性注⼊
@Autowired
private UserService userService;
public void sayHi(){
System.out.println("hi,UserController...");
userService.sayHi();
}
}
(2)构造⽅法注⼊
构造⽅法注⼊是在类的构造⽅法中实现注⼊,如下代码所⽰:
@Controller
public class UserController2 {
//注⼊⽅法2: 构造⽅法
private UserService userService;
@Autowired
public UserController2(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController2...");
userService.sayHi();
}
}
注意:如果类只有⼀个构造⽅法,那么 @Autowired 注解可以省略;如果类中有多个构造⽅法,那么需要添加上 @Autowired 来明确指定到底使⽤哪个构造⽅法。
(3)Setter 注⼊
Setter 注⼊和属性的 Setter ⽅法实现类似,只不过在设置 set ⽅法的时候需要加上 @Autowired 注
解 ,如下代码所⽰:
@Controller
public class UserController3 {
//注⼊⽅法3: Setter⽅法注⼊
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public void sayHi(){
System.out.println("hi,UserController3...");
userService.sayHi();
}
}
3、三种注⼊优缺点分析
(1)属性注⼊
优点:
- 简洁,使⽤⽅便;
缺点:
- 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指 针异常)
- 不能注⼊⼀个Final修饰的属性
(2)构造函数注⼊(Spring 4.X推荐)
优点:
- 可以注⼊final修饰的属性
- 注⼊的对象不会被修改
- 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法是在类加载阶段就会执⾏的⽅法.
- 通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的
缺点:
- 注⼊多个对象时, 代码会⽐较繁琐
(3)Setter注⼊(Spring 3.X推荐)
优点:
- ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊
缺点:
- 不能注⼊⼀个Final修饰的属性
- 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险
4、@Autowired存在问题
当同⼀类型存在多个bean时, 使⽤@Autowired会存在问题
@Component
public class BeanConfig {
@Bean("u1")
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;
}
}
@Controller
public class UserController {
//注入user
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
运行结果:
报错的原因是,⾮唯⼀的 Bean 对象。
对此,Spring有三种解决方式:
(1)使⽤@Primary注解
当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现
@Component
public class BeanConfig {
@Primary //指定该bean为默认bean的实现
@Bean("u1")
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;
}
}
(2)使用@Qualifier注解
指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean的名称。
注意:
@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤
@Controller
public class UserController {
//注⼊user
@Qualifier("user2")
@Autowired
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
(3)使⽤@Resource注解
按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称
@Controller
public class UserController {
//注⼊user
@Resource(name = "user2")
private User user;
public void sayHi(){
System.out.println("hi,UserController...");
System.out.println(user);
}
}
5、@Autowird 与 @Resource的区别
- @Autowired是spring框架提供的,@Resource是JDK提供的注解
- @Autowired默认是按类型注入的,而@Resource优先是按照名称注入的,@Resource提供更多的参数设置
@Autowired装配顺序
那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊
版权归原作者 初晴~ 所有, 如有侵权,请联系我们删除。