Spring简介

官网地址:https://spring.io/

我们所熟知的 SSM 框架之一就是 Spring ,在这之前我们已经学习了 MyBatis. 最后就是 SpringMVC,2022加油干,兄弟姐妹们up~~

框架

先来一张官网的全局框架图:
springProject.png

这里的Spring BootSpring Cloud 相信各位都听过,后面我们也会学习,今天的主角是 Spring Framework 也就是我们平时所说的 Spring框架,后面本文都称之为Spring .

Spring Framework官网地址:https://spring.io/projects/spring-framework

Spring Framework API文档:https://docs.spring.io/spring-framework/docs/current/javadoc-api/

再来看看 Spring Framework 的架构图:
spring-framework.png

核心简介

Spring 将它理解为一个容器,这个容器的功能是可以创建java对象、给java对象赋值、控制对象的生命周期。这个过程就叫做反转。而创建java对象、给java对象赋值、控制对象的生命周期这个过程叫做控制。这就是Spring的第一个核心内容 IoC (Inversion of Control的缩写,意为 控制反转),简言之,就是把对象的控制者交给了Spring

IoC是一个理论思想,自己能理解就可以,而它的技术实现方案叫:DI(Dependency injection 的缩写,意为 依赖注入).

另外一个核心内容是:AOP(面向切面编程)。我们在学习过程中始终把握这两个核心,不要偏移重点。

第一个Spring项目

创建流程

1、我这里为了和前面学习的MyBatis作区分,先新建EmptyProject 然后添加名为spring-01Module ,这样每次新建module即可。

2、添加Spring依赖(在pom.xml中)

  1. <!-- 添加Spring依赖-->
  2. <dependency>
  3. <groupId>org.springframework</groupId>
  4. <artifactId>spring-context</artifactId>
  5. <version>5.3.14</version>
  6. </dependency>

3、定义接口和实现类

接口:

  1. public interface SomeService {
  2. void doSome();
  3. }

实现类:

  1. public class SomeServiceImpl implements SomeService {
  2. @Override
  3. public void doSome() {
  4. System.out.println("实现接口 SomeService");
  5. }
  6. }

4、在resources 目录下创建Spring配置文件beans.xml,作用是声明java对象,通过<bean>标签,把对象交给Spring创建和管理。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5. <!-- 声明java Bean对象
  6. id:必须唯一,如果不自定义,Spring会自动指定
  7. class:接口实现类(可以是实体类本身,也可以是实体类的子类,还可以是接口的实现类)
  8. -->
  9. <bean id="someService" class="com.javafirst.service.impl.SomeServiceImpl"/>
  10. </beans>

5、通过容器使用对象

  1. String config = "beans.xml";
  2. ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
  3. // 根据id 获取对象
  4. SomeService someService = (SomeService) applicationContext.getBean("someService");
  5. someService.doSome();

Spring是通过对象的无参构造方法来创建对象的,Spring是一次全部映射beans.xml中配置的对象。Spring这种管理对象的方式本质是反射技术。拿到容器之后,我们就可以通过容器提供的方法来操作Java对象了。

容器可以映射的对象

前面我们自定义了一个实现接口的Java类,Spring是可以拿到该类并调用方法的,那么系统类呢?Spring是否也可以拿到,我们在beans.xml中添加下面这样一个系统类,来测试结果。

  1. <bean id="javaDate" class="java.util.Date"/>

测试代码和上面的类似:

  1. // 系统类
  2. Date date = (Date) applicationContext.getBean("javaDate");
  3. System.out.println(date);

结果当然是肯定的。那么我们自定义一个类,但没有实现接口,是否可以呢?答案也是可以的。各位感兴趣可以自己尝试,这里不做演示了。

Spring给属性赋值

DI分类:

  • set注入,也叫设值注入(推荐使用)
  • 构造方法注入

第一种方式,set注入

基本类型属性的set注入

语法很简单:

  1. <!-- set注入方式
  2. 适合基本数据类型和String;
  3. 这种方式执行的是java实体类中的setXXX()方法;
  4. -->
  5. <bean id="myStudent" class="com.javafirst.ba01.Student">
  6. <property name="name" value="张三丰"/>
  7. <property name="age" value="208"/>
  8. </bean>

学生类Student中只有两个属性,分别是nameage. 测试代码如下:

  1. @Test
  2. public void testSet_student() {
  3. String config = "ba01/applicationContext.xml";
  4. ApplicationContext context = new ClassPathXmlApplicationContext(config);
  5. Student student = (Student) context.getBean("myStudent");
  6. System.out.println(student);
  7. }

结果各位自行测试,能正确输出我们在xml中的赋值即可,当然这是我们自定义的java对象,如果使用系统的类呢?大家可以自行尝试我们前面提到的java.util.Date类。

对象类型set注入

我们再定义一个Java对象School,有属性nameaddress,然后在Student类中增加该对象。看具体代码:

  1. <bean id="myStudent" class="com.javafirst.ba01.Student">
  2. <property name="name" value="张三丰"/>
  3. <property name="age" value="208"/>
  4. <!-- 引用类型 -->
  5. <property name="school" ref="mySchool"/>
  6. </bean>

在原来的基础上增加了引用类型对象的赋值,与简单类型不同的是,这里使用了ref这个标签属性,很明显,这是引用的意思,也就是引用了下面这段代码:

  1. <bean id="mySchool" class="com.javafirst.ba02.School">
  2. <property name="name" value="北京大学"/>
  3. <property name="address" value="北京市海淀区颐和园路5号"/>
  4. </bean>

这个时候测试结果接口看到我们的赋值,测试代码相同的,就不贴了。

构造方法注入

我们给Student添加构造方法如下:

  1. public Student(String name, int age, School school) {
  2. this.name = name;
  3. this.age = age;
  4. this.school = school;
  5. }

接着是applicationContext.xml 中的代码:

  1. <!-- 构造注入 -->
  2. <bean id="myStudent" class="com.javafirst.ba03.Student">
  3. <!-- 使用name属性 -->
  4. <constructor-arg name="age" value="55"/>
  5. <constructor-arg name="name" value="东邪黄药师"/>
  6. <constructor-arg name="school" ref="mySchool"/>
  7. <!-- 使用index属性 -->
  8. <!-- <constructor-arg index="1" value="75"/>-->
  9. <!-- <constructor-arg index="0" value="重阳真人王重阳"/>-->
  10. <!-- <constructor-arg index="2" ref="mySchool"/>-->
  11. </bean>
  12. <bean id="mySchool" class="com.javafirst.ba03.School">
  13. <property name="name" value="北京理工大学"/>
  14. <property name="address" value="北京市海淀区中关村南大街5号"/>
  15. </bean>

构造方法的形参可以自定义,没有必要和类属性保持一致,也可以通过构造方法的形参的索引来赋值(如果不写name或者index,只给value赋值也是可以的,默认就是按照索引来的)。测试代码和前面一样的,能输出我们赋值的结果即为正确。

引用类型自动注入

  • byName 按名称注入。Java对象中引用类型的属性名和Spring容器中bean的id名称一样,且数据类型也相同,这些bean能够赋值给引用类型。
  • byType 按类型注入。java对象中引用类型的数据类型和Spring容器中bean的class值是同源关系,这些bean能够赋值给引用类型。

看下第一种方式的核心代码:

  1. <bean id="myStudent" class="com.javafirst.ba04.Student" autowire="byName">
  2. <property name="name" value="老顽童"/>
  3. <property name="age" value="112"/>
  4. </bean>
  5. <bean id="school" class="com.javafirst.ba04.School">
  6. <property name="name" value="北京交通大学"/>
  7. <property name="address" value="北京市海淀区高梁桥斜街44号"/>
  8. </bean>

两个注意点:增加了 autowire 属性,按名称注入就是byName;引用对象的注册<bean> 的id需要和Java实体类中引用对象的名称保持一致,也就是这里的 school .

看第二种方式前,我们先搞清楚什么是同源关系

  • Java实体类中引用类型的数据类型和bean的class值是一样的
  • Java实体类中引用类型的数据类型和bean的class值是父子类关系
  • Java实体类中引用类型的数据类型和bean的class值是接口和实现类的关系

这里以第一种为例,其他两种,相信各位自己都可以搞定,看我们的xml中代码:

  1. <bean id="myStudent" class="com.javafirst.ba05.Student" autowire="byType">
  2. <property name="name" value="紫阳真人"/>
  3. <property name="age" value="102"/>
  4. </bean>
  5. <bean id="my_school" class="com.javafirst.ba05.School">
  6. <property name="name" value="北京航空航天大学"/>
  7. <property name="address" value="北京市海淀区学院路37号"/>
  8. </bean>

区别就是这里的 autowire 属性值需要是 byType ;第二点就是引用类型的 class (也就是这里id为my_school 的 class值)需要和Java对象中声明的引用类型是同一个。

测试代码都是相同的,各位自行验证哈。

import的使用

前面我们都是例子比较简单的情况下,写一个 Spring 配置文件就完成了所有工作,但实际开发中远不止这点业务,可能需要我们将业务按照模块划分,这就需要我们提供独立的配置文件,在现有基础上,我们将 StudentSchool 进行独立的配置,然后保留主配置文件,看流程:

  1. 新建 spring-student.xmlspring-school.xml ,内容分别如下:
    ```xml <?xml version=”1.0” encoding=”UTF-8”?>

  1. ```xml
  2. <?xml version="1.0" encoding="UTF-8"?>
  3. <beans xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  6. <bean id="my_school" class="com.javafirst.ba06.School">
  7. <property name="name" value="凌霄宫"/>
  8. <property name="address" value="三十三重天"/>
  9. </bean>
  10. </beans>
  1. 修改原来的配置文件 applicationContext.xml 如下:
    这里其实还可以使用通配符,个人建议初学容易犯小错误,还是暂时都用全路径,这样比较清晰明了,错误容易排查,也容易理解。

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    5. <!-- 这里因为是在同一个包下,完整的路径应该是:classpath:ba06/spring-school.xml-->
    6. <import resource="spring-student.xml"/>
    7. <import resource="classpath:ba06/spring-school.xml"/>
    8. </beans>
  2. 测试代码如下:
    测试代码其实没有变化,结果各位自行验证。

    1. /**
    2. * 测试 import 引入bean配置
    3. */
    4. @Test
    5. public void testImport() {
    6. String config = "ba06/applicationContext.xml";
    7. ApplicationContext context = new ClassPathXmlApplicationContext(config);
    8. com.javafirst.ba06.Student student = (com.javafirst.ba06.Student) context.getBean("myStudent");
    9. System.out.println(student);
    10. }

第二种方式,注解方式

要学习的注解:

  • @Component 注解Java对象类前,创建java对象,给java对象起id,不写其 value 值的话,默认是类名小写。
  • @Repository 放在 dao 接口的实现类上面,创建java对象,是持久层的,可以访问数据库。
  • @Service 放在业务层接口的实现类上面,创建业务层java对象,业务层对象有事务功能。
  • @Controller 放在控制器上面,创建控制器java对象,是视图层,能把请求结果显示给用户。
  • @Value 简单类型属性赋值。
  • @Autowired 引用类型属性赋值。

@Component 注解示例

创建java对象如下:

  1. @Component(value = "my_student") // 不指定value,默认是类名小写
  2. public class Student {
  3. private String name;
  4. private int age;
  5. @Override
  6. public String toString() {
  7. return "Student信息:{" +
  8. "name='" + name + '\'' +
  9. ", age=" + age +
  10. '}';
  11. }
  12. }

和之前的区别少了属性对应的 setXXX()getXXX() 方法。

applicationConext.xml 对应代码如下:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. 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">
  6. <!-- 注解扫描器
  7. base-package 添加了注解的Java对象,扫描器会自动扫描该路径下的类以及子包中的类
  8. -->
  9. <context:component-scan base-package="com.javafirst.ba01"/>
  10. <!-- 扫描多个包 中间的分隔符可以是;也可以是,还可以是空格 -->
  11. <!-- <context:component-scan base-package="com.javafirst.ba01;com.javafirst.ba02"/>-->
  12. <!-- 扫描多个包 还可以直接指定父包(因为扫描器本身会对子文件夹进行扫描) -->
  13. <!-- <context:component-scan base-package="com.javafirst"/>-->
  14. </beans>

这里和之前的变化是不再需要手动添加<bean> 标签了,而是只需要注解扫描器即可,注解扫描器管理Java对象的方式有以上三种。推荐大家选择第二种。

测试代码和之前没什么区别:

  1. @Test
  2. public void testComponent() {
  3. String config = "ba01/applicationContext.xml";
  4. ApplicationContext context = new ClassPathXmlApplicationContext(config);
  5. // 注意这里的 my_student 就是注解 @Component 的 value 值
  6. Student student = (Student) context.getBean("my_student");
  7. System.out.println(student);
  8. }

结果大家自行验证即可。

@Value使用

针对简单类型的属性赋值使用。

我们在上面的基础上,给 Student 对象的两个属性赋值,代码如下:

  1. @Value(value = "司马如意")
  2. private String name;
  3. @Value("26") // 当注解只使用一个属性的时候 注解属性名可省略
  4. private int age;

这种在属性上面加注解的方式推荐使用,此外,还可以在对应的 setXXX() 方法上加注解,比如下面这样:

  1. @Value("诸葛如意")
  2. public void setName(String name) {
  3. this.name = name;
  4. }

如果两个同时加上,则会取后者的值。结果大家自行测试即可。

当然 @Value 注解还可以给属性赋值来自于外部文件中的值,我们在 resources 目录下新建 myconfig.properties,该文件中的内容如下:

  1. my_name=南极贤文
  2. my_age=55

对应的,加载该文件的操作需要我们再来学习一个标签,在 applicationContext.xml 中添加如下代码:

  1. <!-- 读取属性配置文件xxx.properties -->
  2. <context:property-placeholder location="classpath:myconfig.properties" file-encoding="utf-8"/>

这个其实和前面学习 MyBatis 的时候类似,我们也同样配置过加载属性文件的标签。接下来看java对象代码的变化:

  1. /**
  2. * 给属性赋值 来自于外部文件中的值
  3. */
  4. @Value("${my_name}")
  5. private String name;
  6. @Value("${my_age}")
  7. private int age;

测试代码没有变化,各位自行尝试哈。

@Autowired使用

引用类型属性赋值

我们在现有代码基础上增加一个 School 对象,并在原来的 Student 中声明 School 属性,对于 School 本身属性的赋值,就是简单类型赋值操作,其实各位已经学会了,主要看下 Student 中如何给引用类型添加注解:

  1. @Component(value = "my_student")
  2. public class Student {
  3. /**
  4. * 给属性加注解 推荐使用这种方式
  5. */
  6. @Value(value = "上官婉儿")
  7. private String name;
  8. @Value("22")
  9. private int age;
  10. /**
  11. * @Autowired 支持 byName 和 byType(默认)
  12. * 注解位置可在属性上面,也可在属性的 set 方法上面,推荐使用前者
  13. */
  14. @Autowired
  15. private School school;
  16. /**
  17. * @Autowired byName 方式
  18. * 这种方式要结合 @Qualifier(value = "对象的@Component注解的value值") 注解一起使用,但在数写上无先后顺序
  19. * 这种方式如果把 @Qualifier 注解的 value 值写错了,程序会终止(因为 @Autowired 注解的 required 值默认是true)
  20. */
  21. // @Autowired
  22. // @Qualifier(value = "my_school")
  23. // private School school;
  24. @Override
  25. public String toString() {
  26. return "Student信息:{" +
  27. "name='" + name + '\'' +
  28. ", age=" + age +
  29. ", school=" + school +
  30. '}';
  31. }
  32. }

对应的 School 中代码如下:

  1. @Component("my_school")
  2. public class School {
  3. @Value("乾坤学院")
  4. private String name;
  5. @Value("长安城光华街西北角")
  6. private String address;
  7. @Override
  8. public String toString() {
  9. return "School信息:{" +
  10. "name='" + name + '\'' +
  11. ", address='" + address + '\'' +
  12. '}';
  13. }
  14. }

结果各位自行测试哈,很简单。

@Resource使用

jdk1.8本身自带的,如果使用高于1.8,那么需要手动添加依赖

  1. <dependency>
  2. <groupId>javax.resource</groupId>
  3. <artifactId>javax.resource-api</artifactId>
  4. <version>1.7.1</version>
  5. </dependency>

该注解同样支持 byName(默认) 和 byType 两种赋值方式, 不同于 @Autowired 的是 该注解如果 byName 赋值失败,会自动使用 byType 赋值.

我们看下核心的注解代码:

  1. /**
  2. * @Resource 注解是jdk1.8本身自带的,如果使用高于1.8,那么需要手动添加依赖
  3. * <p>
  4. * 该注解同样支持 byName(默认) byType两种赋值方式,
  5. * 不同于 @Autowired 的是 该注解如果 byName 赋值失败,会自动使用 byType 赋值.
  6. <p>
  7. * 如果只想使用 byName 方式,
  8. * 那么只需要给 @Resource 注解的 name属性值指定为对象类型的 @Component 注解的value值(此时value值没有必要和声明的引用对象名相同)
  9. */
  10. @Resource
  11. private School school;

我们知道,byName 方式是通过 id 匹配的,所以我们 School 类的 @Conponent 注解的 value 值应改为 school . 结果各位自己测试,相信能看到这里,说明你已经很熟练了,哈哈~

总结

  • 文本开头也提到了,Spring 中最重要的两个知识点 IoCAOP ,本节主要学习了前者
  • 通过学习MyBatis ,现在学 Spring 就相对轻松一些了,因为很多内容似乎是相同的,其实这个感觉也可以用于学习不同编程语言之间
  • 注解开发方式现在流行起来了,所以相对要重点掌握,但也要看自己所处的“环境”。

本文源码在公众号 推荐学java 回复 springDemo 即可获得。同时再给大家赠送一份手册spring-framework-5-0-0-m3.pdf

小编特意创建了一个公众号:推荐学java,分享原创 java 内容,欢迎大家微信搜索javaFirst关注(关注即送精品视频教程),一起学Java~