【JavaEE进阶】Spring IoC

发布于:2025-02-24 ⋅ 阅读:(13) ⋅ 点赞:(0)

欢迎关注个人主页:逸狼


创造不易,可以点点赞吗

如有错误,欢迎指出~


Spring两大核心思想: IoC 和AOP

Spring,SpringBoot和SpringMVC⼜有什么关系

什么是spring

Spring是⼀个开源框架,他让我们的开发更加简单.他⽀持⼴泛的应⽤场 景,有着活跃⽽庞⼤的社区,具体来说: Spring是包含了众多⼯具⽅法的IoC容器

Ioc 控制反转

IoC: InversionofControl (控制反转),也就是说Spring是⼀个"控制反转"的容器.

控制反转 即 控制权反转: 当需要某个对象时,

  • 传统开发模式中需要⾃⼰通过new创建对象,
  • 运用Ioc思想就不需要再进⾏创 建,把创建对象的任务交给容器,程序中只需要依赖注⼊(DependencyInjection,DI)就可以了.

这个容器称为:IoC容器. Spring是⼀个IoC容器,所以有时Spring也称为Spring容器. 

IoC是Spring的核⼼思想,在类上⾯添加@RestController 和 @Controller 注解,就是把这个对象交给Spring管理,Spring框架启动时就会加载该类.把对象交 给Spring管理,就是IoC思想(之前是对象的使用方创建对象,对象的控制权在对象的使用方手里)

举例: 如果要设计一辆车

传统的代码中对象创建顺序是:Car->Framework->Bottom->Tire

先设计轮⼦(Tire),然后根据轮⼦的⼤⼩设计底盘(Bottom),接着根据底盘设计⻋⾝(Framework),最 后根据⻋⾝设计好整个汽⻋(Car)。这⾥就出现了⼀个"依赖"关系:汽⻋依赖⻋⾝,⻋⾝依赖底盘,底 盘依赖轮⼦.

package com.example.demo.demoTradition;

public class CarTradition {
    public static void main(String[] args) {
        Car car = new Car(88);
        
    }
    static class Car{

        private Framework framework;
        public Car(int size){
            this.framework = new Framework(size);
            System.out.println("car init....");
        }

    }

    static class Framework{
        private Bottom bottom;
        public Framework(int size){
            this.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 tire;
        public Tire(int size){

            System.out.println("tire init...."+size);
        }
    }
}

以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要 修改. 程序的耦合度⾮常⾼(修改⼀处代码,影响其他处的代码修改)  比如要添加新的变量时,如颜色等等,那car到tire的底层代码都要修改 

 

在上⾯的程序中,我们是根据轮⼦的尺⼨设计的底盘,轮⼦的尺⼨⼀改,底盘的设计就得修改.同样因 为我们是根据底盘设计的⻋⾝,那么⻋⾝也得改,同理汽⻋设计也得改,也就是整个设计⼏乎都得改

我们尝试换⼀种思路,我们先设计汽⻋的⼤概样⼦,然后根据汽⻋的样⼦来设计⻋⾝,根据⻋⾝来设计 底盘,最后根据底盘来设计轮⼦.这时候,依赖关系就倒置过来了:轮⼦依赖底盘,底盘依赖⻋⾝, ⻋⾝依赖汽⻋

这就类似我们打造⼀辆完整的汽⻋,如果所有的配件都是⾃⼰造,那么当客⼾需求发⽣改变的时候, ⽐如轮胎的尺⼨不再是原来的尺⼨了,那我们要⾃⼰动⼿来改了,但如果我们是把轮胎外包出去,那 么即使是轮胎的尺⼨发⽣变变了,我们只需要向代理⼯⼚下订单就⾏了,我们⾃⾝是不需要出⼒的.

改用Ioc思想

 改进之后解耦的代码的对象创建顺序是:Tire->Bottom->Framework->Car

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

代码经过以上调整,⽆论底层类如何变化,整个调⽤链是不⽤做任何改变的,这样就完成了代码之间的解耦,从⽽实现了更加灵活、通⽤的程序设计了。  (比如如果要增加新的变量"颜色",只需要修改构造Tire的代码,其他都不用动)

通过对比发现,通⽤程序的实现代码,类的创建顺序是反的,传统代码是Car控制并创建了 Framework,Framework创建并创建了Bottom,依次往下,⽽改进之后的控制权发⽣的反转不再 是使⽤⽅对象创建并控制依赖对象了,⽽是把依赖对象注⼊将当前对象中,依赖对象的控制权不再由 当前类控制了. 这样的话,即使依赖类发⽣任何改变,当前类都是不受影响的,这就是典型的控制反转,也就是IoC的 实现思想。

Spring管理的对象称为bean, Ioc之前,对象的控制权在使用方,Ioc之后,对象的控制权交给了spring

IoC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对 象。 也就是bean的存储

IoC优势

IoC容器具备以下优点: 资源不由使⽤资源的双⽅管理,⽽由不使⽤资源的第三⽅管理,这可以带来很多好处。

第⼀,资源集 中管理,实现资源的可配置和易管理。

第⼆,降低了使⽤资源双⽅的依赖程度,也就是我们说的耦合 度。

  • 1. 资源集中管理: IoC容器会帮我们管理⼀些资源(对象等),我们需要使⽤时,只需要从IoC容器中去取 就可以了
  • 2. 我们在创建实例的时候不需要了解其中的细节,降低了使⽤资源双⽅的依赖程度,也就是耦合度. Spring就是⼀种IoC容器,帮助我们来做了这些资源管理.  

DI依赖注入 

 DI:DependencyInjection(依赖注⼊) 容器在运⾏期间,动态的为应⽤程序提供运⾏时所依赖的资源,称之为依赖注⼊。

上述代码中,是通过构造函数的⽅式,把依赖对象注⼊到需要使⽤的对象中的

IoC是⼀种思想,也是"⽬标",⽽思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽DI就属于 具体的实现。所以也可以说,DI是IoC的⼀种实现. 

实际应用

⽬标:把BookDao,BookService交给Spring管理,完成Controller层,Service层,Dao层的解耦

  • 交给spring管理使用注解:@Component (比如在BookDao类上面加上@Component注解,就表示把BookDao交给Spring管理,Spring会自动new BookDao类的对象 )
  • 注⼊运⾏时依赖的对象:使⽤注解@Autowired(比如如下代码中bookDao就是要注入使用的对象,不用new,而是在注入的对象上面添加@Autowired注解即可)
@Component//表示将BookService的对象交给Spring管理了(Spring帮你创建了该对象放到容器中了)
public class BookService {
    @Autowired //表示从spring中取到对象放到要用的地方
    private BookDao bookDao;

    public List<BookInfo> getList(){
//        BookDao bookDao = new BookDao();

        List<BookInfo> bookInfos = bookDao.mockData();
        for(BookInfo bookInfo : bookInfos){
            if(bookInfo.getStatus() == 1){
                bookInfo.setStatusCN("可借阅");
            }else{
                bookInfo.setStatusCN("不可借阅");
            }
        }
        return bookInfos;
    }
}

@RestController
@RequestMapping("/book")
public class BookController {


    @Autowired//将BookService对象注入
    private BookService bookService;

    @PostMapping("/getList")
    public List<BookInfo> getList(){
//        BookService bookService = new BookService();
        List<BookInfo> bookInfos = bookService.getBookList();
        return bookInfos;
    }
}


@Component //将BookDao对象交给spring管理
public class BookDao {
   .....
}

Bean的存储

将对象的控制权交给Spring的IOC容器就是 bean的存储

Spring框架为了更好的服务web应⽤程序,提供了更丰富的注解.(下面的类注解作用都差不多,只是名称不同,用于不同的模块)共有两类注解类型可以实现:

1. 类注解:@Controller(控制存储)、@Service(服务存储)、@Repository(仓库存储)、@Component(组件存储)、@Configuration(配置存储).

2. ⽅法注解:@Bean.(要配合类注解使用)

类注解

为什么要有多个不同的类注解?

这个也是和咱们前⾯讲的应⽤分层是呼应的.让程序员看到类注解之后,就能直接了解当前类的⽤途.

• @Controller:控制层,接收请求,对请求进⾏处理,并进⾏响应.

• @Servie:业务逻辑层,处理具体的业务逻辑.

• @Repository:数据访问层,也称为持久层.负责数据访问操作

• @Configuration:配置层.处理项⽬中的⼀些配置信息.

程序的应⽤分层,调⽤流程如下:

类注解之间的关系 查看@Controller /@Service /@Repository /@Configuration 等注解的源码发 现: 

其实这些注解⾥⾯都有⼀个注解@Component ,说明它们本⾝就是属于@Component 的"⼦类". @Component 是⼀个元注解,也就是说可以注解其他类注解,如 @Controller ,@Service , @Repository 等.这些注解被称为 @Component 的衍⽣注解.

@Controller ,@Service 和@Repository ⽤于更具体的⽤例(分别在控制层,业务逻辑层,持 久化层),在开发过程中,如果你要在业务逻辑层使⽤ @Component 或@Service,显然@Service是更 好的选择  

使用@Contreller存储bean

将UserController交给spring管理

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

从spring中获取对象

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

ApplicationContext翻译过来就是:Spring上下⽂ 因为对象都交给Spring管理了,所以获取对象要从Spring中获取,那么就得先得到Spring的上下 ⽂(这个上下⽂,就是指当前的运⾏环境,也可以看作是⼀个容器,容器⾥存了很多内容,这些内容是当前 运⾏的环境 )

获取bean对象的其他⽅式

上述代码是根据类型来查找对象,如果Spring容器中,同⼀个类型存在多个bean的话,怎么来获取呢?ApplicationContext也提供了其他获取bean的⽅式,ApplicationContext获取bean对象的功能,是⽗ 类BeanFactory提供的功能.

public interface BeanFactory {
 
 //以上省略... 
 
 // 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种这三种⽅式(根据1.名称; 2.名称和类型; 4.根据类型 ),获取到的bean是⼀样的

bean的名称

程序开发⼈员不需要为bean指定名称(BeanId),如果没有显式的提供名称(BeanId),Spring容器将为该 bean⽣成唯⼀的名称.

  • 命名约定使⽤Java标准约定作为实例字段名.也就是说,bean名称以⼩写字⺟开头,然后使⽤驼峰式 ⼤⼩写.   ⽐如 类名:UserController,Bean的名称为:userController
  • 也有⼀些特殊情况,当有多个字符并且第⼀个和第⼆个字符都是⼤写时,将保留原始的⼤⼩写.这些规则 与java.beans.Introspector.decapitalize(Spring在这⾥使⽤的)定义的规则相同.  ⽐如名 :UController,Bean的名称为:UController
@SpringBootApplication
public class SpringIocDemoApplication {
    public static void main(String[] args) {
        //获取Spring上下⽂对象 
        ApplicationContext context =
                SpringApplication.run(SpringIocDemoApplication.class, args);
        //从Spring上下⽂中获取对象
 
        //1.根据bean类型, 从Spring上下⽂中获取对象  使用于该类只有一个对象
        UserController userController1 = context.getBean(UserController.class);

        //2.根据bean名称, 从Spring上下⽂中获取对象  要进行类型的强转
        UserController userController2 = (UserController)
                context.getBean("userController");

        //3.根据bean类型+名称, 从Spring上下⽂中获取对象 
        UserController userController3 =
                context.getBean("userController",UserController.class);

        System.out.println(userController1);
        System.out.println(userController2);
        System.out.println(userController3);//这三个对象都是同一个
    }
}

地址一样,说明对象是同一个  

获取bean对象,是⽗类BeanFactory提供的功能

ApplicationContext对比BeanFactory(常⻅⾯试题)

  •  继承关系和功能⽅⾯来说:Spring容器有两个顶级的接⼝:BeanFactory和 ApplicationContext。其中BeanFactory提供了基础的访问容器的能⼒,⽽ ApplicationContext属于BeanFactory的⼦类,它除了继承了BeanFactory的所有功能之外, 它还拥有独特的特性,还添加了对国际化⽀持、资源访问⽀持、以及事件传播等⽅⾯的⽀持.
  • 从性能⽅⾯来说:ApplicationContext是⼀次性加载并初始化所有的Bean对象,⽽ BeanFactory是需要哪个才去加载哪个,因此更加轻量.(空间换时间)

⽅法注解@Bean 

@Bean主要用于解决以下两个问题

  1. 使⽤外部包⾥的类,没办法添加类注解
  2. ⼀个类,需要多个对象,⽐如多个数据源 这种场景

@Bean的使用

在Spring框架的设计中,⽅法注解 @Bean 要配合类注解才能将对象正常的存储到Spring容器中

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component//@Bean要配合类注解使用
public class BeanConfig {
    @Bean
    public User user(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        return user;
    }

}
package com.example.demo;

import com.example.demo.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Demo5Application {

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

		User bean = context.getBean(User.class);
		System.out.println(bean);
	}
}

定义多个对象

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

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component//@Bean要配合类注解使用
public class BeanConfig {
    @Bean
    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        user.setGender("男");
        return user;
    }


    @Bean
    public User user2(){
        User user = new User();
        user.setName("lisi");
        user.setAge(81);
        user.setGender("女");
        return user;
    }
}

所以要通过名称来获取bean 

package com.example.demo;

import com.example.demo.Controller.UserController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Demo5Application {

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

		User user1 = (User) context.getBean("user1");
		User user2 = (User) context.getBean("user2");
		System.out.println(user1);
		System.out.println(user2);
	}
}

 

Bean的重命名 

@Component//@Bean要配合类注解使用
public class BeanConfig {

    @Bean(name = {"u1", "user1"})//可以指定多个名称
//    @Bean({"u1","user1"})//name=可以省略
//    @Bean("u1")//只用一个名称时name=和{}都可以省略

    public User user1(){
        User user = new User();
        user.setName("zhangsan");
        user.setAge(18);
        user.setGender("男");
        return user;
    }

扫描路径

Q:使⽤前⾯学习的四个注解声明的bean,⼀定会⽣效吗?

 A:不⼀定(原因:bean想要⽣效,还需要被Spring扫描

举例:修改项⽬⼯程的⽬录结构,来测试bean对象是否⽣效:

运行项目,发现没有找到u1

解释:  使⽤五⼤注解声明的bean,要想⽣效,还需要配置扫描路径,让Spring扫描到这些注解 也就是通过 @ComponentScan 来配置扫描路径. 

推荐做法: 把启动类放在我们希望扫描的包的路径下,这样我们定义的bean就都可以被扫描到