Spring简介

199 阅读7分钟

什么是Spring

Spring是一个开源的轻量级框架,它的目标是使J2EE开发变得更容易。该框架提供了大约20个模块(Spring5.x版本中portlet组件已经被废弃):

core组件是spring所有组件的核心;bean组件和context组件是实现IOC和依赖注入的基础;AOP组件用来实现面向切面编程;web组件包括springmvc是web服务的控制层实现。

第一个Spring项目

以idea2019为例

(1)创建一个新项目Spring,勾选下方的"Create empty spring-config.xml"。(若忘记勾选也可以等新建完项目后自己新建配置文件spring-config.xml)

(2)新建后的项目:

使用示例

新建一个User类:

public class User {
    private String uname;
    private String password;

    public User() {
        System.out.println("User constructor");
    }

     public String getPassword() { return password; }

    public void setPassword(String password) {
        this.password = password;
    }
    
    public String getUname() {
        System.out.println("getUname()");
        return uname;
    }

    public void setUname(String uname) {
        System.out.println("setUname()");
        this.uname = uname;
    }
    //省略password的get/set方法
    public void login() {
        System.out.println(uname + " is logining");
    }
}

按照以往的思路,我们需要创建User类的实例对象,设置好相关字段后,调用方法。而在Spring中,我们在spring-config.xml中添加相关配置:

    <bean id="User" class="bean.User">
        <property name="uname" value="C2y"/>
        <property name="password" value="root"/>
    </bean>

然后测试:

    public static void main(String[] args) {
    //生成工厂对象,
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring-config.xml");
        User user = (User)ac.getBean("User");
        user.login();
    }

输出为:

所有的类实例的创建都不需要我们自己创建,而是交给Spring容器来创建和管理。

ApplicationContext 容器

ApplicationContext是 BeanFactory 的子接口,它被称为Spring上下文。它可以加载配置文件中中定义的 bean,将所有的 bean 集中在一起,当有请求的时候分配 bean。

最常被使用的 ApplicationContext 接口实现:

(1)FileSystemXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。我们需要提供给构造器 XML 文件的完整路径。

ApplicationContext ac = new
FileSystemXmlApplicationContext("F:/javaCode/SpringWeb/src/spring-config.xml");

(2)ClassPathXmlApplicationContext:该容器从 XML 文件中加载已被定义的 bean。我们不需要提供 XML 文件的完整路径,只需正确配置 CLASSPATH 环境变量即可,因为容器会从 CLASSPATH 中搜索 bean 配置文件。

(3)WebXmlApplicationContext:该容器会在一个 web 应用程序的范围内加载在 XML 文件中已被定义的 bean。

控制反转IOC

在传统的Java SE程序设计中,我们主动在类内部创建依赖对象,这导致类与类之间高耦合,难于测试。

以设计一辆汽车为例,我们需要先设计轮子,然后根据轮子大小设计底盘,接着根据底盘设计车身,最后根据车身设计好整个汽车。这种设计方式出现了一个依赖关系:

从代码的角度来看:

每一个类的构造函数直接调用了底层类构造函数,如果我们将来需要调整轮胎Tire的尺寸为动态尺寸,而不是固定的30:

那么上层的类都需要调整。对于实际工程项目,则我们都需要修改以它作为依赖的类,这样的维护成本非常大。

控制反转IOC

控制反转IOC是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。这种设计原则强调应该由上层控制下层。

前面我们提到,传统的Java设计中是直接在类内部创建一个对象,是我们主动控制去获取依赖对象;而IOC有一个容器负责创建这些对象,即IOC容器控制对象的创建,这也是反转的含义:依赖对象的获取被反转了,我们被动的接受依赖对象。

有了这个容器,我们只需要维护一个配置文件(例如xml),而不需要每次初始化一个类还要写一堆的初始化代码:

我们不需要了解所需类的具体创建实例的细节,就好比我们只需要向一个工程请求一个Car实例,然后它就会按照配置来创建。

依赖注入DI

依赖注入DI是控制反转的一种实现方式:它把底层类作为参数传入到上层类,实习上层类对下层类的控制。

这样如果我们想修改底层类的代码,则只需修改底层类即可

此处我们采用的是构造函数传入的方式进行依赖注入,此外还有set传递和接口传递。

代码示例

(1)基于构造方法的依赖注入

public class Student{
    private String studentID;
    private PersonMessage pm;
    
    public Student(String studentID, PersonMessage pm) {
        System.out.println("Student类构造方法");
        this.studentID = studentID;
        this.pm = pm;
    }
    
    public Student() {
        System.out.println("Student类的无参构造函数");
    }
     //省略get/set,toString方法
}
public class PersonMessage {
    private String name;
    private int age;

     public PersonMessage(String name, int age) {
        System.out.println("PersonMessage类有参构造方法");
        this.name = name;
        this.age = age;
    }

    public PersonMessage() {
        System.out.println("PersonMessage类无参构造方法");
    }
    //省略get/set,toString方法
}

spring-config.xml

    <bean id="PersonMessage" class="bean.PersonMessage">
<!--        index属性表示构造函数的索引-->
        <constructor-arg index="0" value="on1"/>
        <constructor-arg index="1" value="18"/>
    </bean>

<bean id="Student" class="bean.Student" >
<!--若要向一个对象传递一个引用,需要使用标签ref属性;-->
<!--若是直接值传递,则使用value属性-->
    <constructor-arg ref="PersonMessage"/>
    <constructor-arg type="java.lang.String" value="201902151918"/>
</bean>

输出:

(2)基于set方法的依赖注入

public class Student {
    private String studentID;
    private PersonMessage pm;
    
    public Student() {
        System.out.println("Student类的无参构造函数");
    }
    
    public Student(String studentID, PersonMessage personMessage) {
        System.out.println("Student类的有参构造函数");
        this.studentID = studentID;
        this.personMessage = personMessage;
    }
    //省略 get/set,toString方法
}
public class PersonMessage {
    private String name;
    private int age;

    public PersonMessage(String name, int age) {
        System.out.println("PersonMessage类有参构造方法");
        this.name = name;
        this.age = age;
    }

    public PersonMessage() {
        System.out.println("PersonMessage类无参构造方法");
    }
     //省略 get/set方法
}

spring-config.xml

    <bean id="PersonMessage" class="bean.PersonMessage">
        <property name="name" value="on1"/>
        <property name="age" value="18"/>
    </bean>

    <bean id="Student" class="bean.Student" >
        <property name="pm" ref="PersonMessage"/>
        <property name="studentID" value="201902151918"/>

    </bean>

输出:

Bean

构成应用程序的支柱并由Spring IoC容器管理的对象称为bean,所有的bean统一放在context的上下文中管理。

Bean 与 Spring 容器的关系

bean的属性

Bean 的作用域

Spring 框架支持以下五个作用域,分别为singleton、prototype、request、session和global session

singleton 是默认的作用域,这样IOC容器只会存在一个共享的bean示例。

    <bean id="User" class="bean.User" scope="singleton">
        <property name="uname" value="C2y"/>
        <property name="password" value="root"/>
    </bean>

Bean 生命周期

当一个 bean 被实例化时,它可能需要执行一些工作使它转换成可用状态;当 bean 不再需要,并且从容器中移除时,可能需要做一些清除工作。为了实现这些 工作,我们可以在声明init-method 和 destroy-method 参数。

此处以 XML 的配置元数据为例:我们在User类添加两个方法:

    public void init() {
        System.out.println("初始化后的一些列工作");
    }

    public void destroy() {
        System.out.println("清除User对象前的一些列工作");
    }

修改xml文件:

    <bean id="User" class="bean.User" init-method="init" destroy-method="destroy">
        <property name="uname" value="C2y"/>
        <property name="password" value="root"/>
    </bean>

测试一下:

    public static void main(String[] args) {
        AbstractApplicationContext aac = new ClassPathXmlApplicationContext("spring-config.xml");
        User user = (User)aac.getBean("User");
        user.login();
        aac.registerShutdownHook();
    }

输出:

如果你的所有bean对象的初始化方法和销毁方法都是一样的,则可以设置为默认的初始化和销毁方法:

<?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"
       default-init-method="init"
       default-destroy-method="destroy">

    <bean id="User" class="bean.User" >
        <property name="uname" value="C2y"/>
        <property name="password" value="root"/>
    </bean>
</beans>

bean继承

bean 定义包含了很多的配置信息,如构造函数的参数,属性值,容器的具体信息例如初始化方法,静态工厂方法名等。而子bean的定义可以继承父定义的配置数据。

Spring Bean 定义的继承与 Java 类的继承无关,但是继承的概念是一样的。

示例

添加类VIPUser

public class VIPUser {
    private int level;
    private String AnoName;
    private String uname;
    private String password;
    //省略get/set方法
    @Override
    public String toString() {
        return "VIPUser{" +
                "level=" + level +
                ", AnoName='" + AnoName + '\'' +
                ", uname='" + uname + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

xml文件:

<bean id="User" class="bean.User" >
        <property name="uname" value="C2y"/>
        <property name="password" value="root"/>
    </bean>

    <bean id="VIPUser" class="bean.VIPUser" >
        <property name="level" value="3"/>
        <property name="AnoName" value="C2y-3"/>
    </bean>

测试一下:

    public static void main(String[] args) {
        ApplicationContext aac = new ClassPathXmlApplicationContext("spring-config.xml");
        VIPUser vipUser = (VIPUser)aac.getBean("VIPUser");
        System.out.println(vipUser);
    }

输出;

参考资料

知乎问题:Spring IoC有什么好处呢? 答主:Mingqi

W3Cschool-Spring教程