Spring介绍

一、Spring简介

1、Spring是什么以及特性?

一句话概括:Spring是一个轻量级的 控制反转(IoC)和面向切面(AOP) 的容器(框架)。

特性:

  • 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
  • 控制反转:IOC——Inversion of Control,指的是将对象的创建权交给 Spring 去创建。使用 Spring 之前,对象的创建都是由我们自己在代码中new创建。而使用 Spring 之后。对象的创建都是给了 Spring 框架。
  • 依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用 setXX 方法去设置,而是通过配置赋值。
  • 面向切面编程:Aspect Oriented Programming——AOP
  • 容器:Spring 是一个容器,因为它包含并且管理应用对象的生命周期
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上 Spring 自身也提供了表现层的 SpringMVC 和持久层的 Spring JDBC)

2、Spring的组件

在这里插入图片描述

1、Core Container(Spring的核心容器)

Spring 的核心容器是其他模块建立的基础,由 Beans 模块、Core 核心模块、Context 上下文模块和 SpEL 表达式语言模块组成,没有这些核心容器,也不可能有 AOP、Web 等上层的功能。具体介绍如下

  • Beans 模块:提供了框架的基础部分,包括控制反转和依赖注入。
  • Core 核心模块:封装了 Spring 框架的底层部分,包括资源访问、类型转换及一些常用工具类。
  • Context 上下文模块:建立在 Core 和 Beans 模块的基础之上,集成 Beans 模块功能并添加资源绑定、数据验证、国际化、Java EE 支持、容器生命周期、事件传播等。ApplicationContext 接口是上下文模块的焦点。
  • SpEL 模块:提供了强大的表达式语言支持,支持访问和修改属性值,方法调用,支持访问及修改数组、容器和索引器,命名变量,支持算数和逻辑运算,支持从 Spring 容器获取 Bean,它也支持列表投影、选择和一般的列表聚合等。

2、Data Access/Integration(数据访问/集成)

JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。

3、Web模块

Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作

4、AOP、Aspects、Instrumentation和Messaging

在 Core Container 之上是 AOP、Aspects 等模块,具体介绍如下:

  • AOP 模块:提供了面向切面编程实现,提供比如日志记录、权限控制、性能统计等通用功能和业务逻辑分离的技术,并且能动态的把这些功能添加到需要的代码中,这样各司其职,降低业务逻辑和通用功能的耦合。
  • Aspects 模块:提供与 AspectJ 的集成,是一个功能强大且成熟的面向切面编程(AOP)框架。
  • Instrumentation 模块:提供了类工具的支持和类加载器的实现,可以在特定的应用服务器中使用。
  • messaging 模块:Spring 4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。 jcl 模块: Spring 5.x中新增了日志框架集成的模块

5、Test模块

Test 模块:Spring 支持 Junit 和 TestNG 测试框架,而且还额外提供了一些基于 Spring 的测试功能,比如在测试 Web 框架时,模拟 Http 请求的功能。

包含Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient

二、Spring 控制反转(IOC)和依赖注入(DI)

1、概念理解

1.1、什么是IOC

Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想,以前我们获取一个对象时采用的是自己创建一个的方式,这是一个主动的过程;而控制反转后,当我们需要对象时就跟工厂要,而工厂来帮我们创建或者查找对象,这是一个被动的过程。

总结:控制反转就是把创建和管理 bean 的过程转移给了第三方。而这个第三方,就是 Spring IoC Container,对于 IoC 来说,最重要的就是容器。

Spring IOC 容器:Spring 框架的核心是 Spring 容器。容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的组件。

Spring Bean:Bean 其实就是包装了的 Object(对象),无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object。(想一下别人送礼物给你的时候都是要包装一下的)

深入思考:

1) 谁控制谁?控制了什么?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建。

谁控制谁? 当然是IoC 容器控制了对象(Bean)
控制什么? 那就是主要控制了外部资源获取(不只是对象包括比如文件等)

2)为何是反转,哪些方面反转了?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象。

为何是反转? 因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;
哪些方面反转了? 依赖对象的获取被反转了。

3)图例解析

传统程序设计下,都是主动去创建相关对象然后再组合起来:

在这里插入图片描述
当有了IoC/DI的容器后,在客户端类中不再主动去创建这些对象了,如图
在这里插入图片描述

4)为什么要用 IoC 这种思想呢?换句话说,IoC 能给我们带来什么好处?

答:解耦!!!!

它把对象之间的依赖关系转成用配置文件来管理,由 Spring IoC Container 来管理。

在项目中,底层的实现都是由很多个对象组成的,对象之间彼此合作实现项目的业务逻辑。但是,很多很多对象紧密结合在一起,一旦有一方出问题了,必然会对其他对象有所影响,所以才有了解藕的这种设计思想。

在这里插入图片描述
如上图所示,本来 ABCD 是互相关联在一起的,当加入第三方容器的管理之后,每个对象都和第三方法的 IoC 容器关联,彼此之间不再直接联系在一起了,没有了耦合关系,全部对象都交由容器来控制,降低了这些对象的亲密度,就叫“解藕”。

1.2、什么是DI

DI:Dependency Injection,即“依赖注入”: 组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

深入思考

1)谁依赖于谁? 为什么需要依赖?

谁依赖于谁? 当然是应用程序依赖于IoC容器
为什么需要依赖? 应用程序需要IoC容器来提供对象需要的外部资源;

2) 谁注入谁? 注入了什么?

谁注入谁? 很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
注入了什么? 就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

1.3、IOC和DI的关系

其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。通俗来说就是IoC是设计思想,DI是实现方式

2、IOC配置的三种方式

2.1、XML配置

将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。这种方式出现很多早前的SSM项目中(现在项目基本不用),将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解(现在可以了)。

  • 优点: 可以使用于任何场景,结构清晰,通俗易懂
  • 缺点: 配置繁琐,不易维护,枯燥无味,扩展性差

示例:

1、配置xx.xml文件
2、声明命名空间和配置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">
    <bean id="mysqlImpl" class = "com.zhang.dao.UserDaoMysqlImpl"/>
    <bean id="oracleImpl" class = "com.zhang.dao.UserDaoOracleImpl"/>
    <bean id="UserServiceImpl" class = "com.zhang.service.UserServiceImpl">
        <!--        id类似于class实例出来的对象-->

        <!--
         ref  引用Spring容器中创建好的对象
         value: 具体的值
         -->
        <property name="userDao" ref="mysqlImpl"/>
    </bean>

</beans>

获取Bean:

    public static void main(String[] args) {
        // 通过xml文件,获取ApplicationContext,拿到Spring的容器
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

        // 通过IOC容器去拿Bean
        UserServiceImpl userServiceimpl = (UserServiceImpl)context.getBean("UserServiceImpl");
        // UserServiceImpl userServiceimpl = context.getBean("UserServiceImpl",UserServiceImpl.class)

        userServiceimpl.getUser();
    }

2.2、Java通过配置类配置

将类的创建交给我们配置的JavcConfig类来完成,Spring只负责维护和管理,采用纯Java创建方式。其本质上就是把在XML上的配置声明转移到Java配置类中

  • 优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活

  • 缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差

示例:

1)创建一个配置类, 添加@Configuration注解声明为配置类
2)创建方法,方法上加上@bean,该方法用于创建实例并返回,该实例创建后会交给spring管理,方法名建议与实例名相同(首字母小写)。注:实例类不需要加任何注解(即不需要加@Controller@Service@Repository等)


// @Configuration 代表这是一个配置类(也会被Spring容器托管,注册到容器中),和我们之前的bean.xml作用是一样的
@Configuration
public class BeansConfig {

    // 注册一个bean,就相当于我们之前写的bean标签
    //这个方法的名字,就相当于bean标签中的id属性
    //这个方法的返回值,就相当于bean标签中的class属性
    @Bean("userDao")
    public UserDaoImpl userDao() {
        return new UserDaoImpl();
    }


    @Bean("userService")  // 若只有 @Bean,则默认Bean的名字为方法名
    public UserServiceImpl userService() {
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(userDao());
        return userService;
    }
}

获取Bean:

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(myConfig.class);
        UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService"); 
        // UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class); 
        System.out.println(userService.findUserList());
    }

2.3、注解配置

通过在类上加注解的方式,来声明一个类交给Spring管理,Spring会自动扫描带有@Component@Controller@Service@Repository这四个注解的类,然后帮我们创建并管理,前提是需要先配置Spring的注解扫描器。

我们一般使用 @Autowired 注解自动装配 bean,要想把类标识成可用于 @Autowired 注解自动装配的 bean 的类,采用以下注解可实现:

  • @Component :通用的注解,可标注任意类为 Spring 组件。如果一个Bean不知道属于哪个层,可以使用@Component 注解标注。
  • @Repository : 对应持久层即 Dao 层,主要用于数据库相关操作。
  • @Service : 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao层。
  • @Controller : 对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面。
  • 优点:开发便捷,通俗易懂,方便维护。

  • 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置。

示例:
1)对类添加@Component相关的注解,比如@Controller@Service@Repository
2)设置ComponentScan的basePackage,共有三种方式,我们在后面一 一演示下


@Service
public class UserServiceImpl {

    /**
     * user dao impl.
     */
    @Autowired
    private UserDaoImpl userDao;

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return userDao.findUserList();
    }

}

要想拿到Bean,我们必须设置ComponentScan的basePackage,共有三种方式

第一种: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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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
                           http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--    指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.zhang"/>

</beans>

获取Bean:

    @Test
    public void test1(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService"); 
        // UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class); 
        System.out.println(userService.findUserList());
    }

第二种:@ComponentScan(“com.zhang”)

在配置类中进行扫描

@Configuration
@ComponentScan("com.zhang")
public class MyConfig {
}

获取Bean

    @Test
    public void test2(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
       UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService"); 
        // UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class); 
        System.out.println(userService.findUserList());
    }

第三种:直接在AnnotationConfigApplicationContext中扫描所需要的包

直接扫描加获取包

@Test
    public void test3(){
        ApplicationContext context = new AnnotationConfigApplicationContext("com.zhang");
        UserServiceImpl userService = ( UserServiceImpl)context.getBean("userService"); 
        // UserServiceImpl userService = context.getBean("userService",UserServiceImpl.class); 
        System.out.println(userService.findUserList());
    }

总结:通过以上三种扫描的方式,我们可以在IOC容器中创建Bean,并可以在IOC容器中拿到Bean。

若想看自己的IOC容器中有哪些Bean,可以通过以下方式查看

String[] names = context.getBeanDefinitionNames();
 for(String name: names){
      System.out.println(name);
}

3、依赖注入的三种方式

因为IOC配置的方式有三种,所以三种依赖注入也要基于这三种去分析。

3.1、Setter注入

Setter注入是通过set方法注入的,所以要保证我们的类(包括POJO、Serveice和Dao等,这些都是已经创建好的Bean)中设置set方法。

第一种情况:XML配置

针对不同的属性类别我们有不同的注入方法

<bean id="address" class="com.zhang.pojo.Address">
        <property name="address" value="西安"/>
    </bean>


    <bean id="student" class="com.zhang.pojo.Student">
        <!--        第一种:普通值注入,value-->
        <property name="name" value="章卫军"/>

        <!--        第二种:Bean注入,ref-->
        <property name="address" ref="address"/>
        <!--       第三种: 数组注入,array-->
        <property name="books">
            <array>
                <value>红楼梦</value>
                <value>西游记</value>
                <value>水浒传</value>
                <value>三国演义</value>
            </array>
        </property>

        <!--      第四种:  list注入,list-->
        <property name="hobbys">
            <list>
                <value>听歌梦</value>
                <value>敲代码</value>
                <value>看电影</value>
            </list>
        </property>

        <!--       第五种: map注入,map-->
        <property name="card">
            <map>
               <entry key="身份证" value="342923199609135517"/>
                <entry key="银行卡" value="44444444444444"/>
            </map>
        </property>

        <!--      第六种:  set注入,set-->
        <property name="games">
            <set>
                <value>LOL</value>
                <value>COC</value>
                <value>BOB</value>
            </set>
        </property>

        <!--      第七种:  null值注入,null-->
        <property name="wife">
            <null/>
        </property>

        <!--     第八种:  Properties值注入,props-->
        <property name="info">
            <props>
                <prop key="driver">E20201074</prop>
                <prop key="url"></prop>
                <prop key="username">zwj</prop>
                <prop key="password">zwj</prop>
            </props>
        </property>

    </bean>

在这里插入图片描述

另外我们是可以通过p:命名空间去依赖注入,这也是一种Setter注入,不过可以一次性注入全

     <!--  p命名空间注入,可以直接注入属性的值:property  -->
      <bean id="user" class="com.zhang.pojo.User" p:name="章卫军" p:age="18"/>

第二和第三种情况:在注解和Java配置方式下

在这两种情况下,我们首先要保证Bean通过这两种方式进行创建了,其次,我们可以在需要注入的Bean的set方法中加@Autowired注解


public class UserServiceImpl {


    private UserDaoImpl userDao;


    public List<User> findUserList() {
        return this.userDao.findUserList();
    }


    @Autowired
    public void setUserDao(UserDaoImpl userDao) {
        this.userDao = userDao;
    }
}

在Spring3.x刚推出的时候,推荐使用注入的就是这种, 但是这种方式比较麻烦,所以在Spring4.x版本中推荐构造函数注入

3.2、构造方法注入

和Setter注入通过set方法注入不同,构造方法是通过有参构造函数进行依赖注入的。接下来也要通过三种IOC配置进行分析。

1、XML配置

首先如果不指定有参构造函数,只是单纯的通过XML去创建一个Bean的话,如:<bean id="user" class="com.zhang.pojo.User">,它会默认使用无参构造函数。

在XML配置下,针对有参构造方法进行依赖注入,也有三种方式,第一种是通过索引,byIndex。第二种是通过类别,byType。第三种是通过名称,byName。不同于Setter注入,构造方法注入可以一次性将依赖全部注入成功

首先我们看实体类User

public class User {
    private String name;

    public User(String name){
         this.name = name;
    }


    public void show(){
        System.out.println("name="+name);
    }
}

然后根据三种有参构造函数进行依赖注入来分析

第一种:byIndex

       <!--  第一种,下标赋值!  -->
    <bean id="user" class="com.zhang.pojo.User">
        <constructor-arg index="0" value="zhangweijun"/>
    </bean>

第二种:byType(不建议使用)

      <!-- 第二种,类型赋值!  不建议使用,当有两个String类型时,就会产生错误-->
    <bean id="user" class="com.zhang.pojo.User">
        <constructor-arg type="java.lang.String" value="zhangweijun2"/>
    </bean>

第三种:byName(最常用)

    <!--  第三种,直接通过参数名来设置赋值! 最常用 -->
    <bean id="user" class="com.zhang.pojo.User">
        <constructor-arg name="name" value="章卫军"/>
    </bean>

同样我们也可以使用有参构造函数运用到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">
    <!-- services -->
    <bean id="userService" class="tech.pdai.springframework.service.UserServiceImpl">
        <constructor-arg name="userDao" ref="userDao"/>
        <!-- additional collaborators and configuration for this bean go here -->
    </bean>
    <!-- more bean definitions for services go here -->
</beans>

本质上是new UserServiceImpl(userDao)创建对象, 所以对应的service类是这样的:


public class UserServiceImpl {

    /**
     * user dao impl.
     */
    private final UserDaoImpl userDao;

    /**
     * init.
     * @param userDaoImpl user dao impl
     */
    public UserServiceImpl(UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }

}


同样我们也可以通过c:命名空间进行依赖注入

 <!--  c命名空间注入,通过构造器注入:constructor-arg -->
    <bean id="user2" class="com.zhang.pojo.User" c:name="章卫军" c:age="25" scope="prototype"/>

第二种和第三种情况:在注解和Java配置方式下

在这两种情况下,我们首先需要根据不同的配置创建Bean,然后和Setter注入相似,它是通过在set方法上加@Autowired,构造方法注入是通过在有参构造函数上加@Autowired


 @Service
public class UserServiceImpl {

    /**
     * user dao impl.
     */
    private final UserDaoImpl userDao;

    /**
     * init.
     * @param userDaoImpl user dao impl
     */
    @Autowired // 这里@Autowired也可以省略
    public UserServiceImpl(final UserDaoImpl userDaoImpl) {
        this.userDao = userDaoImpl;
    }

    /**
     * find user list.
     *
     * @return user list
     */
    public List<User> findUserList() {
        return this.userDao.findUserList();
    }

}

在Spring4.x版本中推荐的注入方式就是这种, 具体原因看后续章节

3.3、基于注解的注入(接口注入)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值