Java EE进阶5:Spring IoC&DI

1.IoC & DI 入门

1.1 Spring概念

Introduction to the Spring IoC Container and Beans :: Spring Frameworkhttps://docs.spring.io/spring-framework/reference/core/beans/introduction.html

1.2 IoC介绍

接下来我们通过案例来了解⼀下什么是IoC,需求 :造⼀辆车

(1) 传统程序开发

最终程序的实现代码如下:

java 复制代码
package com.example.springiocdemo.v1;

public class Main {

    public static void main(String[] args) {
        Car car = new Car();
        car.run();
    }


    //汽车
    static public class Car{
        private Framework framework;
        public Car(){
            this.framework = new Framework();
            System.out.println("car init..."); //init 初始化
        }
        public void run(){
            System.out.println("car run...");
        }
    }


    //框架
    static public class Framework{
        private Bottom bottom;
        public Framework(){
            this.bottom = new Bottom();
            System.out.println("Framework init...");
        }
    }


    //底盘
    static public class Bottom{
        private Tire tire;
        public Bottom(){
            this.tire = new Tire();
            System.out.println("Bottom init...");
        }
    }


    //轮胎
    static public class Tire{
        private int size = 17;
        public Tire(){
            System.out.println("tire size:" + size);
        }
    }


}

(2) 问题分析

(3) 解决方案

(4) IoC程序开发

基于以上思路,我们把调⽤汽车的程序示例改造一下,把创建子类的方式,改为注入传递的方式。

具体实现代码如下:

java 复制代码
package com.example.springiocdemo.v2;

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();
        }


        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;
            private String color;
            public Tire(int size, String color) {
                this.size = size;
                System.out.println("轮胎尺⼨:" + size + ",轮胎颜色:" + color);
            }
        }


}

(5) IoC优势

1.3 DI介绍

2.IoC & DI 使用


实现:(1)把BookDao交给Spring管理,由Spring来管理对象

(2)把BookService交给Spring管理,由Spring来管理对象

(3)删除创建BookDao的代码,从Spring中获取对象

(4)删除创建BookService的代码,从Spring中获取对象

(5)重新运行程序 http://127.0.0.1:8080/book_list.html

3.IoC详解-Bean的存储

通过上面的案例,我们已经知道了SpringIoC和DI的基本操作,接下来我们来系统的学习SpringIoC和DI的操作。

前面我们提到IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象。

也就是bean的存储.

在之前的入门案例中,要把某个对象交给IOC容器管理,需要在类上添加⼀个注解:@Component,而 Spring框架为了更好的服务web应用程序,提供了更丰富的注解。【Spring帮我们new了对象 所以我们可以直接调用】

共有两类注解 类型可以实现:

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

2. 方法 注解**:@Bean. (bean的名称 = 方法名)**

3.1 类注解

(1)@Controller(控制器存储)

使用**@Controller存储bean**的代码如下所示:

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

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

java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象 
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
            
            //从Spring上下⽂中获取对象 
            UserController userController = context.getBean(UserController.class);
            
            //使⽤对象 
            userController.sayHi();
        }
    }


获取bean对象的其他方式

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

ApplicationContext也提供了其他获取bean的方式,ApplicationContext获取bean对象的功能,是父类BeanFactory提供的功能。

java 复制代码
// 1. 根据bean名称获取bean 
 Object getBean(String var1) throws BeansException;
 // 2. 根据bean名称和类型获取bean 
 <T> T getBean(String var1, Class<T> var2) throws BeansException;
 // 3. 按bean名称和构造函数参数动态创建bean,只适⽤于具有原型(prototype)作⽤域的bean 
 Object getBean(String var1, Object... var2) throws BeansException;
 // 4. 根据类型获取bean 
 <T> T getBean(Class<T> var1) throws BeansException;
 // 5. 按bean类型和构造函数参数动态创建bean, 只适⽤于具有原型(prototype)作⽤域的
bean
 <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

常用的是上述1,2,4种,这三种方式,获取到的bean是⼀样的,其中1,2种都涉及到根据名称来获取对象,bean的名称是什么呢?

Spring bean是Spring框架在运行时管理的对象,Spring会给管理的对象起⼀个名字。比如学校管理学生,会给每个学生分配一个学号,根据学号,就可以找到对应的学生。Spring也是如此,给每个对象起⼀个名字,根据Bean的名称(BeanId)就可以获取到对应的对象。

Bean命名约定

我们看下官方文档的说明:https://docs.spring.io/spring-framework/reference/core/beans/definition.html#beans-beanname


java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
            
            //根据bean类型, 从Spring上下⽂中获取对象;有局限性,一个类型对应多个bean。
            UserController userController1 = context.getBean(UserController.class);
            
            //根据bean名称, 从Spring上下⽂中获取对象;一个bean可以有多个名称,但是名称不会重复。
            UserController userController2 = (UserController)context.getBean("userController");
            
            //根据bean类型+名称, 从Spring上下⽂中获取对象
            UserController userController3 = context.getBean("userController",UserController.class);

            System.out.println(userController1);
            System.out.println(userController2);
            System.out.println(userController3);
        }
    }

(2)@Service(服务存储)

使用@Service存储bean的代码如下所示:

java 复制代码
    @Service
    public class UserService {
        public void sayHi(String name) {
            System.out.println("Hi," + name);
        }
    }

读取bean的代码:

java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象 
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

            //从Spring中获取UserService对象 
            UserService userService = context.getBean(UserService.class);

            //使⽤对象 
            userService.sayHi();
        }
    }

(3)@Repository(仓库存储)

使用@Repository 存储bean的代码如下所示:

java 复制代码
    @Repository
    public class UserRepository {
        public void sayHi() {
            System.out.println("Hi, UserRepository~");
        }
    }

读取bean的代码:

java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象 
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
            
            //从Spring上下⽂中获取对象 
            UserRepository userRepository = context.getBean(UserRepository.class);
            
            //使⽤对象 
            userRepository.sayHi();
        }
    }

(4)@Component(组件存储)

使用@Component存储bean的代码如下所示:

java 复制代码
    @Component
    public class UserComponent {
        public void sayHi() {
            System.out.println("Hi, UserComponent~");
        }
    }

读取bean的代码:

java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象 
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
            
            //从Spring上下⽂中获取对象 
            UserComponent userComponent = context.getBean(UserComponent.class);
            
            //使⽤对象 
            userComponent.sayHi();
        }
    }

(5)@Configuration(配置存储)

使用@Configuration存储bean的代码如下所示:

java 复制代码
    @Configuration
    public class UserConfiguration {
        public void sayHi() {
            System.out.println("Hi,UserConfiguration~");
        }
    }

读取bean的代码:

java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象 
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
            
            //从Spring上下⽂中获取对象 
            UserConfiguration userConfiguration = context.getBean(UserConfiguration.class);
            
            //使⽤对象 
            userConfiguration.sayHi();
        }
    }

3.2 为什么要这么多类注解?



类注解之间的关系


3.3 方法注解@Bean

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

使用外部包里的类,没办法添加类注解

② 一个类,需要多个对象,比如多个数据源

这种场景,我们就需要使用方法注解@Bean , 我们先来看看方法注解如何使用:

java 复制代码
public class BeanConfig {
    @Bean
    public User user(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}

然而,当我们写完以上代码,尝试获取bean对象中的user时却发现,根本获取不到:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        
        //从Spring上下⽂中获取对象 
        User user = context.getBean(User.class);
        
        //使⽤对象 
        System.out.println(user);
    }
}

以上程序的执行结果如下:

这是为什么呢? 下面来解释一下

(1)方法注解要配合类注解使用

在Spring框架的设计中,方法注解 @Bean 要配合类注解才能将对象正常的存储到Spring容器中, 如下代码所示。

java 复制代码
@Component
public class BeanConfig {
    @Bean
    public User user(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }
}

再次执行以上代码,运行结果如下:

(2)定义多个对象

对于同⼀个类 ,如何定义多个对象 呢? 比如多数据源的场景,类是同一个,但是配置不同,指向不同的数据源 。

我们看下@Bean的使用

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

定义了多个对象 的话,我们根据类型获取对象 ,获取的是哪个对象呢?

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        
        //从Spring上下⽂中获取对象 
        User user = context.getBean(User.class);
        
        //使⽤对象 
        System.out.println(user);
    }
}

运行结果:

报错信息显示:期望只有一个匹配,结果发现了两个,user1,user2

从报错信息中,可以看出来,@Bean注解的bean,bean的名称就是它的方法名

//因为一个类型(即对象0..0)可以对应多个bean(即多个名称,即它的方法名) ,所以根据bean类型, 从Spring上下文中获取对象就有局限性。 【一个类型可以对应多个bean的前提是一个bean可以有多个名称...............】

//因为一个bean可以有多个名称(bean的名称就是它的方法名!!),但是名称不会重复,所有可以根据根据bean名称, 从Spring上下文中获取对象。

(结合前面命名bean时的代码再思考一下)

java 复制代码
    @SpringBootApplication
    public class SpringIocDemoApplication {
        public static void main(String[] args) {
            //获取Spring上下⽂对象
            ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
            
            //根据bean类型, 从Spring上下⽂中获取对象;有局限性,一个类型对应多个bean。
            UserController userController1 = context.getBean(UserController.class);
            
            //根据bean名称, 从Spring上下⽂中获取对象;一个bean可以有多个名称,但是名称不会重复。
            UserController userController2 = (UserController)context.getBean("userController");
            
            //根据bean类型+名称, 从Spring上下⽂中获取对象
            UserController userController3 = context.getBean("userController",UserController.class);

            System.out.println(userController1);
            System.out.println(userController2);
            System.out.println(userController3);
        }
    }

接下来我们根据名称来获取bean对象

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        
        //根据bean名称, 从Spring上下⽂中获取对象 
        User user1 = (User) context.getBean("user1");
        User user2 = (User) context.getBean("user2");
        System.out.println(user1);
        System.out.println(user2);
    }
}

运行结果:

(3)重命名Bean

可以通过设置name属性给Bean对象进行重命名操作,如下代码所示。

java 复制代码
@Bean(name = {"u1","user1"})
public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
}

此时我们使用u1就可以获取到User对象了,如下代码所示。

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        
        //从Spring上下⽂中获取对象 
        User u1 = (User) context.getBean("u1");
        
        //使⽤对象 
        System.out.println(u1);
    }
}

3.4 扫描路径

再运行代码:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
        public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);

        //从Spring上下⽂中获取对象 
        User u1 = (User) context.getBean("u1");

        //使⽤对象 
        System.out.println(u1);
    }
}
java 复制代码
@ComponentScan({"com.example.demo"})
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        
        //从Spring上下⽂中获取对象 
        User u1 = (User) context.getBean("u1");
        
        //使⽤对象 
        System.out.println(u1);
    }
}


4.DI详解

4.1 属性注入

属性注入是使用@Autowired 实现的,将Service类注入到Controller类中。

Service类的实现代码如下:

java 复制代码
import org.springframework.stereotype.Service;
@Service
public class UserService {
    public void sayHi() {
        System.out.println("Hi,UserService");
    }
}

Controller类的实现代码如下:

java 复制代码
@Controller
public class UserController {
    //注⼊⽅法1: 属性注⼊ 
    @Autowired
    private UserService userService;
    public void sayHi(){
        System.out.println("hi,UserController...");
        userService.sayHi();
    }
}

获取Controller中的sayHi方法:

java 复制代码
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context = SpringApplication.run(SpringIocDemoApplication.class, args);
        //从Spring上下⽂中获取对象 
        UserController userController = (UserController) context.getBean("userController");
        //使⽤对象 
        userController.sayHi();
    }
}

最终结果如下:

4.2 构造方法注入

构造方法注入是在类的构造方法中实现注入,如下代码所示:

java 复制代码
@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来明确指定到底使用哪个构造方法。(优先使用无参的)

4.3 Setter注入

Setter注入和属性的Setter方法实现类似,只不过在设置set方法的时候需要加上@Autowired注解,如下代码所示:

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

练习一下:尝试一下set方法如果不加@Autowired注解能注入成功吗? 不能

4.4 三种注入优缺点分析

更多DI相关内容参考:https://docs.spring.io/spring-framework/reference/core/beans/dependencies.html

4.5 @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

(1) 使用**@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;
    }
}

(2) 使用**@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);
    }
}

(3) 使用**@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);
    }
}

常见面试题:

@Autowird与@Resource的区别

@Autowiredspring框架 提供的注解,而**@ResourceJDK**提供的解。

@Autowired默认是按照类型注入(也有名称),而**@Resource是按照名称注入(也有类型)。相比于@Autowired** 来说, @Resource 支持更多的参数设置,例如name设置,根据名称获取Bean。

Autowired装配顺序

5.练习

通过上面的学习,我们把前面的图书管理系统代码进行调整

Service层的注解,改成**@Service**

Dao层的注解,改成**@Repository**

重新运行代码,验证程序访问正常

6.总结

6.1 Spring、Spring Boot 和Spring MVC的关系以及区别



最后⼀句话总结**:Spring MVC (火车站)和 Spring Boot(12306软件)都属于Spring(火车),Spring MVC 是基于Spring的一个 MVC 框架,而Spring Boot是基于Spring的一套快速开发整合包。**


这三者专注的领域不同,解决的问题也不一样,总的来说,Spring就像一个大家族,有众多衍生产品,但他们的基础都是Spring,用一张图来表示他们三个的关系。


6.2 bean的命名

6.3 常见面试题

相关推荐
IT小哥哥呀2 小时前
Spring Cloud Stream:一次编写,随处运行
java·spring cloud·微服务··后端开发
Kuo-Teng2 小时前
LeetCode 141. Linked List Cycle
java·算法·leetcode·链表·职场和发展
洛_尘2 小时前
数据结构--9:反射、枚举以及lambda表达式(了解即可)
java·开发语言·数据结构
青衫码上行3 小时前
【Java Web学习 | 第12篇】JavaScript(6)DOM
java·开发语言·前端·javascript·学习
TDengine (老段)3 小时前
TDengine 字符串函数 POSITION 用户手册
android·java·大数据·数据库·物联网·时序数据库·tdengine
弘毅 失败的 mian3 小时前
C++、Java 还是测试开发?
java·c++·经验分享·笔记·测试开发·技术方向·就业
杜子不疼.3 小时前
【C++】 set/multiset底层原理与逻辑详解
java·开发语言·c++
q***31893 小时前
Spring Boot--@PathVariable、@RequestParam、@RequestBody
java·spring boot·后端
Macbethad3 小时前
如何用WPF做工控设置界面
java·开发语言·wpf