再探SpringIOC

再探SpringIOC

Spring 概述

Spring框架是由于软件开发的复杂性而创建的。
Spring是一个轻量级的JavaEE开源框架。

Spring有两个核心部分:

  1. IOC:控制反转(Inversion of Control),把创建的对象交给Spring容器进行管理
  2. AOP:面向切面,不修改源代码进行功能增强

Spring的特点:

  1. 方便解耦简化开发
  2. 程序测试方便(Junit)
  3. 方便继承各种优秀框架
  4. 方便进行事物的操作
  5. 降低Api的开发难度(比如对jdbc的简易封装)

Hello Word

万事第一步:写hello word

Spring的功能被分为了很多个jar包,为了简易操作的复杂性,这里是使用maven来进行项目的创建和管理。

这里先建立一个maven的web项目,建立完成后在生成的pom文件中添加本次案例需要使用的依赖。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
  <groupId>commons-logging</groupId>
  <artifactId>commons-logging</artifactId>
  <version>1.1.1</version>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>RELEASE</version>
  <scope>test</scope>
</dependency>

整理项目目录,idea中maven的web项目目录默认是在src下有main文件夹会有resources文件夹和webapp文件夹
我们在main下建立java文件夹和test文件夹分别设置为sources root和sources test root
在resources文件夹下建立applicationContext.xml文件作为Spring的配置文件
在java下建立包名和类top.byfree.spring5.HelloWord
在test下建立包名和测试类top.byfree.test.TestSpring5

在探Spring项目目录

现在是万事具备,在HelloWord类中编写方法helloWord

public void helloWord() {
    System.out.println("Hello Word!");
}

在Spring配置文件中配置HelloWord类

<?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-2.0.xsd">
    <bean id="user" class="top.byfree.spring5.HelloWord"/>
</beans>

在TestSpring5中编写测试方法,使用junit

@Test
public void testAdd() {
    // 加载spring配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 获取配置文件中创建的对象
    HelloWord hello = context.getBean("user", HelloWord.class);
    hello.helloWord();
}

执行方法得到结果
结果

IOC

IOC原理

xml解析 + 工厂模式 + 反射

使用静态工厂解耦合度

class Student {
    private String name;
    private int age;

}
class StrudentFactory {
    public static Student getStudent() {
	return new Student();
    }
}

根据上述代码我们就可以不在测试的时候使用new来创建对象,而是使用工厂类中的静态方法来获取目标类的对象。

IOC的整体过程

  1. 必须有一个配置文件
<bean id="user" class="top.byfree.spring5.HelloWord"/>
  1. 创建工厂类用来返回这个类的对象
  2. 在工厂类中首先用xml解析读取配置文件中的全类名
  3. 用反射创建这个类的对象
class HelloWordFactory {
    public static HelloWord getHelloWord() {
	String helloWord = "获取的全限定路径"; // 通过xml解析
	// 使用反射
	Class clazz = Class.forName("top.byfree.spring5.HelloWord");
	return (HelloWord)clazz;
    }
}

IOC接口

  1. BeanFactory: Spring内部使用的接口,一般不在外部使用,特点是加载配置文件的时候不进行对象的创建,在使用对象的时候才进行创建。

  2. ApplicationContext:它是BeanFactory的子接口,拥有更强大的功能,一般是由开发人员使用,对象在读取配置文件的时候就会创建。

  3. ApplicationContext的实现类

    1. FileSystemXmlApplicationContext:这个实现类是通过文件在硬盘的路径来解析配置文件的
    2. ClassPathXmlApplicationContext:这个是通过项目根目录来找配置文件的

IOC的Bean管理-基于xml

基于xml创建对象

在配置文件中添加bean标签
<bean id="user" class="top.byfree.spring5.HelloWord"/>
bean标签中有很多属性,介绍:

  1. id:唯一的标识
  2. class:要创建类的全限定路径
  3. 创建对象的时候默认是使用无参构造方法来创建对象的,这也是标准的javabean类必须要由无参构造的一个原因之一

基于xml注入属性

DI:依赖注入,就是注入属性的意思
可以说DI是IOC的一种实现

基于set方法注入属性

必须为成员遍历编写set方法

public class HelloWord {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public void helloWord() {
        System.out.println("Hello Word!" + name);
    }
}

使用property标签注入属性
name属性:用来标识变量名称
value属性:表示要注入的值

<bean id="user" class="top.byfree.spring5.HelloWord">
    <property name="name" value="小胖"/>
</bean>

基于有参构造注入属性

编写有参构造方法

public class HelloWord {
    private String name;
    private int age;
    public HelloWord() {
    }
    public HelloWord(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public void helloWord() {
        System.out.println("我是" + name + "今年" + age + "岁了");
    }
}

使用constructor-arg属性来进行属性的注入
name属性:表示要注入的属性值的参数名称
value属性:表示注入的参数值
index属性:不想写属性的参数名可以使用index来表示属性的索引

<bean id="user" class="top.byfree.spring5.HelloWord">
    <!--用名称注入-->
    <constructor-arg name="age" value="18"/>
    <constructor-arg name="name" value="张三"/>
    <!--用索引注入-->
    <constructor-arg index="2" value="18"/>
    <constructor-arg index="1" value="张三"/>
</bean>

使用p命名空间简化set注入

第一步首先引入p名称空间

xmlns:p="http://www.springframework.org/schema/p"

第二步使用p名称空间简化

<bean id="helloWord" class="top.byfree.spring5.HelloWord" p:name="张三" p:age="12"/>

注入特殊符号和空值

注入空值

<bean id="helloWord" class="top.byfree.spring5.HelloWord">
    <property name="name">
        <null/>
    </property>
    <property name="age">
        <null/>
    </property>
</bean>

注入特殊符号

例如注入<南京>

  1. 使用转义
<bean id="helloWord" class="top.byfree.spring5.HelloWord">
    <property name="name" value="&lt;南京&gt;"/>
    <property name="age" value="12"/>
</bean>
  1. 使用CDATA
<bean id="helloWord2" class="top.byfree.spring5.HelloWord">
    <property name="name">
        <value><![CDATA[<南京>]]></value>
    </property>
    <property name="age" value="12"/>
</bean>

引用类型注入

使用外部bean注入引用类型

这里设UserService类中有UserDaoImpl这个类的类型的成员变量变量,怎么注入。
在外部先创建好要引用的bean,然后在要引用它的bean中的property标签中使用ref属性指向之前创建好的bean

<bean id="userDao" class="top.byfree.spring5.dao.UserDaoImpl"/>
<bean id="userService" class="top.byfree.spring5.service.UserService">
    <property name="userDao" ref="userDao"/>
</bean>

使用内部bean注入引用类型

这里是设Person类中有一个值类型的name变量和一个引用类型的team变量,这里使用的是内部注入的方式,直接在property中再写一个要引用的bean即可。

<bean id="person" class="top.byfree.spring5.bean.Person">
    <property name="name" value="卡特琳娜"/>
    <property name="team">
        <bean id="team" class="top.byfree.spring5.bean.Team">
            <property name="name" value="诺克萨斯"/>
        </bean>
    </property>
</bean>

级联注入

这里的级联注入我理解为,给一个类注入一个引用类型的时候这个引用类型很可能还有成员变量,先注入好成员变量,再注入引用变量,这种级联在一起的注入成为级联注入。

级联注入方案一

这个就跟之前的注入外部bean差不多

<bean id="person" class="top.byfree.spring5.bean.Person">
    <property name="name" value="凯瑟琳"/>
    <property name="team" ref="team"/>
</bean>
<bean id="team" class="top.byfree.spring5.bean.Team">
    <property name="name" value="皮尔特沃夫"/>
</bean>

级联注入方案二

这种方式是将要注入的引用类型的成员变量,也放到原来这个bean中注入,但是由于要获取到这个引用变量,在原类中必须要有这个引用变量的get方法

<bean id="person" class="top.byfree.spring5.bean.Person">
    <property name="name" value="艾希"/>
    <property name="team" ref="team"/>
    <property name="team.name" value="弗雷尔卓德"/>
</bean>
<bean id="team" class="top.byfree.spring5.bean.Team"/>

注入数组或者集合

首先这里有一个类stu中有各种数组和集合的成员属性

public class Stu {
    private String[] name;
    private List<Integer> age;
    private Map<String, String> home;
    public void setAge(List<Integer> age) {
        this.age = age;
    }
    public void setHome(Map<String, String> home) {
        this.home = home;
    }
    public void setName(String[] name) {
        this.name = name;
    }
    public void getStu() {
        System.out.println(Arrays.toString(name));
        System.out.println(home);
    }
}

数组可以使用array标签或者list标签来注入
list集合用list标签注入
map集合用map标签,key-value 用entry标签来表示
set集合就用set标签来注入

<bean id="stu" class="top.byfree.spring5.bean.Stu">
    <property name="name">
        <array>
            <value>吴亦凡</value>
            <value>凯瑟琳</value>
            <value>伊泽瑞尔</value>
        </array>
    </property>
    <property name="age">
        <list>
            <value>12</value>
            <value>11</value>
            <value>17</value>
        </list>
    </property>
    <property name="home">
        <map>
            <entry key="潮汐海灵" value="菲兹"/>
            <entry key="猩红收割者" value="弗拉基米尔"/>
        </map>
    </property>
</bean>

使用FactoryBean

Spring中有两种bean:普通bean和FactoryBean

普通bean:只能返回这个类实例
FactoryBean:可以返回其他类的实例

在创建我们的类的时候实现FactoryBean这个接口就把普通的类变成了工厂类,在其重写的方法getObject()中来定义和返回实例的类型。

public class MyFactoryBean implements FactoryBean<Person> {
    @Override
    public Person getObject() throws Exception {
        return new Person();
    }
    @Override
    public Class<?> getObjectType() {
        return null;
    }
    @Override
    public boolean isSingleton() {
        return false;
    }
}

在xml中配置bean

<bean id="myFactoryBean" class="top.byfree.spring5.bean.MyFactoryBean"/>

bean的作用域

我们在配置文件中编写一个helloWrod的bean,在测试方法中获取两次也就是两个对象,判断这两个对象地址是否相等

<bean id="helloWord" class="top.byfree.spring5.HelloWord"/>
@Test
public void testBean3() {
    // 加载spring配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 获取配置文件中创建的对象
    HelloWord helloWord1 = context.getBean("helloWord", HelloWord.class);
    HelloWord helloWord2 = context.getBean("helloWord", HelloWord.class);
    System.out.println(helloWord1);
    System.out.println(helloWord2);
}

bean的作用域结果
结果是相同的,这就是单例模式,也是bean默认的模式
在配置文件被加载时创建好对象,无论之后获取多少次都是获取的之前创建好的那个对象

和其对应的使原型模式,在配置文件中我们可以将scope设置为prototype来将这个bean设置为原型模式这样就不会再加载配置文件的时候来创建对象了,而是获取的时候创建对象,所以获取的都不会使一个对象

<bean id="helloWord" class="top.byfree.spring5.HelloWord" scope="prototype"/>

bean的作用域结果

另外scope的默认值是singleton也就是单例模式

bean的生命周期

生命周期就是一个对象从创建到销毁的过程

bean的生命周期

  1. 构造器创建bean实例(无参数构造)
  2. 为bean的属性设置值和对其他的bean引用(调用set方法)
  3. 把bean实例传递给bean后置处理器的方法
  4. 调用bean的初始化方法(需要进行配置初始化的方法)
  5. 把bean实例传递给bean后处理器的方法
  6. bean可以使用了(对象获取到了)
  7. 当容器关闭的时候,调用bean的销毁方法(需要进行配置销毁的方法)

后置处理器:编写一个类实现BeanPostProcessor接口并重写其中的postProcessBeforeInitialization方法和postProcessAfterInitialization方法,前方法是在bean初始化前执行,后面方法是在bean初始化后执行。注意:后置处理器在添加到配置文件后生效,生效后会对所有的在配置文件中的bean生效

演示

javabean代码

public class LeftBean {
    String name;
    public void setName(String name) {
        this.name = name;
        System.out.println("第二部:注入值!");
    }
    public LeftBean() {
        System.out.println("第一步:构造方法执行了!");
    }
    public void init() {
        System.out.println("第四步:初始化方法执行!");
    }
    public void des() {
        System.out.println("第七步:销毁方法执行!");
    }
}

后置处理器代码

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步:初始化之前");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步:初始化之后");
        return bean;
    }
}

配置文件
在配置文件中使用init-method属性来指定bean的初始化方法,用destroy-method来指定销毁时执行的方法。

    <bean id="leftBean" class="top.byfree.spring5.bean.LeftBean" init-method="init" destroy-method="des">
        <property name="name" value="张山"/>
    </bean>
    <bean id="myBeanPost" class="top.byfree.spring5.bean.MyBeanPost"/>

测试方法

    @Test
    public void testBean() {
        // 加载spring配置文件
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取配置文件中创建的对象
        LeftBean leftBean = context.getBean("leftBean", LeftBean.class);
        System.out.println("第六步:bean创建好了!");
        context.close();
    }

结果
bean的生命周期结果

bean的自动装配

bean的自动装配:是指在进行注入属性的时候,使属性按照定义的规则(按照名称,按照类型)进行自动注入。注意这里自动注入的属性只能使引用属性,也就是我们配置的其他的bean。

使用autowire标签设置byName为按照名称自动注入,设置byType为按照类型自动注入,注意使用byType的时候如果有两个相同类型的bean那么自动注入会报错。

<bean id="person" class="top.byfree.spring5.bean.Person" autowire="byType">
    <property name="name" value="艾希"/>
</bean>
<bean id="team" class="top.byfree.spring5.bean.Team">
    <property name="name" value="弗雷尔卓德"/>
</bean>

使用外部配置文件

应用情景,使用数据库时要注入连接池,这个链接池中的4大基本属性,我们通常是放在一个properties配置文件中的,在spring配置文件中读取它。

添加依赖

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.1.23</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.21</version>
</dependency>

创建外部配置文件jdbc.properties

prop.driverClass=com.mysql.jdbc.Driver
prop.url=jdbc:mysql://localhost:3306/userDb
prop.userName=root
prop.possWord=root

在核心配置文件中添加命名空间和约束文件地址

xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

向核心配置文件中注入外部配置文件并使用表达式获取值,注入到德鲁伊连接池中表达式使用${}获取外部配置文件中的值

<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driver" value="${prop.driverClass}"/>
    <property name="url" value="${prop.url}"/>
    <property name="name" value="${prop.userName}"/>
    <property name="password" value="${prop.possWord}"/>
</bean>

IOC的Bean管理-基于注解

使用注解创建对象

  1. @Component
  2. @Service:建议在业务逻辑层创建对象
  3. @Controller:建议在web层创建对象
  4. @Repositroy:建议在持久层创建对象

*注:上述4个注解全部都可以用来创建任何类,也就是他们的作用都是用来创建对象,只不过有名称上的区别,官方建议用来创建各种类的对象。

开启注解扫描

因为要开启注解扫描,而注解扫描在context标签中,所以要引入命名空间和约束文件

xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

开启注解扫描

<context:component-scan base-package="top.byfree.spring5"/>

注解扫描的细节配置

如果想要扫描多个包,之间可以使用逗号隔开

<context:component-scan base-package="top.byfree.spring5.service, top.byfree.spring5.dao"/>

可以配置只扫描那种注解类型
使用的是context:include-filter标签配置,具体看演示

<context:component-scan base-package="top.byfree.spring5">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

可以配置不扫描那种注解类型

<context:component-scan base-package="top.byfree.spring5">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
</context:component-scan>

演示创建对象

使用Component注解添加在HelloWord类中

@Component
public class HelloWord {
}

在测试类中测试

@Test
public void testBean() {
    // 加载spring配置文件
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 获取配置文件中创建的对象
    HelloWord helloWord = context.getBean("helloWord", HelloWord.class);
    System.out.println(helloWord);
}

使用注解注入属性

注入属性又会分为注入基本值属性或者引用类型,会用到4个注解

  1. @Autowired:按照类型进行自动注入引用
  2. @Qualifier:在Autowired的基础上指定名称注入引用
  3. @Resource:按照名称或者类型自动注入引用
  4. @Value:注入字面量类型
@Component
public class Person {
    @Value(value = "张三")
    private String name;
    @Autowired
    @Qualifier(value = "team")
    private Team team;
}

@Resource这个注解不是Spring官方的注解所以不推荐使用这里就不演示了,@Value注入集合属性的时候,需要事前在配置文件中声明然后再用表达式获取,具体可以看之前的springboot笔记中的介绍。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.byfree.top/archives/spring5