Spring核心和设计思想

1.Spring是什么?

我们通常所说的 Spring 指的是 Spring Framework(Spring 框架),它是⼀个开源框架,有着活跃⽽庞 ⼤的社区,这就是它之所以能⻓久不衰的原因。Spring ⽀持⼴泛的应⽤场景,它可以让 Java 企业级的 应⽤程序开发起来更简单。

⽤⼀句话概括 Spring:Spring 是包含了众多⼯具⽅法的 IoC 容器

由此引发的问题是:什么是容器? 什么又是Ioc容器???

什么是容器?

容纳某种物品的装置
比如前面学数据结构中的List / Map (容纳数据)等等
包括 Tomcat (Web容器)

什么是IoC呢?

Ioc实际上是控制反转的意思 —> Inversion of control
如何理解控制反转?

传统程序开发
假设我们去构建一辆车,利用传统的程序思想进行开发…
在这里插入图片描述构建⼀辆⻋(Car Class),然⽽⻋需要依赖⻋身(FrameWork Class),⽽⻋身需要依赖底盘(Bottom Class),⽽底盘需要依赖轮胎(Tire Class),最终程序的实现代码如下


```java
在这里插入代码片
```public class NewCarExample {

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

    /**
     * 汽车对象
     */
    static class Car {
        public void init() {
            // 依赖车身
            Framework framework = new Framework();
            framework.init();
        }
    }

    /**
     * 车身类
     */
    static class Framework {
        public void init() {
            // 依赖底盘
            Bottom bottom = new Bottom();
            bottom.init();
        }
    }

    /**
     * 底盘类
     */
    static class Bottom {

        public void init() {
            // 依赖轮胎
            Tire tire = new Tire();
            tire.init();
        }
    }

    /**
     * 轮胎类
     */
    static class Tire {
        // 尺寸
        private int size = 30;

        public void init() {
            System.out.println("轮胎尺寸:" + size);
        }
    }
}

通过代码我们发现了一些规律, Car类依赖于Framework,也就是new Car的时候,Framework也被创建了,并且调用init方法,接下来调用的init方法又自动去new Bottom,因为通过这些类的含义知道,他们之间是相互依赖的,如果你想去建造一辆车,就需要去保证这种依赖关系

但是这种设计思想也有缺陷,当你满足不同客户的需求的时候,轮胎的尺寸不同,你就要去修改参数,又或者你想加别的参数,可能解决方法是 在每个类中加上参数,通过参数传递就行了呀.实际上这种方式问题很大,因为这几个类的依赖性很强,我们不断的因为客户的需求而修改类的代码是很麻烦而且很容易出Bug的行为.

传统程序开发的缺陷:
以上程序中,轮胎的尺⼨的固定的,然⽽随着对的⻋的需求量越来越⼤,个性化需求也会越来越多,这 时候我们就需要加⼯多种尺⼨的轮胎,那这个时候就要对上⾯的程序进⾏修改了

public class NewCarExample2 {

    public static void main(String[] args) {
        Car car = new Car();
        car.init(50, "猛男粉");
    }

    /**
     * 汽车对象
     */
    static class Car {
        public void init(int size, String color) {
            // 依赖车身
            Framework framework = new Framework();
            framework.init(size, color);
        }
    }

    /**
     * 车身类
     */
    static class Framework {
        public void init(int size, String color) {
            // 依赖底盘
            Bottom bottom = new Bottom();
            bottom.init(size, color);
        }
    }

    /**
     * 底盘类
     */
    static class Bottom {

        public void init(int size, String color) {
            // 依赖轮胎
            Tire tire = new Tire();
            tire.init(size, color);
        }
    }

    /**
     * 轮胎类
     */
    static class Tire {
        // 尺寸
//        private int size = 30;

        public void init(int size, String color) {
            System.out.println("轮胎尺寸:" + size + " | 颜色:" + color);
        }
    }
}

这种就是不断的修改类中的代码,实际上并不能完全解决问题反而会出很大的问题.
从以上代码可以看出,以上程序的问题是:当最底层代码改动之后,整个调⽤链上的所有代码都需要修 改。

我们可以尝试不在每个类中⾃⼰创建下级类,如果⾃⼰创建下级类就会出现当下级类发⽣改变操作,⾃ ⼰也要跟着修改。 此时,我们只需要将原来由⾃⼰创建的下级类,改为传递的⽅式(也就是注⼊的⽅式),因为我们不需 要在当前类中创建下级类了,所以下级类即使发⽣变化(创建或减少参数),当前类本身也⽆需修改任 何代码,这样就完成了程序的解耦。 解决传统开发中的缺陷

PS:解耦指的是解决了代码的耦合性,耦合性也可以换⼀种叫法叫程序相关性。好的程序代码的耦合 性(代码之间的相关性)是很低的,也就是代码之间要实现解耦

实际上IoC就是解耦操作

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

控制反转程序开发

基于上述思路,之前我们是通过在构造方法中创建依赖类的方法,现在换一种思路就是改为 依赖注入的方法

public class IocCarExample {
    public static void main(String[] args) {
        Tire tire = new Tire(50, "红色");
        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;
        }

        public void run() {
            framework.init();
        }
    }

    static class Framework {
        private Bottom bottom;

        public Framework(Bottom bottom) {
            this.bottom = bottom;
        }

        public void init() {
            bottom.init();
        }
    }

    static class Bottom {
        private Tire tire;

        public Bottom(Tire tire) {
            this.tire = tire;
        }

        public void init() {
            tire.init();
        }
    }

    static class Tire {
        private int size;

        private String color;

        public Tire(int size, String color) {
            this.size = size;
            this.color = color;
        }

        public void init() {
            System.out.println("轮胎:" + size + " | 颜色:" + color);
        }
    }
}

仔细观察这种代码跟上面的有什么区别???
这是很重要的一点

当我们new Car的时候,已经创建好了framework并将framework注入到car的构造方法中,并且 car.run中去执行framework的init方法
每个类的init方法实际上都是去执行下一个类的init方法
反正这种代码是很精妙的,需要自己好好看看,博主在这也说不太懂,毕竟刚学…
但是最重要的一点是,你跟着代码的思路走发现他确确实实解决了上树的问题,无论我们怎么去改或者加多少个参数,都是很简单的事情,只需要在new Tire那里和 Tire类本身进行修改即可

这就是IoC思想,接下里的两张图可以进行对比一下

在这里插入图片描述

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

在传统设计里面,我们new 的是Car,但是改的是Tire,由于改了Tire就要改Bottom等等
在IoC中,我们通过注入依赖的方式,改的还是Tire,但是我们已经将Tire注入到Bottom中去了,所以Bottom也随之改变了,之后也是如此,所以他主要是对之前的控制顺序进行了反转,所以IoC叫做控制反转

IoC的设计思想说完了,那么如何去理解Spring是一个Ioc容器呢???

理解Spring IoC

既然 Spring 是⼀个 IoC(控制反转)容器,重点还在“容器”⼆字上,那么它就具备两个最基础的功能: 将对象存⼊到容器; 从容器中取出对象。 也就是说学 Spring 最核⼼的功能,就是学如何将对象存⼊到 Spring 中,再从 Spring 中获取对象的过 程。

将对象存放到容器中的好处:将对象存储在 IoC 容器相当于将以后可能⽤的所有⼯具制作好都放到仓 库中,需要的时候直接取就⾏了,⽤完再把它放回到仓库。⽽ new 对象的⽅式相当于,每次需要⼯具 了,才现做,⽤完就扔掉了也不会保存,下次再⽤的时候还得重新做,这就是 IoC 容器和普通程序开 发的区别。

Spring 是⼀个 IoC 容器,说的是对象的创建和销毁的权利都交给 Spring 来管理了,它本身⼜具备了存 储对象和获取对象的能⼒。

DI概念

说到 IoC 不得不提的⼀个词就是“DI”,DI 是 Dependency Injection 的缩写,翻译成中⽂是“依赖注 ⼊”的意思。 所谓依赖注⼊,就是由 IoC 容器在运⾏期间,动态地将某种依赖关系注⼊到对象之中。所以,依 赖注⼊(DI)和控制反转(IoC)是从不同的⻆度的描述的同⼀件事情,就是指通过引⼊ IoC 容 器,利⽤依赖关系注⼊的⽅式,实现对象之间的解耦。 IoC 是“⽬标”也是⼀种思想,⽽⽬标和思想只是⼀种指导原则,最终还是要有可⾏的落地⽅案,⽽ DI 就 属于具体的实现。

DI就是我们讲的依赖注入的实现,通过将下级类注入进去的具体实现
所以建议大家多去看看这种通过注入方式来实现的代码,这对我们如何去设计代码是很重要的

Spring的创建和使用

Spring作为容器,最重要的两个功能就是:

  1. 将对象存储到容器中
  2. 将对象从容器中取出来
    实际上这里存放的内容就是对象特叫做Bean

创建Spring项目

接下来使⽤ Maven ⽅式来创建⼀个 Spring 项⽬,创建 Spring 项⽬和 Servlet 类似,总共分为以下 3 步:

  1. 创建⼀个普通 Maven 项⽬。
  2. 添加 Spring 框架⽀持(spring-context、spring-beans)。
  3. 添加启动类。
    虽然是Spring项目,但是还是基于maven的,这根Spring Boot不一样的
    在这里插入图片描述
    在这里插入图片描述

添加Spring框架支持

在项⽬的 pom.xml 中添加 Spring 框架的⽀持,xml 配置如下:

<dependencies>
   <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>
  
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>
</dependencies>

从上面的依赖可以看到,添加了两个框架:

  1. spring-context Spring上下文
  2. spring-beans 管理对象模块

这跟Servlet很像的,都是需要去添加依赖来下载jar包
在这里插入图片描述
这里需要去设置依赖源,博主这里设置的国内的阿里源,最重要的是需要勾选住,否则后期下载别的依赖就会很慢(下载国外的源 网络不太行)
在这里插入图片描述

创建启动类和main

在这里插入图片描述
到这里Spring的配置和创建就完成了

存储Bean对象

存储 Bean 分为以下 2 步:

  1. 存储 Bean 之前,先得有 Bean 才⾏,因此先要创建⼀个 Bean。
  2. 将创建的 Bean 注册到 Spring 容器中。

创建Bean对象

在这里插入图片描述

将Bean注册到Spring容器中

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

</beans>

接下来,再将 User 对象注册到 Spring 中就可以,具体操作是在 中添加如下配置:

<beans>
  <bean id="user" class="com.bit.User"></bean>
</beans>

在beans里面每一个bean都代表一个注册类,id就不是类名,而是beanName
class代表的是注册类存放的位置
在这里插入图片描述
写到这里之后,意味着bean注册成功
在这里插入图片描述
然后就可以利用spring-context上下文来将bean对象取出来用
在这里插入图片描述

在这里插入图片描述
这里通过Application来进行创建的,当然还有别的方法
在这里插入图片描述
ApplicationContext 和 BeanFactory 效果是⼀样的,ApplicationContext 属于 BeanFactory 的⼦类, 它们的区别如下。

ApplicationContext VS BeanFactory(常⻅⾯试题):

相同点:都从容器中获取bean,并且都有getBean方法
不同点1、ApplicationContext属于BeanFactory的子类。BeanFactory 只提供了基础访问Bean法,
而ApplicationContext除了拥有BeanFactory的所有功能之外,还提供了更的方法实现,比如对国际化的支持、资源访问的支持、以及事件和传播等方面的支持。
2、从性能方面来说二者是不同,BeanFactory 是按需加载Bean,ApplicationContext是饿汉方式,
在创建时会将所有的Bean都加载起来,以备以后使用。

PS:⽽ ClassPathXmlApplicationContext 属于 ApplicationContext 的⼦类,拥有 ApplicationContext 的所有功能,是通过 xml 的配置来获取所有的 Bean 容器的

在这里插入图片描述
第一种里面的"userinfo"要跟当时的bean id对应,并且返回的是一个object类型还需要强转才能拿到
第二种里面直接通过类型进行获取,但是假设同一个类型注册了多次就会出现问题,他要保证注册次数是唯一
在这里插入图片描述这就是注册两次的情况,所以不建议使用
在这里插入图片描述在这里插入图片描述

第三种就是双重保险在这里插入图片描述

总结

  1. 先去创建maven项目并且去pom.xml中注入依赖spring-context 和spring-beans
  2. 添加配置文件 spring-config.xml
  3. 创建Bean
  4. 在spring-config.xml中注入Bean
  5. 利用spring-context得到Application对象,并且使用getBean()获取对象
  6. 使用对象
    在这里插入图片描述

更简单的Spring读取和存储对象

经过前⾯的学习,我们已经可以实现基本的 Spring 读取和存储对象的操作了,但在操作的过程中我们 发现读取和存储对象并没有想象中的那么“简单”,所以接下来我们要学习更加简单的操作 Bean 对象的 ⽅法。
在 Spring 中想要更简单的存储和读取对象的核⼼是使⽤注解,也就是我们接下来要学习 Spring 中的相 关注解,来存储和读取 Bean 对象

存储Bean对象

之前我们在注册类的时候,每注册一个类都要写一行配置,现在可以通过注解的方式来更方便的完成
在开始存对象之前,需要准备工作

配置扫描路径

注意:想要将对象成功的存储到 Spring 中,我们需要配置⼀下存储对象的扫描包路径,只有被配置的 包下的所有类,添加了注解才能被正确的识别并保存到 Spring 中
在spring-config.xml下添加一下信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
    <content:component-scan base-package="   ">
</content:component-scan>
</beans>

在这里插入图片描述

base-package就是设置的扫描路径,你可以把想要注册的类都放到这个路径下,他就会自动去扫描注册
也就是说,即使添加了注解,如果不是在配置的扫描包下的类对象,也是不能被存储到 Spring 中的

添加注解存储Bean对象

想要将对象存储在 Spring 中,有两种注解类型可以实现:

  1. 类注解:@Controller、@Service、@Repository、@Component、@Configuration。
  2. ⽅法注解:@Bean。

接下来我们分别来看。 使⽤ @Controller 存储 bean 的代码如下所示:

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

更简单的注册结束了,还是利用原来的方式进行上下文的读取

public class Application {
    public static void main(String[] args) {
        // 1.得到 spring 上下⽂
        ApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        // 2.得到 bean
        UserController userController = (UserController)
context.getBean("userController");
        // 3.调⽤ bean ⽅法
        userController.sayHi("Bit");
   }
}

类注解一共是五类,分别是

  • @Controller(控制器)
  • @Service(服务)
  • @Repository(仓库)
  • @Component(组件)
  • Configuration(配置)
    其实这些类注解的功能都是一样的,加了某一个类注解并且在扫描路径下,就可以完成注册类工作
    但是不同的业务逻辑用到的类注解是不一样的,他也是为了提供程序员的开发效率

既然功能是⼀样的,为什么需要这么多的类注解呢?
这和为什么每个省/市都有⾃⼰的⻋牌号是⼀样的?⽐如陕⻄的⻋牌号就是:陕X:XXXXXX,北京的⻋ 牌号:京X:XXXXXX,⼀样。甚⾄⼀个省不同的县区也是不同的,⽐如⻄安就是,陕A:XXXXX,咸 阳:陕B:XXXXXX,宝鸡,陕C:XXXXXX,⼀样。这样做的好处除了可以节约号码之外,更重要的作 ⽤是可以直观的标识⼀辆⻋的归属地。 那么为什么需要怎么多的类注解也是相同的原因,就是让程序员看到类注解之后,就能直接了解当前类 的⽤途,⽐如: @Controller:表示的是业务逻辑层; @Servie:服务层; @Repository:持久层; @Configuration:配置层。
在这里插入图片描述
一个完成的程序必然是有很逻辑化的步骤顺序来执行的,直到Resposity这一层才能和数据库进行交互

这些类注解之间的关系通过源码可以看到
在这里插入图片描述

注意Bean的命名

通过上⾯示例,我们可以看出,通常我们 bean 使⽤的都是标准的⼤驼峰命名,⽽读取的时候⾸字⺟⼩ 写就可以获取到 bean 了,如下图所示:
在这里插入图片描述
实际上小驼峰的类名就是BeanName,因为现在采取更简单的存储Bean方法,所以不需要去一行一行的注册,通过注解的方式来实现第一种取出Bean,但是没有了ID
User user = (User) context.getBean(“user”);

此时getBean里面的参数就是类名,但是这个类名有点古怪

先说结论:
当类似于User的时候,beanName就是 user
当类似于UCompontent的时候,beanName就不变了

我们可以找BeanName命名方式的源码,进行查找
实际上beanName的命名方式是属于jdk而不是spring的jar包的
连续按两次shift出现查找框
在这里插入图片描述
在这里插入图片描述

使用Bean注解来存储对象

在这里插入图片描述

在这里插入图片描述
然⽽,当我们写完以上代码,尝试获取 bean 对象中的 user1 时却发现,根本获取不到
在这里插入图片描述
因为Bean注解是方法注解,要配合着类注解一起使用才能生效

@Component
public class Users {
    @Bean
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("Java");
        return user;
   }
}

这样就大功告成了…

重命名Bean

在上面代码的基础上加上这样一行:
@Bean(name = {“u1”})就表示重命名了

@Component
public class Users {
    @Bean(name = {"u1"})
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("Java");
        return user;
   }
}

在这里插入图片描述

这样做法意味着程序员不一定就要拿引用名去作为getBean的参数,当你觉得这个引用名字不好,你就可以采取这种重命名来设置,一旦你设置了重命名之后,引用名就不行了
此时我们使⽤ u1 就可以获取到 User 对象了,当然了设置name是可以有多个,无论你设置多少个name都可以获取到对象,并且 name={} 可以省略

在这里插入图片描述

获取Bean对象(对象装配)

获取 bean 对象也叫做对象装配,是把对象取出来放到某个类中,有时候也叫对象注⼊
对象装配有三种实现方法:

  • 1.属性注入
  • 构造方法注入
  • Setter注入
    下⾯我们按照实际开发中的模式,将 Service(注解) 类注⼊到 Controller(注解) 类中

属性注入

属性注⼊是使⽤ @Autowired 实现的,将 Service 类注⼊到 Controller 类中
代码如下:

Service类

import org.springframework.stereotype.Service;
@Service
public class UserService {
    /**
     * 根据 ID 获取⽤户数据
     *
     * @param id
     * @return
     */
    public User getUser(Integer id) {
        // 伪代码,不连接数据库
        User user = new User();
        user.setId(id);
        user.setName("Java-" + id);
        return user;
   }
}

Controller类代码:
在这里插入图片描述
因为是在Controlller中注入Service,所以在Controlller中加入了
private UserService userService,@Autowired注解就表示将这个类注入到Controller中, 名字就是userService,通过这个名字去调用注入类的方法和属性

这种注入方式是属性注入, 注入Bean跟前面的注册类是两码事,之前的注册类意思是将Bean存入到Spring仓库中,随取随用.这里的注入类,是将一个类注入到另一个类中,也符合我们之前讲的IoC容器思想和DI

获取 Controller 中的 getUser ⽅法
` 结果如下;
在这里插入图片描述
属性注入的核心就是 在创造注入类的引用前面加上@AutoWired注解
在这里插入图片描述

构造方法注入

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

@Controller
public class UserController2 {
    // 注⼊⽅法2:构造⽅法注⼊
    private UserService userService;
 @Autowired
    public UserController2(UserService userService) {
        this.userService = userService;//已经注入了,赋值即可
   }
    public User getUser(Integer id) {
        return userService.getUser(id);
   }
}

当前类如果只有一个构造方法,就不需要去加注解,有多个构造方法的时候,必须指定某一个方法嘉善注解,才能注入成功
在这里插入图片描述

跟上面的属性注入不同的是,构造方法注入的注解是加在构造方法的上面,并且将引用作为参数传了进来,然后就可以在别的方法中去使用注入类了,这也是官方最推荐的一种。

Setter注入

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

@Controller
public class UserController3 {
    // 注⼊⽅法3:Setter注⼊
    private UserService userService;
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
   }
    public User getUser(Integer id) {
        return userService.getUser(id);
   }
}

当然了,Setter如果不加@AutoWired自然是不能注入成功的…

三种注入的优缺点分析

  • 属性注⼊的优点是简洁,使⽤⽅便;缺点是只能⽤于 IoC 容器,如果是⾮ IoC 容器不可⽤,并且只 有在使⽤的时候才会出现 NPE(空指针异常)。
  • 构造⽅法注⼊是 Spring 推荐的注⼊⽅式,它的缺点是如果有多个注⼊会显得⽐较臃肿,但出现这种 情况你应该考虑⼀下当前类是否符合程序的单⼀职责的设计模式了,它的优点是通⽤性,在使⽤之 前⼀定能把保证注⼊的类不为空。
  • Setter ⽅式是 Spring 前期版本推荐的注⼊⽅式,但通⽤性不如构造⽅法,所有 Spring 现版本已经 推荐使⽤构造⽅法注⼊的⽅式来进⾏类注⼊了。

属性注入收到了容器的限制,只能在Ioc容器中可以注入,但是它很简便
构造方法注入的通用性很强,并且构造方法注入是可以确保使用对象之前,注入对象已经初始化过了,然后开始构造,避免出现空指针,先去注入之后,在执行构造方法,并且构造方法的参数过多时,开发者就要检查自己的代码哦是否符合单一设计原则规范.
Setter注入很单纯,避免了构造方法的注入传多个参数,但是Setter的通用性没构造方法通用性强

虽然官方推荐构造方法注入,但是我们是实践中还是来使用属性注入

@Resource:另⼀种注⼊关键字

在进⾏类注⼊时,除了可以使⽤ @Autowired 关键字之外,我们还可以使⽤ @Resource 进⾏注⼊,如 下代码所示:

@Controller
public class UserController {
    // 注⼊
    @Resource
    private UserService userService;
    public User getUser(Integer id) {
        return userService.getUser(id);
   }
}

在这里插入图片描述

@AutoWired和Resource注解的区别

  • 出身不同:@Autowired 来⾃于 Spring,⽽ @Resource 来⾃于 JDK 的注解;
  • 使⽤时设置的参数不同:相⽐于 @Autowired 来说,@Resource ⽀持更多的参数设置,例如 name 设置,根据名称获取 Bean。
  • @Autowired 支持三种注入方式,但是@Resource是不支持构造方法注入的

同⼀类型多个 @Bean 报错

当出现以下多个 Bean,返回同⼀对象类型时程序会报错

@Component
public class Users {
    @Bean
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("Java");
        return user;
   }
    @Bean
    public User user2() {
        User user = new User();
        user.setId(2);
        user.setName("MySQL");
        return user;
   }
}

在另一类中去注入的时候

@Controller
public class UserController4 {
    // 注⼊
    @Resource
    private User user;
    public User getUser() {
        return user;
   }
}

在这里插入图片描述
这就是出现了同一个类型多个Bean的报错,

在这里插入图片描述

Bean的作用域和生命周期

从前⾯的课程我们可以看出 Spring 是⽤来读取和存储 Bean,因此在 Spring 中 Bean 是最核⼼的操作 资源,所以接下来我们深⼊学习⼀下 Bean 对象

通过⼀个案例来看 Bean 作⽤域的问题

假设现在有⼀个公共的 Bean,提供给 A ⽤户和 B ⽤户使⽤,然⽽在使⽤的途中 A ⽤户却“悄悄”地修改 了公共 Bean 的数据,导致 B ⽤户在使⽤时发⽣了预期之外的逻辑错误。(说好⼀起到⽩头,你却悄悄 焗了油)。

被修改的Bean

公共Bean

@Component
public class Users {
    @Bean
    public User user1() {
        User user = new User();
        user.setId(1);
        user.setName("Java"); // 【重点:名称是 Java】
        return user;
   }
} 

A用户在使用时,进行了修改操作

@Controller
public class BeanScopesController {
    @Autowired
    private User user1;
    public User getUser1() {
        User user = user1;
        System.out.println("Bean 原 Name:" + user.getName());
        user.setName("悟空"); // 【重点:进⾏了修改操作】
        return user;
   }
}

B用户再去使用的时候

@Controller
public class BeanScopesController2 {
    @Autowired
    private User user1;
    public User getUser1() {
        User user = user1;
        return user;
   }
} 

打印A和B用户使用公共Bean的值

public class BeanScopesTest {
    public static void main(String[] args) {
        ApplicationContext context = new
ClassPathXmlApplicationContext("spring-config.xml");
        BeanScopesController beanScopesController =
context.getBean(BeanScopesController.class);
        System.out.println("A 对象修改之后 Name:" +
beanScopesController.getUser1().toString());
        BeanScopesController2 beanScopesController2 =
context.getBean(BeanScopesController2.class);
        System.out.println("B 对象读取到的 Name:" +
beanScopesController2.getUser1().toString());
   }
}

在这里插入图片描述
说明A在修改之后,B再去打印发现公共Bean也被修改了
在这里插入图片描述
原因分析:

操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同 ⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中 Bean 的作⽤域默认也是 singleton 单例模式

作用域定义

限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。

⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表 示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个 ⼈读取到的就是被修改的值。

Bean的六种作用域

Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域, 最后四种是基于 Spring MVC ⽣效的:

  • singleton:单例作⽤域
  • prototype:原型作⽤域(多例作⽤域)
  • request:请求作⽤域
  • session:回话作⽤域
  • application:全局作⽤域
  • websocket:HTTP WebSocket 作⽤域
    注意后 4 种状态是 Spring MVC 中的值,在普通的 Spring 项⽬中只有前两种
singleton
  • 官⽅说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
  • 描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀ 个对象。
  • 场景:通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新 备注:Spring默认选择该作⽤域

无状态的意思是说明Bean的属性不需要更改,在这种情况下默认就是singleton模式

prototype(原型对象)
  • 官⽅说明:Scopes a single bean definition to any number of object instances.
  • 描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的 对象实例。
  • 场景:通常有状态的Bean使⽤该作⽤域

有状态就是说 这个Bean很可能会被修改或者一定被修改,你就需要设置成prototype作用域,这样他每次都是去创建新的Bean

Request
  • 官⽅说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:每次http请求会创建新的Bean实例,类似于prototype
  • 场景:⼀次http的请求和响应的共享Bean
  • 备注:限定SpringMVC中使⽤,他跟在Spring中的prototype是很像的,每次请求都会创建新的对象
session
  • 官⽅说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在⼀个http session中,定义⼀个Bean实例
  • 场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
  • 备注:限定SpringMVC中使⽤
application(了解)
  • 官⽅说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在⼀个http servlet Context中,定义⼀个Bean实例
  • 场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
  • 备注:限定SpringMVC中使⽤
websocket(了解)
  • 官⽅说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
  • 描述:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
  • 场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息 头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
  • 备注:限定Spring WebSocket中使⽤

单例作⽤域(singleton)和全局作⽤域(application)区别

  1. singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域
  2. singleton 作⽤于 IoC 的容器,⽽ application 作⽤于 Servlet 容器。

Bean的原理分析

Bean的执行流程在这里插入图片描述

Bean 执⾏流程(Spring 执⾏流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从⽆到有) -> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)。

Bean的生命周期

所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命 周期。
Bean 的⽣命周期分为以下 5 ⼤部分:

1 实例化Bean(分配内存空间)
2 设置属性(Bean的注入和装配)
3 Bean的初始化

  • 实现了各种 Aware 通知的⽅法,如 BeanNameAware、 BeanFactoryAware、 ApplicationContextAware 的接⼝⽅法;
  • 执⾏ BeanPostProcessor 初始化前置⽅法;
  • 执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏;
  • 执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话);
  • 执⾏ BeanPostProcessor 初始化后置⽅法。

4 使用Bean
5 销毁Bean

在这里插入图片描述

生命周期演示

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Component
public class BeanLifeComponent implements BeanNameAware {
    @PostConstruct
    public void postConstruct() {
        System.out.println("执⾏ PostConstruct()");
   }
    public void init() {
        System.out.println("执⾏ init-method");
   }
    @PreDestroy
    public void preDestroy() {
        System.out.println("执⾏:preDestroy()");
   }
    public void setBeanName(String s) {
        System.out.println("执⾏了 setBeanName ⽅法:" + s);
   }
}

xml配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
    <content:component-scan base-package="com.bit.component">
</content:component-scan>
    <beans>
        <bean id="beanLifeComponent"
class="com.bit.component.BeanLifeComponent" init-method="init"></bean>
    </beans>
</beans>

调用类

import com.bit.controller.BeanLife;
import
org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanLifeTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("spring-config.xml");
        BeanLife life = context.getBean(BeanLife.class);
        System.out.println("执⾏ main ⽅法");
        // 执⾏销毁⽅法
        context.destroy();
   }
}

这个结果跟上面的不太一样,但是没啥太大区别,主要就是第一条和第四条是没写的,因为第一条是Aware没有重写,然后就是使用bean这是正常方法,也没写其余的顺序是一致的在这里插入图片描述总结:本节课介绍了 Bean 的 6 种作⽤域:

  • singleton:单例作⽤域
  • prototype:原型作⽤域(多例作⽤域)
  • request:请求作⽤域
  • session:回话作⽤域
  • application:全局作⽤域
  • websocket:HTTP WebSocket 作⽤域

其中前两种是 spring 核⼼作⽤域,⽽后 4 种是 spring mvc 中的作⽤域,也介绍了 spring 的执⾏ 流程和 bean 的⽣命周期,其中 bean 的作⽤域是最重要的知识点也是常⻅的⾯试题,⽽ bean ⼤ 的执⾏流程也⼀定要牢记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值