Spring学习第一天:Spring是什么,ioc,DI?

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Spring学习第一天:Spring是什么,ioc,DI?

什么是Spring

这里的Spring指的是SpringForMark

官网介绍

下面是官网的介绍

image.png

Spring优势

Spring的优势是什么呢,b站老师给我们做出以下解释:

方便解耦,简化开发
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造
成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可
以更专注于上层的应用。
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以
传智播客——专注于 Java、.Net 和 Php、网页平面设计工程师的培训
北京市昌平区建材城西路金燕龙办公楼一层 电话:400-618-9090
通过 AOP 轻松应付。
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,
提高开发效率和质量。
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可
做的事情。
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz
等)的直接支持。
降低 JavaEE API 的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的
使用难度大为降低。
Java 源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以
及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。

我们可以看到第一句是:“方便解耦,简化开发”

那什么是耦呢,耦在这里是指耦合

什么是耦合

耦合是指类与类之间的关联,在java中通常体现为“导入包”等操作

耦合是不可能完全消除的,我们能做的只能是降低耦合

这里为了更好的理解耦合的概念 写一个程序,这是一个使用jdbc访问数据库的程序

public static void main(String[] args) throws SQLException {
        //注册驱动
        DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
        //获取链接
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring_test","root","adminadmin");
        //获取数据库处理对象
        PreparedStatement ps = connection.prepareCall("select * from account");
        //执行sql得到结果集
        ResultSet rs = ps.executeQuery();
        //遍历结果集
        while (rs.next()){
            System.out.println(rs.getString("name")+" "+rs.getString("money"));
        }
        rs.close();
        ps.close();
        connection.close();
    }

可以看到在注册驱动时我们采用了DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());

但如果这时,我们删除了msql的jar包会发生什么呢

image.png

这时我们会发现这是一个编译错误

但是如果我们通常注册驱动时是不会这样写的

我们通常采用的是通过反射来注册驱动

比如Class.forName("com.mysql.cj.jdbc.Driver");

此时运行会发现

image.png

我们的错误从编译错误变成了运行时的错误

小结一下:

image.png

image.png

对于平常创建对象的一些问题

我们对象创建通常是采用new的方式进行的,即

IAccountserver accountserver = new accountserverimpl();

但是我们采用这种方式,必然需要导入accountserverimpl这个类

如果删除类就会导致编译时报错

但是如果我们使用工厂模式的思想就可以解决这个问题

我们试着写一个工厂类

public class BeanFactory {
    private static Properties propertie = new Properties();
    static {
      //读取配置文件
        InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            propertie.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static Object getbean(String beanname) throws Exception {
        Object a= null;
        //获取全限定类名
        String classname=propertie.getProperty(beanname);
        System.out.println(classname);
        //通过反射来创建对象
        a=Class.forName(classname).newInstance();
        return a;
    }
}

这就是读取配置文件再通过反射的方式来创建对象

此时就可以用以下代码来创建对象IAccountserver accountserver=(IAccountserver) BeanFactory.getbean("accountserver");

现在我们观察代码,发现已经成功的解耦了,但是还有个问题

我们修改主函数来观察一下

  public static void main(String[] args) throws Exception {
        for (int i = 0; i < 5; i++) {
            IAccountserver accountserver=(IAccountserver) BeanFactory.getbean("accountserver");
            System.out.println(accountserver);
        }
    }

最后输出的结果是这样的

image.png

这表示这创建的是多例对象

那如果要创建单例对象呢

我们更改工厂类的代码,主要是在获取之前先将对象创建好并存储到容器中,以遍调用

public class BeanFactory {
    //读取配置文件
    private static Properties propertie = new Properties();
    //创建容器用于存储单例对象
    private static Map<String,Object> objectMap;
    static {
        objectMap=new HashMap<String,Object>();
        //读取文件
        InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
        try {
            //加载文件
            propertie.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取所有关键字
        Enumeration keys = propertie.keys();
        //遍历关键字
        while (keys.hasMoreElements()){
            //枚举关键字
            String key = keys.nextElement().toString();
            try {
                //创建对象,并存入容器中
                objectMap.put(key,Class.forName(propertie.getProperty(key)).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 单例工厂模式创建javabean
     * @param beanname
     * @return
     */
    public static Object getbean(String beanname){
        return objectMap.get(beanname);
    }
}

此时运行结果便成了:

image.png

spring基于配置文件的开发

1.导入jar包

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

2.编写配置文件:

<?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">
    <bean id="accountServer" class="com.spring.server.impl.accountserverimpl"></bean>
    <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl"></bean>
</beans>

关于代码开头的约束可以在下面的网站中查找到

https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html#beans

3.编写测试类:

public class Test {
    public static void main(String[] args) throws Exception {
        //获取核心容器对象
        //ApplicationContext的三个实现类:
        //      ClassPathXmlApplicationContext          -----类路径下的配置文件创建
        //      FileSystemApplicationContext            -----磁盘上的配置文件
        //      AnnotationConfigApplicationContext      -----基于注解
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取对象
        IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
        accountserver.saveAccount();
    }
}

注意这里 IAccountserver 是一个自己创建的接口 定义了saveAccount方法

他的实现类 com.spring.server.impl.accountserverimpl实现了saveAccount方法,public void saveUser() { System.out.println("保存成功"); }

所以此时运行结果为

image.png

那么此时创建的对象是单例对象还是多例对象呢

我们更改一下测试程序

public class Test {
    public static void main(String[] args) throws Exception {
        //获取核心容器对象
        //ApplicationContext的三个实现类:
        //      ClassPathXmlApplicationContext          -----类路径下的配置文件创建
        //      FileSystemApplicationContext            -----磁盘上的配置文件
        //      AnnotationConfigApplicationContext      -----基于注解
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取对象
        IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
        accountserver.saveAccount();
        IAccountserver accountserver2=ac.getBean("accountServer",IAccountserver.class);
        accountserver2.saveAccount();
        System.out.println(accountserver==accountserver2);
    }
}

此时的运行结果为:

image.png

由于两个对象地址相同,所以证明此时创建的是单例对象

另外需要注意的是

ApplicationContext 在读取配置文件时,就采用立即加载的方式将配置的对象创建完成了

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

即这句执行完将立即创建配置文件里所有的对象

而我们采用BeanFactory是采用延迟加载的方式实现的

这里我们来验证一下

我们首先重写一下com.spring.server.impl.accountserverimpl的构造函数,让其执行构造函数时输出

public class accountserverimpl implements IAccountserver {
    private IaccountDao dao = new AccountDaoImpl();
    public accountserverimpl() {
        System.out.println("创建了accountserverimpl");
    }
    @Override
    public void saveAccount() {
        dao.saveUser();
    }
}

在试着在读取配置文件时打一个断点,然后在debug模式一步一步运行

image.png

由此可以证明ApplicationContext 是采用立即加载的方式

同理可以证明

image.png

所以BeanFactory采用的时延迟加载的方式

总结一下

ApplicationContext 由于在程序运行一开始就创建好了对象(对象只创建一次)所以适用于单例对象

BeanFactory由于程序运行到创建对象时才进行对象创建(对象可以创建0次或多次),所以适用于多例对象 **注意:**这里不是说BeanFactory创建的对象是多例的,而是延迟加载的方式更适合于多例对象的创建

Spring细节

spring管理的细节

1.创建bean对象的三种方式

2.bean对象的作用范围

3.ben对象的生命周期

一,创建bean的三种方式

<!--    一,创建bean的三种方式-->
    <!--   1.使用默认构造函数创建   注意:没有无参构造函数时将无法创建    -->
    <bean id="accountServer" class="com.spring.server.impl.accountserverimpl"></bean>
    <!--   2.使用工厂中的方法来创建对象 (使用某个类中的方法(非static方法)创建对象,并存入spring容器中)  -->
    <bean id="instanFactory" class="com.spring.factory.InstanceFactory"></bean>
    <bean id="accountService" factory-bean="instanFactory" factory-method="getAccountService"></bean>
    <!--   3.使用工厂中的静态方法创建对象  -->
    <bean id="accountService" class="com.spring.factory.InstanceFactory" factory-method="getAccountService"></bean>

二,bean的作用范围调整

<!--     二,bean的作用范围调整
        bean标签的scope属性:
        作用:用于指定bean的作用位置
        value:
               singleton:单例(默认值)
               prototype:多例
               request: web应用的请求范围
               session: web应用的会话范围
               global—session :集群环境的会话范围
   -->
    <bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton"></bean>

前两个可以简单的修改测试函数来查看区别

public static void main(String[] args) throws Exception {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取对象
        IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
        IAccountserver accountserver2=(IAccountserver)ac.getBean("accountServer");
        System.out.println(accountserver2==accountserver);
    }

先将创建bean的方式设置为单例创建即<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton"></bean>

此时运行执行结果为image.png

我们再试一下多例,即<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype"></bean>

此时运行的结果是:

image.png

globa—session的作用如下所示,在服务器众多的情况下,需要一个多个服务器公用的session即globa—session

image.png

三,bean的生命周期

1.单例对象的生命周期:

我们在对象里添加两个方法
    public void init() {
        System.out.println("初始化了");
    }
    public void close() {
        System.out.println("销毁了");
    }

并更改配置文件,指定初始化方法和销毁方法

<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="singleton" init-method="init" destroy-method="close"></bean>

执行方法运行结果为

image.png

执行主函数:

    public static void main(String[] args) throws Exception {
        //获取核心容器对象
        //ApplicationContext的三个实现类:
        //      ClassPathXmlApplicationContext          -----类路径下的配置文件创建
        //      FileSystemApplicationContext            -----磁盘上的配置文件
        //      AnnotationConfigApplicationContext      -----基于注解
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取对象
        IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
        accountserver.saveAccount();
        ac.close();
    }

分析一下 ,单例对象的生命周期的为

出生:创建容器时bean就被创建了

活着:只要容器在就一直活着

销毁:容器销毁,bean就销毁

总结:单例对象的的生命周期和容器相同

2.多例对象的生命周期

改变配置文件使用多例对象的方式

<bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype" init-method="init" destroy-method="close"></bean>

此时运行结果为:

image.png

发现对象的销毁并没有受容器的影响。

再加上多例对象采用懒加载不难做出以下总结

分析一下 ,单例对象的生命周期的为

出生:使用对象时被创建

活着:只要对象在使用过程中就一直活着

销毁:被jvm垃圾回收

DI依赖注入

编写类:

public class accountserverimpl implements IAccountserver {
    //如果经常改变的数据不适用于DI
    private String name;
    private Integer age;
    private Date birthday;
    public accountserverimpl() {
    }
    public accountserverimpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
    @Override
    public void saveAccount() {
        System.out.println("accountserverimpl{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}');
    }

构造函数注入

编写配置文件

<!--
        在程序中依赖关系的管理将交给spring管理
        依赖关系的维护:称之为依赖注入
        依赖注入:
            能注入的三种类型:
                基本数据类型和String
                其他bean(配置文件或注解配置过的)
                复杂类型,集合类
            注入的方式:三种
                构造函数
                set方法
                注解提供
-->
<!--    使用constructor-arg标签,采用构造函数注入,必须有有参的构造函数
            具体有以下属性:
                1.index 指定要插入的位置    很少单独使用
                2.type 赋值全限定类名来指定数据类型导入 很少单独使用
                3.name 通过形参的名称注入   最常用
                ==========上面三种都是来找插入的位置的,通常单独使用第三种方式进行===================
                4.value 注入基本数据类型和String使用
                5.ref 注入其他bean对象
-->
    <bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype">
        <constructor-arg name="name" value="姓名"/>
        <constructor-arg name="age" value="12"/>
        <constructor-arg name="birthday" ref="day1"/>
    </bean>
    <bean id="day1" class="java.util.Date">
        <constructor-arg name="year" value="128"/>
        <constructor-arg name="month" value="10"/>
        <constructor-arg name="date" value="20"/>
    </bean>

此时运行main函数可以得到结果:

public class Test {
    public static void main(String[] args) throws Exception {
        //获取核心容器对象
        //ApplicationContext的三个实现类:
        //      ClassPathXmlApplicationContext          -----类路径下的配置文件创建
        //      FileSystemApplicationContext            -----磁盘上的配置文件
        //      AnnotationConfigApplicationContext      -----基于注解
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //根据id获取对象
        IAccountserver accountserver=(IAccountserver)ac.getBean("accountServer");
        accountserver.saveAccount();
        ac.close();
    }
}

image.png

这种构造函数方式注入的方式有一个弊端:即在不需要某些参数时也必须传入参数

set方法注入

1.为类创捷get和set方法

2.更改配置文件

<!--    set注入:使用set方法注入
        使用标签property
            属性值
                name 通过形参的名称注入
                value 注入基本数据类型和String使用
                ref 注入其他bean对象
           -->
    <bean id="accountServer" class="com.spring.server.impl.accountserverimpl" scope="prototype">
        <property name="name" value="hhhhh"/>
        <property name="age" value="15"/>
        <property name="birthday" ref="day1"/>
    </bean>
    <bean id="day1" class="java.util.Date">
        <constructor-arg name="year" value="128"/>
        <constructor-arg name="month" value="10"/>
        <constructor-arg name="date" value="20"/>
    </bean>

这种方式较为灵活,但无法保证某些必须有值的成员有值

以上两种方式对比 set方式更加常用

复杂数据类型的注入

更改要创建的类

    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;
    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs;
    }
    public void setMyList(List<String> myList) {
        this.myList = myList;
    }
    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }
    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }
    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }
    public void  saveAccount(){
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }

更改配置文件

    <!-- 复杂类型的注入/集合类型的注入
        用于给List结构集合注入的标签:
            list array set
        用于个Map结构集合注入的标签:
            map  props
        结构相同,标签可以互换
    -->
    <bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
        <property name="myStrs">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>
        <property name="myList">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <property name="mySet">
            <list>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </list>
        </property>
        <property name="myMap">
            <props>
                <prop key="testC">ccc</prop>
                <prop key="testD">ddd</prop>
            </props>
        </property>
        <property name="myProps">
            <map>
                <entry key="testA" value="aaa"></entry>
                <entry key="testB">
                    <value>BBB</value>
                </entry>
            </map>
        </property>
    </bean>

此时运行便可以得到想要的结果

注意

用于给List结构集合注入的标签:

list array set

用于个Map结构集合注入的标签:

map props

结构相同,标签可以互换

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
16天前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
38 2
|
1月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
63 9
|
1月前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
2月前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
41 9
|
29天前
|
Java Kotlin 索引
学习Spring框架特性及jiar包下载
Spring 5作为最新版本,更新了JDK基线至8,修订了核心框架,增强了反射和接口功能,支持响应式编程及Kotlin语言,引入了函数式Web框架,并提升了测试功能。Spring框架可在其官网下载,包括文档、jar包和XML Schema文档,适用于Java SE和Java EE项目。
32 0
|
2月前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
26 1
|
1月前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
39 0
|
2月前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
99 2
|
2月前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
191 1
|
2月前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
79 0
下一篇
DataWorks