Spring IoC & DI

什么是IoC

其实IoC我们在前⾯已经使⽤了, 我在前⾯讲到, 在类上⾯添加 @RestController 和 @Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交 给Spring管理, 就是IoC思想

IoC:Inversion of Control (控制反转),也就是说Spring是一个"控制反转"的容器

控制反转

什么是控制反转呢? 也就是控制权反转. 什么的控制权发⽣了反转? 获得依赖对象的过程被反转了 也就是说, 当需要某个对象时, 传统开发模式中需要⾃⼰通过 new 创建对象, 现在不需要再进⾏创 建, 把创建对象的任务交给容器, 程序中只需要依赖注⼊ (Dependency Injection,DI)就可以了. 这个容器称为:IoC容器. Spring是⼀个IoC容器, 所以有时Spring 也称为Spring 容器

IoC的优点

  1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等), 我们需要使⽤时, 只需要从IoC容器中去取就可以了

  2. 我们在创建实例的时候不需要了解其中的细节, 降低了使⽤资源双⽅的依赖程度, 也就是耦合度

IoC的使用

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

共有两类注解可以实现:

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

2.方法注解:@Bean

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

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

java 复制代码
@SpringBootApplication
public class BeanApplication {

    public static void main(String[] args) {
            //获取Spring上下文对象
        ApplicationContext context = SpringApplication.run(BeanApplication.class, args);
            //从Spring上下文获取对象     //根据bean类型获取bean
        UserControll userControll = context.getBean(UserControll.class);   
            //使用对象
        userControll.sayHi();
    }
}

运行结果

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

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

获取bean对象的其他方式

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

java 复制代码
@SpringBootApplication
public class BeanApplication {

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

        UserControll userControll1 = context.getBean(UserControll.class);       //根据bean类型获取bean
        userControll1.sayHi();

        UserControll userControll2 = (UserControll) context.getBean("userControll"); //根据bean名称获取bean
        userControll2.sayHi();

        UserControll userControll3 = context.getBean("userControll",UserControll.class);//根据bean名称和类型来获取bean
        userControll3.sayHi();
    }
}

注意:类采用大驼峰命名,采用类名获取bean时,要用小驼峰

运行结果

其余的注解@Service、@Repository、@Component、@Configuration,使用的方法不变,将上述@Controller依次换成@Service、@Repository、@Component、@Configuration这些注解,程序也能照常运行

类注解之间的关系

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

其实这些注解里面都有一个注解@Component,说明它们本⾝就是属于 @Component 的"⼦类",

@Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller , @Service , @Repository 等. 这些注解被称为 @Component 的衍⽣注解. @Controller , @Service 和 @Repository ⽤于更具体的⽤例(分别在控制层, 业务逻辑层, 持 久化层), 在开发过程中, 如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更 好的选择

方法注解@Bean

类注解是添加到某个类上的, 但是存在两个问题:

  1. 使⽤外部包⾥的类, 没办法添加类注解

  2. ⼀个类, 需要多个对象, ⽐如多个数据源 这种场景, 我们就需要使⽤⽅法注解 @Bean

举例 我们创建一个类UserInfo,包含id和name,让后在UserControll类中进行初试化,在主函数中进行调用

java 复制代码
@Controller
public class UserControll {


@Bean
    public UserInfo userInfo1(){
        UserInfo userInfo = new UserInfo(1,"zhangsan");
        return userInfo;
    }
    public void sayHi(){
        System.out.println("Hi Controller");

    }




}
java 复制代码
public class BeanApplication {

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

        UserControll userControll1 = context.getBean(UserControll.class);       //根据bean类型获取bean
        userControll1.sayHi();

        UserInfo userInfo = context.getBean(UserInfo.class);
        System.out.println(userInfo);
    }
}

在不加@Bean时

报错显示找不到UserInfo

加上@Bean后

当一个类定义多个对象的时候,如下

java 复制代码
@Controller
public class UserControll {


@Bean
    public UserInfo userInfo1(){
        UserInfo userInfo = new UserInfo(1,"zhangsan");
        return userInfo;
    }
    @Bean
    public UserInfo userInfo2(){
        UserInfo userInfo = new UserInfo(2,"lisi");
        return userInfo;
    }
    public void sayHi(){
        System.out.println("Hi Controller");

    }




}

运行结果如下

报错信息显⽰: 期望只有⼀个匹配, 结果发现了两个, userInfo1, userInfor2

这时候我们可以通过给Bean重命名,让后通过上述说的,通过名称来获取bean,如下

java 复制代码
@Controller
public class UserControll {


@Bean("u1")
    public UserInfo userInfo1(){
        UserInfo userInfo = new UserInfo(1,"zhangsan");
        return userInfo;
    }
    @Bean("u2")
    public UserInfo userInfo2(){
        UserInfo userInfo = new UserInfo(2,"lisi");
        return userInfo;
    }
    public void sayHi(){
        System.out.println("Hi Controller");

    }

}
java 复制代码
@SpringBootApplication
public class BeanApplication {

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

        UserControll userControll1 = context.getBean(UserControll.class);       //根据bean类型获取bean
        userControll1.sayHi();

        UserInfo userInfo1 = (UserInfo) context.getBean("u1");
        UserInfo userInfo2 = (UserInfo) context.getBean("u2");
        System.out.println(userInfo1);
        System.out.println(userInfo2);
    }
}

运行结果

DI介绍

DI: Dependency Injection(依赖注⼊)容器在运⾏期间, 动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。程序运行时需要某个资源,此时容器就为其提供这个资源

DI的使用

依赖注⼊是⼀个过程,是指IoC容器在创建Bean时, 去提供运⾏时所依赖的资源,⽽资源指的就是对象. 在上⾯程序案例中,我们使⽤了 @Autowired 这个注解,完成了依赖注⼊的操作. 简单来说, 就是把对象取出来放到某个类的属性中.

关于依赖注⼊, Spring也给我们提供了三种⽅式:

  1. 属性注⼊(Field Injection)

  2. 构造⽅法注⼊(Constructor Injection)

  3. Setter 注⼊(Setter Injection)

我们在Controller类中实现Configuration类中的方法,代码如下

我们可以看到,在加了@Autowired的注解的时候,类Configuration中的方法时可以被正常调用的,运行结果也时正常的,如果没有加@Autowired,,运行结果如下

构造方法注入和Setter注入方法如下

java 复制代码
@Controller
public class UserControll {




//@Autowired
    private Configuration configuration;
    @Autowired      //构造方法注入
    public UserControll(Configuration configuration){
        this.configuration=configuration;
    }

    public void sayHi(){
        System.out.println("Hi Controller");
        configuration.sayHi();

    }

}

Setter注入

java 复制代码
@Controller
public class UserControll {




//@Autowired
    private Configuration configuration;
    @Autowired      //Setter注入
    public void setConfiguration(Configuration configuration){
        this.configuration=configuration;
    }

    public void sayHi(){
        System.out.println("Hi Controller");
        configuration.sayHi();

    }

}
三种注⼊优缺点分析

属性注⼊ :

优点: 简洁,使⽤⽅便;

缺点: 只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只有在使⽤的时候才会出现 NPE(空指 针异常)不能注⼊⼀个Final修饰的属性

构造函数注⼊(Spring 4.X推荐)

优点: 可以注⼊final修饰的属性 ,注⼊的对象不会被修改 , 依赖对象在使⽤前⼀定会被完全初始化,因为依赖是在类的构造⽅法中执⾏的,⽽构造⽅法 是在类加载阶段就会执⾏的⽅法. ,通⽤性好, 构造⽅法是JDK⽀持的, 所以更换任何框架,他都是适⽤的

缺点:注入多个对象时,代码会比较繁琐

Setter注⼊(Spring 3.X推荐)

优点: ⽅便在类实例之后, 重新对该对象进⾏配置或者注⼊

缺点: 不能注⼊⼀个Final修饰的属性 注⼊对象可能会被改变, 因为setter⽅法可能会被多次调⽤, 就有被修改的⻛险.

@Autowired存在问题

当同意类型存在多个bean时,使用Autowired会存在问题

java 复制代码
@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;
 }
}
java 复制代码
@Controller
public class UserController {
 
 @Autowired
 private UserService userService;
 //注⼊user
 @Autowired
 private User user;
public void sayHi(){
 System.out.println("hi,UserController...");
 userService.sayHi();
 System.out.println(user);
 }
}

运行结果入下

报错的原因是,⾮唯⼀的 Bean 对象。

如何解决上述问题呢?Spring提供了以下⼏种解决⽅案: @Primary @Qualifier @Resource

使⽤@Primary注解:当存在多个相同类型的Bean注⼊时,加上@Primary注解,来确定默认的实现.

java 复制代码
@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;
 }
}

使⽤@Qualifier注解:指定当前要注⼊的bean对象。 在@Qualifier的value属性中,指定注⼊的bean 的名称。

@Qualifier注解不能单独使⽤,必须配合@Autowired使⽤

java 复制代码
@Controller
public class UserController {
 @Qualifier("user2") //指定bean名称
 @Autowired
 private User user;
 public void sayHi(){
 System.out.println("hi,UserController...");
 System.out.println(user);
 }
}

使⽤@Resource注解:是按照bean的名称进⾏注⼊。通过name属性指定要注⼊的bean的名称。

java 复制代码
@Controller
public class UserController {
 @Resource(name = "user2")
 private User user;
 public void sayHi(){
 System.out.println("hi,UserController...");
 System.out.println(user);
 }
}

创作不易,点个赞把

相关推荐
ღ᭄ꦿ࿐Never say never꧂2 分钟前
微服务架构中的负载均衡与服务注册中心(Nacos)
java·spring boot·后端·spring cloud·微服务·架构·负载均衡
所待.3833 分钟前
小小扑克牌算法
java·算法
.生产的驴11 分钟前
SpringBoot 消息队列RabbitMQ 消息确认机制确保消息发送成功和失败 生产者确认
java·javascript·spring boot·后端·rabbitmq·负载均衡·java-rabbitmq
.生产的驴11 分钟前
SpringBoot 消息队列RabbitMQ在代码中声明 交换机 与 队列使用注解创建
java·spring boot·分布式·servlet·kafka·rabbitmq·java-rabbitmq
idealzouhu25 分钟前
Java 并发编程 —— AQS 抽象队列同步器
java·开发语言
听封29 分钟前
Thymeleaf 的创建
java·spring boot·spring·maven
写bug写bug35 分钟前
6 种服务限流的实现方式
java·后端·微服务
离开地球表面_991 小时前
索引失效?查询结果不正确?原来都是隐式转换惹的祸
数据库·后端·mysql
楠枬1 小时前
双指针算法
java·算法·leetcode