• 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);
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    通过代码我们发现了一些规律, 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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

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

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

    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);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

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

    当我们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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    <beans>
      <bean id="user" class="com.bit.User"></bean>
    </beans>
    
    • 1
    • 2
    • 3

    在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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    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);
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

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

    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");
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    • @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;
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这样就大功告成了…

  • 相关阅读:
    【Unity】TimeLine系列教程——编排剧情!
    chrome插件通过下载实现导出文件
    【YOLOV5】YOLOV5添加OTA
    Linux进程控制
    AUTOSAR从入门到精通100讲(150)-SOA架构及应用
    开源数据库MariaDB帮助金融服务业打造闭源专有数据库解决方案
    C语言预处理的过程
    GIS前端编程-Leaflet插件发布
    vue3引入three.js
    springboot毕设项目宠物商城系统的设计与实现pcz03(java+VUE+Mybatis+Maven+Mysql)
  • 原文地址:https://blog.csdn.net/weixin_59551524/article/details/125610477