SpringIoC And DI

今天小编来分享下Spring IoC 以及DI

那么首先来请第一个主角“Spring IoC”。

Spring IoC:

Ioc(Inversion of Control),意为控制反转,这是Spring框架的核心思想之一,用于管理应用程序中的对象(或者Bean)的创建和依赖关系。简单来说,它是由Spring容器来负责对象的创建、配置和组装,而不是在代码中手动完成这些工作。

控制反转:

简单来说,在传统编程中,你的代码是主动控制对象的创建和依赖的调用,在IoC中,这样的控制权被“反转”给了框架(Spring),由它来控制对象的创建和依赖注入。

容器:

在我们的一般性认知中,容器就是来装东西的,同样Spring中的容器也是装东西的

它的作用是:

  • 读取配置(XML、注解、Java配置类)
  • 创建并初始化对象(也就是Bean)
  • 自动注入依赖管理Bean的作用域
  • 管理Bean的生命周期

那么这里要值得注意的是,JavaBean和Spring中的Bean是有些区别的

JavaBean特点:

  • 必须是public类
  • 提供一个无参的构造方法
  • 成员变量是private
  • 提供对应的getter/setter方法

Spring框架中的Bean:

特点:

  • 可以是任何类(甚至没有getter/setter)
  • 只要被Spring容器创建管理,就是Spring Bean
  • 可以通过注解或者xml方式配置Bean

Spring容器的生命周期

大致过程如下:

1.启动容器:

  • 读取配置(XML、注解、JavaConfig)
  • 创建ApplicationContext对象

2.实例化Bean

  • 扫描到定义的Bean
  • 创建并注入依赖

3.初始化Bean

  • 调用@PostConstruct或实现InitializingBean的afterPropertiesSet()
  • 调用自定义的Init-method(配置的情况下)

4.销毁Bean(关闭容器时)

  • 调用@PreDestroy或实现DisposableBean的destroy()
  • 调用自定义的destroy-method(配置的情况下)

Bean的常见作用域

作用域名

描述

singleton

容器中只有一个实例,所有请求共享(类似于单例模式)

prototype

每次请求都会创建一个新实例(非共享)

request

每次HTTP请求创建一个实例(仅限Web)

session

每个HTTP Session创建一个实例(仅限Web)

application

整个ServletContext共享一个实例(仅限Web)

websocket

每个WebSocket会话一个实例(仅限Web)

使用IoC的好处:

  • 降低耦合度
  • 更易于单元测试(可以注入Mock对象)
  • 提高代码的可维护性和可扩展性
  • 更方便地进行模块替换和配置

所以总结来说:
SpringIoc就像是一个“对象工厂”,负责替你创建和管理所有对象,并自动把它们装配起来。

举个例子来解释下,传统编程的弊端吧

以造一辆车为例子

java
//轮胎
public class Tire {
    private int size;
    public Tire(int size){
        System.out.println("Tire Init:"+size);
    }
}
//地盘
public class Bottom {
    private Tire tire;
    public Bottom(int size){
        this.tire=new Tire(size);
        System.out.println("Bottom Init");
    }
}
//车架
public class FrameWork {
    private Bottom bottom;
    public FrameWork(int size){
        this.bottom=new Bottom(size);
        System.out.println("Frame Init");
    }
}
//总车
public class Car {
    private FrameWork frameWork;
    public Car(int size){
        this.frameWork=new FrameWork(size);
        System.out.println("Car Init");
    }

   public void run(){
       System.out.println("car Run");
   }
}
//启动
public class Start {
    public static void main(String[] args) {
        Car car=new Car(20);
        car.run();
    }
}

在这部分代码中,我们可以发现,如若把轮胎的属性动了,那么其他类也跟着动,耦合度高

那么如何去解耦呢?

可以这样做:

每个类构造方法参数中,不提供具体数值,而是提供相对应的对象,那么修改的时候,也是对一个被引用类中的内容进行修改,引用类中不需要做出修改

代码如下 :

java
//轮胎
public class Tire {
    private int size;
    private String color;
    public Tire(int size ,String color){
        this.size=size;
        this.color=color;
        System.out.println("Tire Color:"+color);
        System.out.println("Tire Init!");
    }
}
//底盘
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!");
    }
}
//启动
public class Main {
    public static void main(String[] args) {
        Tire tire=new Tire(20,"red");
        Bottom bottom=new Bottom(tire);
        FrameWork frameWork=new FrameWork(bottom);
        Car car=new Car(frameWork);
        car.run();
    }
}

所以在这个例子中,想要修改tire的参数,那么就在tire类中修改即可,其他组成类,比如bottom类就不需要再动了

ok,那么对于Ioc中呢,其实是由对应的注解来进行对象注册到容器中的

那么接下来,小编来介绍下,有哪些注解吧。

注解:

注册对象到容器

1.@Component

含义:通用的组件注解,表示这一个类是一个Spring组件

适合于需要交给Spring管理的类

组件:指的是被Spring容器管理并纳入其控制反转体系中的Java对象

代码:

typescript
@Component
public class UserComponent {
    public void say(){
        System.out.println("UserComponent Init");
    }

}

2.@Controller

含义:表示控制层的类(Web层)

适用于MVC架构中的控制器,用于处理前端请求

代码:

Java
@Controller
public class UserController {
    public void say(){
        System.out.println("UserController Init");
        userService.say();
    }
}

3.@Service

含义:表示业务逻辑层的类

适用于封装业务逻辑的类

代码:

typescript
@Service
public class UserService {
    public void say(){
        System.out.println("service Init");
    }
}

4.@Repository

含义:表示数据访问层的类

适用于操作数据库,DAO类

代码:

typescript
@Repository("myRepository")
public class UserResp {
    public void say(){
        System.out.println("Repository Init");
    }
}

5.Configuration

含义:表示一个配置类

用于Java Config方式配置Bean(替代XML)

代码:

typescript
@Configuration
public class UserConfig {
    public void say(){
        System.out.println("UserConfig Init");
    }
}

那么以上这是常见的五大IoC注解

这些注解都用于将类标记为Spring Bean 即由容器自动识别并管理的类。

那么这些UserConfig、UserService……类作为对象,放进了Spring容器中了,如何取呢?

取对象从容器:

首先找到启动类后

写下这句代码:

Java
@SpringBootApplication
public class SpringBootTestApplication {

    public static void main(String[] args) {
    ApplicationContext context=SpringApplication.run(SpringBootTestApplication.class, args);
    }
}

ApplicationContext是一个Spring的IoC容器接口,负责管理所有Bean的创建,配置和生命周期

通过它对象名,可以得到Spring容器中的对象。

方法一:

Java
UserController controller=context.getBean(UserController.class);
controller.say();

这个getBean有几个重载版本,这是第一个

在这个中,提供的参数是类型,这个类型必须是唯一的

方法二:

Java
UserController  controller=(UserController) context.getBean("userController");
controller.say();

通过Bean的名称获取

这里的Bean名称就是需要取对象的类名

值得注意的是,使用名称获取的时候,是有要求的

比如当类名是UserController  ,那么如若没有给这个类显式的起其他名字,那么Spring会自动使用类名首字母小写作为Bean的名称

当类名式XMLController,首字母前两个都是大写的时候,那么Spring不会对其做任何改变,即使用原来的类名作为参数。

方法三:

Java
UserController controller=context.getBean("userController",UserController.class);
controller.say();

通过类型名和Bean的名称去获取,准确度高

它们的运行结果都如下:

通过这个五大注解,只是呢,提取到了,一个类,如若是一个类中,也包含了其他对象呢?该如何获取?

此时就要用到了另一个注解

@Bean

这个注解注解是专门将Java方法的返回值注册为一个Bean,也是说加入到Spring容器中

当然,它还通常出现在带Configuration注解类中,用于显式地配置某些Bean,尤其适用于你无法用@Component注解的类(比如第三方库中的类)

当然,举例之前,先介绍一个注解

@Data

这个注解是Lombok库提供的一个非常常用的注解,用来简化Java类的编写,特别是冗长样板代码

(如getter、setter、toString、equals和hashcode方法)

这个注解是一个复合注解,它相当于使用了以下这些常用的Lombok注解:

  • @Getter:自动生成类中所有字段的getter方法
  • @Setter:自动生成类中所有字段的setter方法
  • @ToString:生成toString方法
  • @EqualsAndHashCode:自动生成equals和hashcode方法
  • @RequiredArgsConstructor:自动生成一个构造函数,包含所有字段和@NonNull注解字段

同样的,这个@NonNull注解也是LomBok进行提供的,用于标记某个字段、方法参数或方法返回值不能为空。

依赖引入:

XML
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

举个例子使用

提供一个UserInfo类

typescript
@Data
//这个是全参构造方法
@AllArgsConstructor
//这个是无参的构造方法
@NoArgsConstructor
public class UserInfo {
    private String name;
    private Integer age;
}

提供一个UserInfoComponent类

typescript
@Component
public class UserInfoComponent {
    //通过bean注解,注册更多的对象
    @Bean
    public UserInfo getInfo(){
        return new UserInfo("张三",18);
    }
 }   

那么如何去获取到呢这个UserInfo呢?

Java
UserInfo info= (UserInfo) context.getBean("getInfo");
System.out.println(info)

此时为什么是通过

getInfo这个参数呢?

是因为Spring默认使用@Bean下,方法名作为Bean名称

当然,此时要是你写UserInfo.class作为参数也是可行。

那么既然是默认使用方法名称作为Bean名称,那说明是可以修改的

typescript
@Component
public class UserInfoComponent {
    //通过bean注解,注册更多的对象
    @Bean("myInfo")
    public UserInfo getInfo(){
        return new UserInfo("张三",18);
    }
 }   

运行结果都如下:

值得注意的是,Bean中提供参数,可以是多个的

typescript
//指的是,Info、myInfo都指向UserInfo这个对象
@Bean(name={"Info,myInfo"})
public UserInfo getInfo(){
    return new UserInfo("张三",18);
}

此时呢,获取对象的时候,依旧是可以分别通过Info,myInfo名字,获得对象,

此时,如若分别用Info,myInfo去取出对象,然后进行地址打印输出,会发现是一样的

这是因为spring默认使用了单例模式。

那么值得注意的是,如若出现了多个同类型的对象,如何做呢?

typescript
@Component
public class UserInfoComponent {
@Bean
public String name2(){
    return "王五";
}
@Bean
public String name(){
    return "李四";
    }
}

通过以下代码运行后

Java
String name=context.getBean(String.class);
System.out.println(name);

就会出现报错,这是因为,有多个同为String类型对象了,Spring它不知道取哪个了。

解决办法:

使用Primary注解

这个注解是可以使代码全局优先使用这个Bean,在未指定的情况下

typescript
@Component
public class UserInfoComponent {
@Bean
public String name2(){
    return "王五";
}
@Primary
@Bean
public String name(){
    return "李四";
    }
}

当然,此时如若是想特定指定哪个注解的话

那么,还可以使用@Qualifier

这个注解是明确指定了哪个对象注册到容器中

typescript
@Component
public class UserInfoComponent {
@Qualifier("myName")
@Bean
public String name2(){
    return "王五";
}
@Bean
public String name(){
    return "李四";
    }
  }
}

值得注意的是,Qualifier的优先级是大于Primary,且必须搭配Autowried进行使用

还有一个注解,是jdk提供的,也可以进行依赖注入,

@Resource

Java
@Component
public class UserComponent {
@Resource(name = "Info")
private UserInfo userInfo;
public void say(){
    System.out.println("UserComponent Init");
    System.out.println(userInfo);
}
}

注意,导入的包是:import jakarta.annotation.Resource;

这个注解是必须是要加上参数的,且参数中的值是存在spring容器中存在的

这个注解功能可以等同于:Autowired+Qualifier

java
@Component
public class UserComponent {
    @Autowired
    @Qualifier("Info")
    private UserInfo userInfo;
    public void say(){
    System.out.println("UserComponent Init");
    System.out.println(userInfo);
}
}

那么到这里算是分享完了,那么接着分享下什么是依赖注入吧

DI(Dependencey Injection):

就是说你需要的对象(依赖),不再自己new出来,而是让Spring自动”注入“给你。

注入方式呢,有这三种

1.通过属性注入

Java
@Controller
public class UserController {
@Autowired
private UserService service;
  public void say(){
        System.out.println("UserController Init");
        userService.say();
    }
}

这里的属性指的是Java类中成员变量,这里就是指service

2.通过构造方法注入

Java
private UserService service;
public UserController(UserService service) {
    this.service = service;
}

在Spring FrameWork 4.3开始,就支持了当存在一个构造方法去注入注解,就不需要加@Autowired注解,spring

会自动处理。

那么什么时候去使用这个@Autowired注解呢

java
private UserService service;
@Autowired
public UserController(UserService service) {
    this.service = service;
}
//提供一个无参构造方法
public UserController() {
    System.out.println("提供无参构造方法……");
}

出现多个构造方法,且不加注解,那么spring就无法正确提取,所以在需要注入的对象上,加入注解。

3.通过setter方法注入

Java
@Controller
public class UserController {
    private UserService userService;
    //通过setter方法注入
    @Autowired
    public void setUserService(UserService userService) {
    this.userService = userService;
    }
}

同样值得注意的是,如若通过此方法注入,那么必须是要加上@Autowired

三种方法优缺点分析:

一:字段注入

优点:

写法简洁、使用方便

缺点:

  1. 不利于单元测试(即不能通过构造器传入mock对象)
  1. 类的依赖不透明(外部看不到依赖项)
  1. 只适用于IoC容器,如果是非IoC容器不可用,会出现空指针异常
  1. 不能注入一个Final修饰的属性

二:构造器注入(spring framework4.X推荐)

优点:

  1. 可以注入final修饰的属性
  1. 注入的对象不会被修改
  1. 依赖项显式可见(在构造方法中)
  1. 易于测试(可以通过构造方法传入mock)
  1. 通用性好,构造方法式jdk支持的,所以更换任何框架,它都是适用的

缺点:

注入多个对象的时候,代码会比较繁琐

三:setter注入(spring framework3.x推荐)

优点:

在类进行实例化后,可以重新对该对象进行配置或者注入

缺点:

  1. 不能注入一个Final修饰的属性
  1. 注入对象可能会被改变,因为setter方法可能会多次调用,就有着被修改的风险。

扫描路径

还有一个点,值得注意,注解声明的Bean,不一定会生效的。

Bean生效,是要被spring扫描到,注册到容器中。

那么这里涉及到扫描路径

spring的默认扫描路径,是在启动类所在的包及其子包

举例:

当然也是可以通过注解去显式修改扫描路径

typescript
@SpringBootApplication
//扫描的包必须得存在
@ComponentScan(basePackages = {"com.example", "com.abc.bean"})
public class SpringBootTestApplication {
    public static void main(String[] args) {
    SpringApplication.run(SpringBootTestApplication.class, args);
    }
  }

Autowired装配顺序:

还有一个值得说的是,无论是Autowired还是Resource,它们都是先要更具类型匹配先的,然后Resource再根据名称匹配。

ok,那么对于以上内容,小编就先分享到这里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值