天天看点

对Spring IOC还迷糊的?赶紧看过来吧

作者:添甄
满怀忧思,不如先干再说!持续高频更新技术文章,值得持续关注哦!

Spring框架是做Java开发必备的技能,我们常说的Spring框架其实指的是Spring Framework这个产品,Spring的生态非常多:

  • SpringMVC:通过MVC模式实现Web开发
  • SpringBoot:提供默认配置实现快速开发,微服务的基础
  • SpringCloud:提供微服务,分布式架构的解决方案
  • SpringData:提供数据层支持
  • SpringSecurity:提供权限控制,安全访问解决方案
  • ......
学习Spring生态第一个要接触的其实就是Spring Framework,Spring中文意思是春天,也被广大程序员戏称,我们的春天来啦,如今Spring已经发展到第 6 个版本,生态非常完善,稳定,活跃,是Java程序员不可或缺的技术!

一、概述

1.1、什么是Spring

Spring是一个一站式、开源、低侵入式、轻量级框架,它的核心理念是IoC (Inversion of Control,控制反转) 和 AOP(Aspect Oriented Programming,面向切面编程),可以适用于任何Java程序,方便Java开发,是每一个Java Coder的必备技能。

1.2、术语介绍

框架:是能完成一定功能的半成品。框架帮助我们完成了一部分功能,比如:整体架构、资源管理、问题解决方案等。我们自己再完成业务,那这个项目就完成了。

一站式:Spring框架很“全面”且整合度高,提供IOC管理维护对象和AOP编写事务日志等模块,提供SpringMVC与用户交互,提供JDBCTemplate与数据库交互数据等技术。

开源:Sparing框架的源码对外开放,我们可以到git上下载源码,开源意味着全世界的Spring爱好者们可以参与到Spring框架的优化设计上,使该框架越来越好,同时知道源代码如何编写,对深入学习Spring框架也有很大好处,当然开源就也意味着免费。

低侵入式:侵入式代表使用该技术是否要求你的程序必须继承或者实现框架中的类或接口,在切换技术时就是一场灾难,而Spring是一个低侵入式的框架,不需要程序强制的实现或继承Spring中的接口和类。典型的Sturts框架侵入性很高,MyBatis-Plus其实也是有一定低侵入性!

轻量级:轻量级是相对于重量级而言的,轻量级一般就是非入侵性的、所依赖的东西非常少、资源占用非常少、部署简单等等,其实就是比较容易使用,而重量级正好相反。

JavaBean:属性私有,有Getter和Setter方法,无参的构造方法,这个类就符合JvaBean规范,就是一个JavaBean,JavaBean一般用来存储对象数据。

容器:在日常生活中容器就是一种盛放东西的器具,从程序设计角度看就是装对象的的对象,我们可以放入和拿出对象,容器也管理对象生命周期,我们使用的Tomcat其实也是一个Web容器,用来存放web程序。Java中的集合、数组都可以称为容器,它们用来存放程序运行时的对象,每个容器功能不同而已。

1.3、Spring的优势

  • 低侵入 / 低耦合 (降低组件之间的耦合度,实现软件各层之间的解耦)
  • 声明式事务管理(基于切面和惯例)
  • 方便集成其他框架(如MyBatis、Hibernate等)
  • 降低 Java 开发难度
  • Spring 框架中包括了 J2EE 三层的每一层的解决方案(一站式)
小贴士:Spring是可以单独使用的哦!在做基础Java应用时也可以使用Spring

1.4、Spring能帮我们做什么

1、Spring 能帮我们根据配置文件创建及组装对象之间的依赖关系。

2、Spring 面向切面编程能帮助我们无耦合的实现日志记录,性能统计,安全控制。

3、Spring 能非常简单的帮我们管理数据库事务。

4、Spring 还提供了与第三方数据访问框架(如Hibernate、JPA)无缝集成,而且自己也提供了一套JDBC访问模板来方便数据库访问。

5、Spring 还提供与第三方Web(如Struts1/2、JSF)框架无缝集成,而且自己也提供了一套Spring MVC框架,来方便web层搭建。

6、Spring 能方便的与Java EE(如Java Mail、任务调度)整合,与更多技术整合(比如缓存框架、消息队列等)。

1.5、结构

Spring大约有20个模块,由1300多个不同的文件构成。这些模块可以分为核心容器、AOP和设备支持、数据访问与集成、Web组件、通信报文和集成测试、集成兼容等类。Spring 5的模块结构如下图所示

对Spring IOC还迷糊的?赶紧看过来吧

spring核心模块

  • Spring-core:依赖注入IOC和DI的基本实现
  • Spring-beans:Bean工厂与Bean装配
  • Spring-context:定义基础的Spring上下文,也就是Spring容器
  • spring-context-support:对Spring IOC的扩展支持,以及IOC子容器
  • spring-context-indexer:Spring的类管理组件和Classpath扫描
  • spring-expression:Spring表达式语言

spring切面模块

  • spring-aop:面向切面编程的应用模块,整合Asm,CGLib,JDKProxy
  • spring-aspects:集成AspectJ,AOP应用框架
  • spring-instrument:动态Class Loading模块

spring数据访问与集成

  • spring-jdbc:spring提供的JDBC抽象框架的主要实现模块,用于简化Spring JDBC操作
  • spring-tx:Spring JDBC的事务支持模块
  • spring-orm:主要集成Hibernate、JPA、JDO
  • spring-oxm:将Java对象映射为XML数据,或者将XML数据解析为Java对象
  • spring-jms:Java Messaging Service能够发送和接收消息

spring web组件

  • spring-web:提供基础的Web支持,主要建立于核心容器之上,通过Servlet和Listeners初始化容器
  • spring-webmvc:实现SpringMVC的Web应用
  • spring-websocket:与前端的全双工通讯协议
  • spring-webflux:Spring5推出的一种新的非阻塞函数式Reactive Web框架,可以用来构建异步,非阻塞,事件驱动的web服务

spring通信报文

  • spring-messaging:spring4新加入的模块,为Spring集成基础的报文传送应用

spring集成测试

  • spring-test:主要为测试提供支持

spring集成兼容

  • spring-framework-bom:解决Spring不同版本,依赖版本不同的问题

二、IOC和DI

2.1、IoC:Inverse of Control(控制反转)

2.1.1、IOC概述

  • 读作“反转控制”,更好理解,不是什么技术,而是一种设计思想,就是将原本在程序中手动使用new关键字创建对象的控制权,交由Spring框架来管理。
  • 正控:若要使用某个对象,需要自己去负责对象的创建
  • 反控:若要使用某个对象,只需要从 Spring 容器中获取需要使用的对象,不关心对象的创建过程,也就是把创建对象的控制权反转给了Spring框架

2.1.2、创建名为spring的项目

使用IDEA通过Maven构建项目

2.1.3、pom依赖

<!--定义spring版本-->
    <properties>
        <spring-version>5.3.23</spring-version>
    </properties>

    <dependencies>
        <!--spring核心依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!--beans负责spring的ioc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring-version}</version>
        </dependency>
        <!--spring 的上下文,该模块主要是对beans的扩展-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring-version}</version>
        </dependency>
    </dependencies>           

2.1.4、创建spring配置文件

在resources目录下创建applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--使用bena标签创建对象:创建Fruit的对象在spring中的标记为fruit。我们可以通过fruit找到该对象并使用
        id属性:可以随便写。spring创建对象存储在spring中容器中的名字,注意不是对象名
        class属性:创建的对象的全路径
    -->
    <bean id="fruit" class="com.stt.pojo.Fruit">
        <!--使用property标签赋值
        name属性:类中的属性名
        value属性:具体的数据
        -->
        <property name="name" value="banana"></property>
        <property name="color" value="yellow"></property>
        <property name="taste" value="sweet"></property>
    </bean>
</beans>           

2.1.5、测试

在java目录下创建SpringTest类进行测试

package com.stt.test;

import com.stt.pojo.Fruit;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: SpringTest
 */
public class SpringTest {
    public static void main(String[] args) {

        //读取applicationContext.xml配置文件,获取applicationContext对象
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过applicationContext对象获取spring容器中的对象,我们来获取fruit对象
        Fruit fruit = (Fruit)applicationContext.getBean("fruit");
        //打印
        System.out.println(fruit);
    }
}           

结果如下:

对Spring IOC还迷糊的?赶紧看过来吧

2.1.6、总结

1、传统的方式就是通过new关键字创建Fruit的对象来使用,对象的创建、管理维护、销毁的工作都需要我们做,不方便。

2、使用IOC,对象的创建、管理维护和销毁都交给IOC来管理,如果要使用对象就到IOC容器中获取即可。

3、类中一定要有无参的构造方法,IOC是通过无参的构造方法创建对象。

4、IOC创建的对象默认是单例的,可以在bean标签中使用scope属性修改作用范围。

  • singleton:单例的(默认值)
  • prototype:多例的
  • request:作用于web应用的请求范围
  • session:作用于web应用的会话范围
  • global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session

2.2、DI:Dependency Injection(依赖注入)

2.2.1、DI概述

指 Spring 创建对象的过程中,将对象依赖属性(简单值,集合,对象)通过配置设值给该对象,简单的说就是给类中的属性赋值,比如上边案例中的给Fruit赋名字、颜色、口味。

2.2.2、新建Person类

在上边代码的基础上继续开发,我们创建一个Person类,类中有一个Fruit对象,可以把它想象成是Service调用的Dao层。

package com.stt.pojo;

/**
 * ClassName: Person
 */
public class Person {
    private String name;//名字
    private Fruit fruit;//水果

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Fruit getFruit() {
        return fruit;
    }

    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }
    //吃水果方法
    public void eat(){
        System.out.println(name+"吃一个"+fruit.getColor()+"的"+fruit.getName()+"觉得很"+fruit.getTaste());
    }
}
           

2.2.3、applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--使用bena标签创建对象:创建Fruit的对象在spring中的标记为fruit。我们可以通过fruit找到该对象并使用
        id属性:可以随便写。spring创建对象存储在spring中容器中的名字,注意不是对象名
        class属性:创建的对象的全路径
    -->
    <bean id="fruit" class="com.stt.pojo.Fruit">
        <!--使用property标签赋值
        name属性:类中的属性名
        value属性:具体的数据
        -->
        <property name="name" value="banana"></property>
        <property name="color" value="yellow"></property>
        <property name="taste" value="sweet"></property>
    </bean>
    <!--创建另外一个Fruit对象-->
    <bean id="fruit2" class="com.stt.pojo.Fruit">
        <property name="name" value="苹果"></property>
        <property name="color" value="绿"></property>
        <property name="taste" value="酸"></property>
    </bean>
    <!--创建Person对象-->
    <bean id="person" class="com.stt.pojo.Person">
        <!--使用ref属性引用IOC容器中的对象进行赋值-->
        <property name="fruit" ref="fruit2"></property>

        <property name="name" value="小明"></property>
    </bean>
</beans>           

2.2.4、测试

package com.stt.test;

import com.stt.pojo.Fruit;
import com.stt.pojo.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: SpringTest
 */
public class SpringTest {
    public static void main(String[] args) {

        //读取applicationContext.xml配置文件,获取applicationContext对象
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过applicationContext对象获取spring容器中的对象,我们来获取fruit对象
        //Fruit fruit = (Fruit)applicationContext.getBean("fruit");
        //打印
        //System.out.println(fruit);
        Person person = (Person) applicationContext.getBean("person");
        person.eat();
    }
}           

结果:

对Spring IOC还迷糊的?赶紧看过来吧

2.2.5、总结

1、DI其实就是对属性进行赋值,可以是一个普通的变量(String),也可以是一个对象(Fruit)甚至是一个集合

2、注入值在bean标签中使用property标签,注入固定的值使用value属性,注入其他的IOC中的对象使用ref,填写在IOC中id属性值即可

3、类中一定要写set方法,因为注入值是使用set方法注入的

4、IOC可以单独存在使用,而DI需要依赖于IOC

2.3、IOC原理

这是我们本章节的重点,我们知道了IOC可以帮助我们创建对象,那么它到底是如何创建的对象,我们一起来学习一下

1、读取配置文件或者注解,看看Person依赖的是哪个Fruit,拿到类的全路径也就是包名.类名

2、通过Jva中的反射,基于类名调用getInstance()方法,该方法回去调用类中的无参构造进行实例化,所以类中必须有个一个无参构造

3、将对象实例,通过构造函数或者 setter,传递给 Person

我们也可以自己通过编码方式实现IOC,但是没有必要重复造轮子,但是需要知道轮子是如何创造出来的。可以通过阅读Spring源码具体了解Spring中的IOC实现的具体细节

三、注解方式实现IOC和DI

3.1、背景

我们上边的案例使用xml配置通过bean标签和property标签实现IOC和DI,如果我们有很多对象需要Spring帮助我们管理编写xml文件将非常繁琐,Spring2.X的推出伴随着JDK1.5的诞生支持了通过注解的方式实现IOC和DI,极大的简化了我们的开发

3.2、编码实战

3.2.1、applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启包扫描,可以扫描到com.stt包下的所有注解-->
    <context:component-scan base-package="com.stt"/>
</beans>           

该配置文件需要添加context约束,使用context:component-scan开启包扫描,填写自己的包名,可以找到我们编写的注解。

3.2.2、Fruit类

package com.stt.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * ClassName: Fruit
 * 使用@Component注解创建对象,相当于bean标签,value属性相当于id属性
 * 意思是创建Fruit类的对象存储在IOC容器中,在IOC中名字为fruit
 * 使用Value注解来为属性注入固定的值
 */
@Component(value = "fruit")
public class Fruit {
    @Value("芒果")
    private String name;//水果名
    @Value("黄色")
    private String color;//颜色
    @Value("芒果味")
    private String taste;//口味

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getTaste() {
        return taste;
    }

    public void setTaste(String taste) {
        this.taste = taste;
    }

    @Override
    public String toString() {
        return name+"的味道是"+taste+"颜色是"+color;
    }
}
           

3.2.3、Person类

1、使用Resource注解注入Fruit对象

package com.stt.pojo;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * ClassName: Person
 */
@Component(value = "person")
public class Person {
    @Value("小明")
    private String name;//名字
    //使用Resource注解引用IOC容器中的对象给属性赋值
    @Resource(name = "fruit")
    private Fruit fruit;//水果

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Fruit getFruit() {
        return fruit;
    }

    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }
    //吃水果方法
    public void eat(){
        System.out.println(name+"吃一个"+fruit.getColor()+"的"+fruit.getName()+"觉得很"+fruit.getTaste());
    }
}
           

2、使用Autowired注解注入Fruit对象

package com.stt.pojo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * ClassName: Person
 */
@Component(value = "person")
public class Person {
    @Value("小明")
    private String name;//名字
    //使用Resource注解引用IOC容器中的对象给属性赋值
    //@Resource(name = "fruit")
    @Autowired
    private Fruit fruit;//水果

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Fruit getFruit() {
        return fruit;
    }

    public void setFruit(Fruit fruit) {
        this.fruit = fruit;
    }
    //吃水果方法
    public void eat(){
        System.out.println(name+"吃一个"+fruit.getColor()+"的"+fruit.getName()+"觉得很"+fruit.getTaste());
    }
}           

3.2.4、测试

package com.stt.test;

import com.stt.pojo.Person;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * ClassName: SpringTest
 */
public class SpringTest {
    public static void main(String[] args) {

        //读取applicationContext.xml配置文件,获取applicationContext对象
        ClassPathXmlApplicationContext applicationContext =
                new ClassPathXmlApplicationContext("applicationContext.xml");
        Person person =(Person) applicationContext.getBean("person");
        person.eat();
    }
}           

结果:

对Spring IOC还迷糊的?赶紧看过来吧

3.3、注解详解

3.3.1、IOC注解

这些注解都是使用在类上,我们的示例代码使用的是@Component注解实现的IOC,其实一共有四个注解可以实现该功能,其余三个分别为@Service(使用在service层)、@Repository(使用在dao层)、 @Controller(使用在web层)。它们的功能都一样,可以填写一个value参数,该参数的值就是对象在IOC容器中的名字,当然该参数不是必须的,如果不写,在IOC中的名字默认为类名小写,比如:fruit、person

3.3.2、DI注解

这些注解都是使用在属性上

1、@Value:注入一个固定的值

2、@Resource:该注解在javax.annotation包中,它属于Java,在使用时可以填写name参数,对应IOC容器中对象的名字,就是ByName进行引用注入,如果不写name参数则根据对象的类型,ByType(类的全路径)进行注入数据

3、@Autowired:该注解在org.springframework.beans.factory.annotation包中,它属于Spring,没有name属性,ByType从IOC容器中获取对象进行数据注入

3.3.3、作用域注解

使用SpringIOC管理对象,默认是单例的,使用xml文件我们可以在bean标签中使用scope属性设置对象的作用范围,如果使用的注解可以在类上添加@Scope注解自定义对象的作用范围,当然不写的话默认就是单例的,我们上边的案例就没有写。

3.4、总结

1、注解是Spring2.X推出的我们使用的Spring是5.3.23,Java是1.5支持注解,我这里的JDK是1.8

2、明显感觉出注解方式编写代码更加优雅方便,在工作中也是推荐使用注解方式

3、使用注解xml文件中就不需要编写bean标签,但是需要添加注解扫描<context:component-scan base-package="xxx.xx"/>

4、使用@Component、@Service、@Repository、 @Controller注解实现IOC

5、使用@Value、@Resource、@Autowired实现DI

不错记得三连哦,收藏等于学会,还不快快收藏!