首页 > 技术文章 > [JavaWeb] Spring笔记(基于狂神)

hoppz 2021-08-19 10:43 原文

Spring基础(笔记)

1、低耦合、高内聚(了解)

​ 在学习Spring框架之前,我们先来看看低耦合、高内聚这两个词,他们都是出自于软件工程中的概念,是判断一个程序设计好坏的标准之一

  • 内聚性:又称块内联系。指模块的功能强度的度量,即一个模块内元素彼此之间结合的紧密程度的度量。若一个程序之间各元素之间(程序段之间)联系紧密,则内聚性就高(高内聚)。

  • 耦合性:又称块间联系。指软件系统各模块之间相互紧密联系程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性就越差。相反其耦合性就越弱(低耦合)。

​ 举一个简单的例子:一个程序有50个函数,这个程序执行得非常好;然而一旦你修改其中一个函数,其他49个函数都需要做修改,这就是高耦合带来的后果。

​ 在实际的项目场景中,因为需求的变动而更改代码是很常见的,如果我们的代码是高耦合的,那么就可能出现上述描述的况:修改一处代码,导致依赖这段代码的其他原本正常运行的模块出现错误,这无疑是灾难性的。

​ 高耦合代码:

public class A{
    public void action(){ System.out.print("A实现方式") };   
}

public class B{
     public void action(){ System.out.print("B实现方式") };  
}

// C想要使用A的功能,在C中直接new A的对象 , 如果此时想将A换成B,则需要修改C中的代码
public class C{
    public void service (){
        B b = new B();
        b.action();
    }
}
public class A{
    public void action(){ System.out.print("A实现方式") };   
}

public class B{
     public void action(){ System.out.print("B实现方式") };  
}


//C 想要使用A的功能,在C的形参中指定具体实现类类型为A , 如果此时想将A换成B,则需要修改C中的代码
public class C{
    public void service (B b){
        b.action();
    }
}

低耦合代码:

​ 像我们平时学习的多态,就是解耦的一种体现,如下所示 :

public interface Base{
    public void action();
}

public class A implements Base{
    public void action(){ System.out.print("A实现方式") };   
}
public class B implements Base{
     public void action(){ System.out.print("B实现方式") };  
}

public class C{
    public void service (Base base){
        base.action();
    }
}


class Test{
    public static void main(String[] args){
        /* 
        	我们将共同的方法抽象到一个接口中,A 和 B两个类代表两种不同情况下,此方法的不同实现,
        	在实际的业务逻辑代码C中,用接口类型代替具体的子类型。实现功能上的解耦。
        */
        Base base = new A(); 
        // Base base = new B();
        new C().service(base);
    } 
}

2、工厂模式简介

​ 在程序中,一般我们会将一个对象相关的职责分为三类: 对象本身所具有的职责、创建对象的职责和使用对象的职责。对象本身的职责比较容易理解,就是对象自身所具有的一些数据和行为,可通过一些公开的方法来实现它的职责。我们主要来看创建对象的职责,在Java语言中,我们通常有以下几种创建对象的方式:

(1) 使用new关键字直接创建对象;

(2) 通过反射机制创建对象;

(3) 通过clone()方法创建对象;

(4) 通过工厂类创建对象。

​ 毫无疑问,在客户端代码中直接使用new关键字是最简单的一种创建对象的方式,但是它的使用弊端我们刚才也看到了,它会使用两个具有依赖关系的类耦合性增强,从而导致代码灵活性变差,难以维护。

​ 为了解决new带来的问题,我们最常见的一种思考方式是将 对象的创建职责从另一个类中移除,在它之外交由另一个类来创建,这个类就是我们说的工厂类。

​ 引入工厂类以后,对象的创建职责和对象的使用职责被分开, 要使用某个对象时,先问工厂获取(工厂内部具体怎么实现我们不关心)被使用对象发生修改也好,还是被替换也好,都只发生在工厂内部, 外部的使用者就知道一件事,问这个工厂可以拿到自己要使用的对象,从而实现代码之间的解耦。

​ 如下所示代码案例:

​ 比如我们在开发一个系统支付功能,一开始程序只有网银支付

public class EBankPay{
    public void pay(){
        System.out.println("执行网银支付逻辑。。。");
    }   
}



public class ServiceA{   
    public void action(){
        System.out.println("ServiceA 业务逻辑执行。。。。");
        EBankPay ebankPay = new EBankPay();
        ebankPay.pay();
    }
}

public class ServiceB{
    public void action(){
        System.out.println("ServiceB 业务逻辑执行。。。。");
        EBankPay ebankPay = new EBankPay();
        ebankPay.pay();
    }
}


class Test{
    public static void main(String[] args){
 
        ServiceA a = new ServiceA();
        a.action();
        
        ServiceB b = new ServiceB();
        b.action();
        
    } 
}

​ 思考: 当前代码如果想换成微信支付,会发现需要修改大量的代码 ,

​ 如何设计程序,可以实现在不修改代码的情况下,完成支付功能的切换

3、简单工厂模式(掌握)

工厂模式是最常用的一类创建型设计模式(主要用于创建对象的设计模式,共六种此类设计模式)。

工厂模式分三类,分别是:简单工厂模式、工厂方法模式、抽象工厂模式

  • 在简单工厂模式中常包含如下几个角色:

    • Factory(工厂角色):负责对象的创建,工厂类可以被外界直接调用,在工厂类中提供了 静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product。

    • Product(抽象产品角色):它是工厂类所创建的所有对象的父类,封装了各种产品对象的 公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的工厂方法, 因为所有创建的具体产品对象都是其子类对象。

    • ConcreteProduct(具体产品角色):它是简单工厂模式的创建目标,所有被创建的对象都充当这个角色的某个具体类的实例。

  • 代码实现

    • 工厂类

      /**
       * @Description:  支付对象创建工厂,专门负责创建支付对象
       */
      public class FactoryPay {
          
          public static PayBase getPayBase(){
              return new AliPay();
          }
      }
      
    • 抽象产品

      public interface PayBase {
          void pay();
      }
      
    • 具体产品

      public class AliPay implements PayBase{
          @Override
          public void pay() {
              System.out.println("支付宝支付逻辑。。。。");
          }
      }
      
      public class WeChatPay implements PayBase{
          @Override
          public void pay(){
              System.out.println("执行微信支付逻辑。。。");
          }
      }
      
      public class EBankPay implements PayBase{
          @Override
          public void pay(){
              System.out.println("执行网银支付逻辑。。。");
          }
      }
      
    • 使用

      public class ServiceA {
          public void action(){
              System.out.println("ServiceA 业务逻辑执行。。。。");
              PayBase payBase = FactoryPay.getPayBase();
              payBase.pay();
          }
      }
      
      public class ServiceB {
          public void action(){
              System.out.println("ServiceB 业务逻辑执行。。。。");
              PayBase payBase = FactoryPay.getPayBase();
              payBase.pay();
          }
      }
      

​ 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。当我们需要某个对象时,直接从工厂中获取,而不用自己创建,这样做的好处就是,所有需要指定对象的地方,我们都通过相应的工厂来得到,当此对象发生变化时,我们可以用修改大量的代码,只需要去修改工厂类中的代码即可,这样通过工厂模式我们可以降低两个类之间的耦合性。

4、Spring基础(了解)

  • 1、简介:

    ​ 一般来说,在传统应用程序开发中,一个完整的应用是由一组相互协作的对象组成,所以开发一个应用除了要开发业务逻辑之外,最多的是关注如何使这些对象协作来完成所需功能,并且要使得他们之间低耦合、高内聚,但是在传统程序开发创建对象及组装对象间依赖关系由我们在程序内部进行控制,这样会加大各个对象间的耦合,如果我们要修改对象间的依赖关系就必须修改源代码,重新编译、部署。
    ​ 如果能有种工具来帮助我们管理对象之间依赖关系,这样在开发过程便能减少开发人员的许多工作,让开发人员更多关注在业务上,从而大大提升开发的效率,而Spring 就是基于这个目的诞生出来的一个工具。

    ​ Spring是一个开源的的轻量级的应用开发框架,其目的是用来简化企业级应用程序开发,减少代码之间的侵入性。

    使用Spring的目的:Spring 的本质是管理软件中的对象,即如何创建对象和维护对象之间的关系,我们可以把Spring看成一个超级大工厂。

  • 2、历史及发展:

    • Spring诞生于2002年,当时Spring之父Rod Johnson(罗德·约翰逊) 在2001年10月写了一本书《Expert One-on-One J2EE》,介绍了当时Java企业应用程序开发的情况,并指出了 Java EE 和 EJB 组件框架中存在的一些主要缺陷。在这本书中,他提出了一个基于普通 Java 类和依赖注入的更简单的解决方案。
    • 为了构建应用程序,他编写了超过 30,000 行的基础结构代码,项目中的根包命名为 com.interface21,所以人们最初称这套开源框架为 interface21,这就是 Spring 的前身。
    • 2003 年 Rod Johnson 和同伴在此框架的基础上开发了一个全新的框架命名为 Spring,据 Rod Johnson 介绍 Spring 是传统 J2EE 新的开始,随后 Spring 发展进入快车道。
    • 2004 年 03 月,1.0 版发布。
    • 2006 年 10 月,2.0 版发布。
    • 2009 年 12 月,Spring 3.0 发布。
    • 2013 年 12 月,Pivotal 宣布发布 Spring 框架 4.0。
    • 2017 年 09 月,Spring 5.0 发布。
    • 经过十几年的优化迭代,Spring Framework 已经从最初的取代 EJB 框架逐步发展为一套完整的生态,最新的版本是 5.X,支持现代 Java 开发的各个技术领域,家族两大核心成员 Spring Boot 和 Spring Cloud 更是当下 Java 领域最为热门的技术栈。
  • 3、特点:

    • 方便解耦

      ​ 简化开发 通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更专注于上层的应⽤。

    • AOP编程的支持

      ​ 通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过 AOP轻松应付。 声明式事务的⽀持 @Transactional 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼开发效率和质量

    • 方便程序的测试

      ​ 可以用⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的事情。

    • 方便集成各种优秀框架

      ​ Spring可以降低各种框架的使⽤难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、 Quartz等)的直接⽀持。 降低JavaEE API的使⽤难度 Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤难度⼤为降低

  • 4、七大核心功能模块简介:

    • (1)、Spring Core(核心模块),即Spring容器

      ​ 核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转 (IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。

    • (2)、Spring AOP

      ​ 面向切面编程,通过配置管理特性,spring AOP直接将面向切面的编程集中到了框架中,所以可以很容易使spring管理的对象支持AOP。

    • (3) 、Spring DAO:

      ​ DAO是 Data Access Object的缩写,DAO模式思想是将业务逻辑代码与数据库交互代码分离,降低两者耦合。通过DAO模式可以使结构变得更为清晰,代码更为简洁。DAO模块提供了JDBC的抽象层,简化了数据库厂商的异常错误(不再从SQLException继承大批代码),大幅度减少代码的编写,并且提供了对声明式事务和编程式事务的支持。

    • (4)、Spring ORM:

      ​ Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,ORM建立了Java对象与数据库对象之间的影射关系,程序员不需要编写复杂的SQL语句,直接操作Java对象即可,从而大大降低了代码量,也使程序员更加专注于业务逻辑的实现。

    • (5)、Spring Context

      ​ Spring Context模块继承BeanFactory类,它是一个配置文件,向 Spring 框架提供上下文信息,并且添加了事件处理、国际化、资源装载、透明装载、以及数据校验等功能。

    • (6)、Spring Web

      ​ 此模块建立在Spring Context 基础之上,它提供了Servlet监听器的Context 和 Web应用的上下文 。

    • (7)、Spring MVC框架(Spring WebMVC)

      ​ MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。Spring的MVC框架提供清晰的角色划分:控制器、验证器、命令对象、表单对象和模型对象、分发器、处理器映射和视图解析器。Spring支持多种视图技术。

5、通过Spring获得一个对象(掌握)

  • 5.1、Spring中的Bean:

    ​ 在Spring中,Java类基本都统一交由Spring容器(可以简单理解成一个Spring超级工厂)管理,并且这些类的对象都由Spring容器来统一创建,我们称Spring创建的这些对象叫Bean

    注:Spring中的bean和 我们平常学的javaBean有本质区别,javaBean指的是一种特殊的java类,是我们用来封装数据的可复用的软件组件模型,Spring中的bean是纳入Spring容器管理的对象。并且在实际的开发过程中,我们一般不会将javaBean纳入到Spring的管理中。

  • 5.2、Spring容器分类:

    Spring容器有BeanFactory和ApplicationContext两种类型。BeanFacotry是Spring中比较原始的Factory,ApplicationContext继承自BeanFactory接口,拥有更多的企业级方法。

  • 5.3 、使用Spring获取一个对象的步骤:

    • (1)、创建一个普通Maven项目,并导入Spring相关 jar包

      <dependencies>
          <!--junit-->
          <dependency>
              <groupId>junit</groupId>
              <artifactId>junit</artifactId>
              <version>4.11</version>
              <scope>test</scope>
          </dependency>
          
       	<!--spring相关-->
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-core</artifactId>
              <version>5.1.12.RELEASE</version>
          </dependency>
         
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-context</artifactId>
              <version>5.1.12.RELEASE</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aop</artifactId>
              <version>5.1.12.RELEASE</version>
          </dependency>
      
          <dependency>
              <groupId>org.springframework</groupId>
              <artifactId>spring-beans</artifactId>
              <version>5.1.12.RELEASE</version>
          </dependency>
          <dependency>
              <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
              <version>5.1.12.RELEASE</version>
        </dependency>
          
          <!--日志-->
          <dependency>
              <groupId>commons-logging</groupId>
              <artifactId>commons-logging</artifactId>
              <version>1.2</version>
          </dependency>
      </dependencies>
      
    • (2)、书写一个普通的java类

    public class User{
    private int id = 1;
    private String userName = "zhangsan";
    private int age = 19;
    private String address = "厦门";
    //...省略get、set方法
    }

    
    
    
    - **(3)、将这个类相关配置书写在一个叫applicationContext.xml的文件中**
    
    ~~~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">
        
    
    	<bean id="userInfo" class="cn.gok.entity.User"></bean>
    
    </beans>
    
    - 配置文件相关配置属性简介:
    
            **1.beans** —— xml文件的根节点 
    
            **2.xmlns** ——是XML NameSpace的缩写,因为XML文件的标签名称都是自定义的,自己写的和其他人定义的标签很有可能会重复命名,而功能却不一样,所以需要加上一个namespace来区分这个xml文件和其他的xml文件,类似于java中的package。 
    
            **3.xmlns:xsi** ——是指xml文件遵守xml规范,是指具体用到的schema资源文件里定义的元素所准守的规范。即http://www.w3.org/2001/XMLSchema-instance这个文件里定义的元素遵守什么标准 
        **4.xsi:schemaLocation**——是指本文档里的xml元素所遵守的规范,这些规范都是由官方制定的,可以进你写的网址里面看版本的变动。xsd的网址还可以帮助你判断使用的代码是否合法。
    
  • (4)、写一个测试类获得java实例

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserBean  user=(UserBean)ac.getBean("userInfo");
    System.out.println("用户信息: "+user);	
    

6、Spring Bean标签分析(掌握)

<!--
      bean元素的常见属性:
            1、id  : 当前bean的唯一标识符,
                    ApplicationContext实例通过该属性获取指定类的对象
            2、name:    功能和id是一样(基本上不使用,为了满足struts1的遗留问题)
            3、class:   创建类的全路径
            4、scope:   Bean的作用域范围
-->
<bean id="userBean" class="cn.gok.entity.User"></bean>

7、Spring中Bean的实例化的三种方式(了解)

  • 1、使用构造器实例化:(90%通常使用该方法)

    ​ 构造器实例即是Spring在创建对象时,会自动调用相应的构造器来创建该对象

    public class User{
        private int id = 1;
        private String userName = "zhangsan";
        private int age = 19;
        private String address = "厦门";
        //...省略get、set方法
        
        public User(){
            System.out.println("调用User()创建出此对象")
        }
    }
    
    <bean id="user1" class="cn.gok.entity1.User"></bean>
    
  • 2、使用静态工厂方法实例化:

    ​ 目前我们在创建对象时,大多数都是直接调用构造器来创建,但是有些时候一些第三方的工具类,它创建过程中,可能需要初始化过多的东西,这时候直接调用构造器来创建这种类的对象,对我们来说是及其不友好的,因为创建该对象,可能你需要了解他一些初始化参数才能创建。这个时候一些第三方工具类为了简化我们的操作,会自定义一个工厂来创建工具类对象,在其底层封装了创建的细节,我们只需要简单使用该工厂类就能获取到想要的对象。

    ​ 那么同样的道理,Spring原本也是通过调用构造器方式去创建对象,但是一些第三方工具只提供了相应的工厂类来获取工具对象,也即是现在Spring这个超级工厂要 借助工具类的工厂 来获取工具对象。

    使用静态工厂方法实例化,也即是Spring调用静态工厂方法来回去一个对象:

    public class User{
        private int id;
        private String userName;
        private int age;
        private String address;
        //...省略get、set方法
    }
    
    //定义一个用户工厂来创建用户对象
    public class UserFactory(){
        
        //静态工厂方法返回User对象
        public static User newInstace(){
            User user = new User();
            user.setId(2);
            user.setUserName("zhangsan");
            user.setAge(20);
            user.setAddress("厦门");
            return user;
        }
    }
    
    
    <!--
    	使用静态工厂方法来获取对象
    		class现在为工厂类的全类名
    		factory-method指定的是创建相应对象的静态方法
    -->
    <bean  id="user" class="cn.gok.factory.UserFactory"  factory-method="newInstace"></bean>  
    
  • 3、使用实例化工厂方法实例化:

    public class User{
        private int id;
        private String userName;
        private int age;
        private String address;
        //...省略get、set方法
    }
    
    //定义一个用户工厂来创建用户对象
    public class UserFactory1(){
        
        //实例工厂方法返回User对象
        public User newInstace(){
            User user = new User();
            user.setId(2);
            user.setUserName("zhangsan");
            user.setAge(20);
            user.setAddress("厦门");
            return user;
        }
    }
    
    <!--使用实例工厂方法来获取对象:-->
    
    <!-- 首先我们需要先将工厂进行实例化-->
    <bean id="userBeanFactory" class="cn.gok.factory.UserFactory1"></bean>
    <!-- 
      接着借助实例工厂来创建对象
       factory-bean : 工厂的beanid
       factorymethod: 工厂中创建对象的实例方法名
     -->
    <bean id="user" factory-bean="userBeanFactory" factorymethod="newInstace"></bean>
    

  • 4、FactoryBean

    ​ FactoryBean是Spring提供的一个接口,FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。

    ​ FactoryBean的作用和 前面的静态工厂方法和实例工厂方法类似, 即通过定义一个工厂来实现bean的创建,只不过它是Spring提供的,在它里面有Spring要求的更规范的bean的创建过程。所以使用FactoryBean会更多一些。其他第三方框架在整合,也多半会去实现该接口,比如我们熟悉的Mybatis,在和Spring整合的过程中,就有定义工厂类实现该接口。

    如下所示,为FactoryBean接口源码

    // 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
    public interface FactoryBean<T> {
        
         @Nullable
         // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,
         //   则该实例会放到Spring容器的单例对象缓存池中Map
         T getObject() throws Exception;
    
    
         @Nullable
         // 返回FactoryBean创建的Bean类型
         Class<?> getObjectType();
    
    
         // 返回作⽤域是否单例
         default boolean isSingleton() {
            return true;
         } 
    
    }
    

具体实现:

/**
 * @Author: chenfan
 * @Description: 通过实现Spring提供的FactoryBean接口,来自定义一个工厂类,负责产生User对象
 */
public class UserFactoryBean implements FactoryBean<User> {

    /**
     *    Spring 会自动的调用该方法来获取需要的对象
     */
    @Override
    public User getObject() throws Exception {
        System.out.println("UserFactoryBean.getObject  run ....");
        return new User();
    }

    /**
     * 返回FactoryBean创建的Bean类型
     */
    @Override
    public Class<?> getObjectType() {
        return null;
    }

    /**
     * 返回作⽤域是否单例 ,true表示单例
     *      如果返回的是单实例,则不管调用多少次获取此对象的方法,获取的都是同一个对象
     */
    @Override
    public boolean isSingleton() {
        return true;
    }
}
<!--
 4、通过Spring提供的FactoryBean接口来定义工厂,并获取bean对象
-->
<bean class="cn.gok.factory.UserFactoryBean" id="user4"></bean>

8、Spring中Bean的五个作用域(了解)

(1)、singleton:单例模式 (默认), 每次调用getBean都获取的是同一个对象

(2)、 prototype:原型模式,每次调用getBean获取新的实例。

​ (3)、request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例

​ (4)、session: 将单个bean定义定义为HTTP会话的生命周期。仅在支持web的Spring ApplicationContext上下文中有效。

​ (5)、application:将单个bean定义定位到ServletContext的生命周期。仅在支持web的Spring ApplicationContext上下文中有效。

<bean class="cn.gok.entity.User" id="user" scope="prototype"/>

9、Spring中Bean的生命周期(了解)

bean的生命周期,指的是对象从创建到销毁的过程

  • 第一步: 通过构造器创建bean实例
  • 第二步:为bean所表示的对象的属性进行赋值
  • 第三步:调用bean中的init方法,进行初始化操作
  • 第四步:对象初始化完毕,可以通过getBean方法获取相应的对象
  • 第五步: 关闭Spring容器,会自动调用bean的destory方法进行销毁操作

10、SpringIOC(重点掌握)

  • 1、IOC(控制反转)基础概念:

    它不是一种技术,而是一种设计思想,即java程序中获取对象的方式发生反转,由最初的new方式创建,转变成由第三方框架创建、注入。

  • 2、DI(依赖注入):

    IOC是解耦思想,DI是实现这个思想的手段。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  • 3、Spring中两种依赖注入的方式(重点):

    public interface UserDAO {
        void queryById();
    }
    
    public class UserDAOImpl implements UserDAO {
        @Override
        public void queryById() {
            System.out.println("UserDAOImpl1.queryById  run...");
        }
    }
    
    • 3.1、set注入:

      public class UserServiceImpl {
      
          private UserDAO userDAO;
      
          /**
           * 通过set注入方式实现  userDAO的注入
           *      Spring在创建UserServiceImpl对象的时候,会自动的调用此方法,将UserDAO的实例注入进来
           */
          public void setUserDAO(UserDAO userDAO) {
              System.out.println("UserServiceImpl.setUserDAO  run...");
              this.userDAO = userDAO;
          }
      
          public void queryById(){
              userDAO.queryById();
          }
      }
      
      <bean class="cn.gok.dao.impl.UserDAOImpl" id="userDAO"></bean>
      
      <!--1、set方式注入-->
      <bean class="cn.gok.service.UserServiceImpl" id="userService">
          <!--
                  当我们在bean中添加了property子标签以后,
      			Spring会自动的调用相应的set方法,将具体参数值注入到指定的属性中
      
                  	name: 指定要被进行依赖注入的属性名
                  	ref : 要依赖注入的某个bean的id
              -->
          <property name="userDAO"  ref="userDAO"></property>
      </bean>
      
    • 3.2、构造器注入:

    
    public class UserServiceImpl1 {
    
        private UserDAO userDAO;
    
        /**
           *Spring自动调用此有参构造器,完成对UserDAO对象的注入
           */
        public UserServiceImpl1(UserDAO userDAO){
            System.out.println("UserServiceImpl1 有参构造器被调用...");
            this.userDAO = userDAO;
        }
    
        public void queryById(){
            userDAO.queryById();
        }
    }
    
     <!--2、构造器注入-->
    <bean class="cn.gok.service.UserServiceImpl1" id="userServiceImpl1">
    
        <!--
          当我们在bean中配置了constructor-arg子标签以后,
    			Spring就会自动调用相应的构造器,进行构造器注入
    
              name: 指定属性名
             ref:  指的是要依赖的bean的id
         -->
        <constructor-arg name="userDAO" ref="userDAO"></constructor-arg>
    </bean>
    
  • 4、Spring依赖注入实现注入普通值:

    Spring除了能实现对象与对象之间依赖关系外,还可以实现普通参数值注入

    public class Warehouse{
        private String WarehouseName;  
    }
    
    public class WarehouseMan {
        
        private String name;
        
        private Integer age;
        
        private List<String> interests;
        
        private Set<String> addresses;
        
        private Map<String, String> actions;
    		
        private Warehouse warehouse;
        
    }
    
    • 4.1、Spring注入普通参数值

      
      <bean class="cn.gok.WarehouseMan" id="warehouseMan">
      
          <!--
                  property 进行set注入时,如果对普通的参数值进行依赖注入(像字符串、整数、浮点数等)
                             name指定属性名
                             value指定普通的参数值
              -->
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
      </bean>
      
      
    • 4.2、p名称空间注入

      p名称空间注入在使用前,需要先引入p元素约束规范

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:p="http://www.springframework.org/schema/p"
             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">
      
          
          <!--
              2、p名称空间注入:
                      它是对set注入的一种简写形式
                      语法: p:属性名 = "属性值"
          -->
          <bean class="cn.gok.WarehouseMan" id="warehouseMan1" p:name="zhangsan" p:age="19"></bean>
          
          
          
      </beans>
      
    • 4.3、Spring注入List集合

      <!--
              3、实现list集合注入
                     使用list子元素实现
      -->
      <bean class="cn.gok.WarehouseMan" id="warehouseMan2">
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
          <property name="interests">
              <list>
                  <value>唱歌</value>
                  <value>跳舞</value>
                  <value>打游戏</value>
              </list>
          </property>
      </bean>
      
      
    • 4.4、Spring注入Set集合

      <!--
              4、实现set集合注入
                      通过set子标签实现
       -->
      <bean class="cn.gok.WarehouseMan" id="warehouseMan3">
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
          <property name="addresses">
              <set>
                  <value>厦门</value>
                  <value>福州</value>
                  <value>泉州</value>
              </set>
          </property>
      </bean>
      
  • 4.5、Spring注入Map

      
      <!--
              5、实现Map数据类型注入
                      通过map子标签实现
      -->
      <bean class="cn.gok.WarehouseMan" id="warehouseMan4">
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
          <property name="actions">
              <map>
                  <entry key="addWarehouse" value="新增仓库"></entry>
                  <entry key="updateWarehouse" value="修改仓库"></entry>
                  <entry key="deleteWarehouse" value="删除仓库"></entry>
              </map>
          </property>
      </bean>
    
    • 4.6、bean的级联赋值

       <!--
              内部bean和外部bean
      
                  外部bean:  被依赖的bean对象被定义在外部,通过ref属性引入
      
                  内部bean:  被依赖的bean对象直接定义在当前property子标签中
      
      -->
      
      
      <bean class="cn.gok.Warehouse" id="warehouse1">
          <property name="warehouseName" value="xxx仓库"></property>
      </bean>
      
      <bean class="cn.gok.WarehouseMan" id="warehouseMan5">
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
      
          <!-- 引入外部bean-->
          <property name="warehouse" ref="warehouse1"></property>
      </bean>
      
      
      
      
      <bean class="cn.gok.Warehouse" id="warehouse2"></bean>
      
      <bean class="cn.gok.WarehouseMan" id="warehouseMan6">
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
      
          <!-- 引入外部bean-->
          <property name="warehouse" ref="warehouse2"></property>
      
          <!-- 此时对warehouseName ,name = bean属性名.属性名-->
          <property name="warehouse.warehouseName" value="xxx仓库"></property>
      
      </bean>
      
      
      
      <bean class="cn.gok.WarehouseMan" id="warehouseMan7">
          <property name="name" value="zhangsan"></property>
          <property name="age" value="19"></property>
      
          <!--直接在内部声明bean对象-->
          <property name="warehouse">
              <bean class="cn.gok.Warehouse">
                  <property name="warehouseName" value="xxx仓库"></property>
              </bean>
          </property>
      </bean>
      

11、SpEl表达式

​ 我们在对Spring中的bean元素的普通属性进行注入的时候,可能会遇到这样的情况,这些参数值配置一些其他配置文件中。比如大家熟悉的jdbc.properties文件中就配置着数据库的连接参数,现在我们期望由Spring来创建JDBCUtil对象,然而JDBCUtil又需要jdbc.properties文件中的数据,那么该如何处理?

<bean id="jDBCUtil" class="cn.gok.db.JDBCUtil">
    <property name="driver" value="???"/>
    <property name="url" value="???"/>
    <property name="userName" value="???"/>
    <property name="password" value="???"/>
</bean>

​ 这种时候,我们就需要Spring给提供的SpEL表达式来帮助我们获取相关的参数值。

  • 1、SpEL简介

    ​ SpEL是一种Spring表达式语言,它使用起来类似于JSP中的EL表达式,只不过它是Spring支持的一种表达式语言(使用时

    我们需要有引入spring-expression这个jar包),它能够以一种强大而简洁的方式将值装配到Bean的属性和构造器参数中。

    ​ SpEL有三种用法,一种是在注解@Value中使用;一种是XML配置使用;最后一种是在java代码中通过操作Expression对

    象来实现使用SpEL(比较少用)。

    基础语法:

    #{表达式}${表达式}(一般用来取properties资源文件内的配置项)

    ​ 使用SpEL表达式时,我们只需要将表达式放入 #{ } 就可以使用了,使用SpEL我们可以:

    ​ (1)、使用bean的ID来引用bean;

    ​ (2)、调用对象方法和访问对象的属性;

    ​ (3)、对值进行算术、关系和逻辑运算;

    ​ (4)、 正则表达式匹配;

    ​ (5)、集合操作;

    ​ SpEL的用法较多,这里我们先学习SpEL在XML配置中的取值操作。

  • 2、SpEL表达式简单使用

    • (1)、SpEL表示字面量
      我们可以直接在SpEL中定义字面量,如整数、浮点数、字符串、布尔值等
    语法:  #{整数值}、    #{'字符串'}
    
     <!--(1)、SpEL表示字面量 -->
    <bean class="cn.gok.enity.Person" id="person">
        <property name="name" value="#{'zhangsan'}"></property>
        <property name="age" value="#{19}"></property>
    </bean>
    
    • (2)引用bean、bean的属性和bean的方法

      在SpEL中,我们可以直接使用在Spring容器已经存在的bean以及bean的属性、方法等。

      语法: #{beanId.属性}、#{beanId.方法名()}
      
      <bean class="cn.gok.enity.Dog" id="dog">
              <property name="name" value="旺财"></property>
              <property name="address" value="厦门"></property>
      </bean>
      
       <bean class="cn.gok.enity.Person" id="person1">
              <property name="dog" value="#{dog}"></property>
              <property name="address" value="#{dog.address}"></property>
      <!--        <property name="address" value="#{dog.getAddress()}"></property>-->
      </bean>
      
    • (3)、访问静态方法和常量

      在SpEL中,还可以直接操作静态方法或常量

      语法: #{T(全类名).常量 }   #{T(全类名).静态方法名() }
      
      <bean class="cn.gok.enity.Person" id="person1">
          <property name="creteDate" value="#{T(java.time.LocalDate).now()}"></property>
      </bean>
      
    • (4)、SpEL运算符

      在SpEL中我们还可以使用运算符进行相关的运算操作, 常见可以使用的运算符如下所示:

    运算符类型 运算符
    算术运算 +、-、*、/、%
    比较运算 <、>、==、<=、>=、lt、gt、rq、le、ge
    逻辑运算 and、or、not、
    条件运算 ?: (ternary)、?:(Elvis)
    正则表达式 matches
      语法: #{运算操作}
    
    <bean class="cn.gok.enity.Dog" id="dog1">
        <property name="age" value="8"></property>
    </bean>
    <bean class="cn.gok.enity.Person" id="person2">
        <property name="age" value="#{dog1.age + 10}"></property>
    </bean>
    
    • (5)、Elvis运算符

      该方式三目运算符的特殊写法,可以避免null报错的情况

      #{ str?:"默认值" }
      
      等价于java代码
      
      str != null? str: "默认值" 
      
      <!--
                (4)、Elvis运算符 在赋值的时候避免出现赋空值的情况
                      #{ str?:"默认值" }
                      等价于java代码
                      str != null? str: "默认值"
      -->
      <bean class="cn.gok.enity.Dog" id="dog2">
          <property name="address" value="厦门"></property>
      </bean>
      
      <bean class="cn.gok.enity.Person" id="person3">
          <property name="address" value="#{dog2.address?:'福州'}"></property>
      </bean>
      
    • (6)、 安全保证语法

      我们在操作某个对象的属性或方法时, 如果这个对象值为null, 则会抛出空指针异常。

      语法: “对象?.变量|方法” 可以避免这种情况

      #{user?.name}
      
      等价于java代码
      user == null? null: user.name
      
      <!--
              (5)、 安全保证语法
                      我们在操作某个对象的属性或方法时, 如果这个对象值为null, 则会抛出空指针异常。
                      #{user?.name}
                      等价于java代码
                      user == null? null: user.name
      -->
      <bean class="cn.gok.enity.Dog" id="dog3"></bean>
      
      <bean class="cn.gok.enity.Person" id="person4">
          <property name="address" value="#{dog3.address?.toUpperCase()}"></property>
      </bean>
      
    • (7)、SpEL表达式读取jdbc.properties配置文件信息

      <!--
       利用SpEL向指定bean注入xxx.properties文件的信息
        location属性自动加载配置xxx.properties,并且注册成对应bean
      -->
      <context:property-placeholder location="classpath:jdbc.properties"/>
      <bean id="jDBCUtil" class="cn.gok.db.JDBCUtil">
          <property name="driver" value="${jdbc.driver}"/>
          <property name="url" value="${jdbc.url}"/>
          <property name="userName" value="${jdbc.userName}"/>
          <property name="password" value="${jdbc.password}"/>
      </bean>
      

12、Spring自动装配

​ Spring能自动装配Bean与Bean之间的依赖关系,即无须使用ref属性显式地指定依赖Bean,而是由Spring容器检查XML配置文件内容。根据某种规则,为调用者Bean注入被依赖的Bean。Spring的自动装配可以通过根标签<beans../>元素的default-autowire属性指定,该属性对配置文件中所有的Bean起作用;也可以通过元素的autowire属性指定,该属性只对当前bean起作用。

Spring提供了5种装配方式:

  • 1、no:默认值,不使用自动装配,bean依赖关系完全由ref元素定义,在较大的部署环境中不鼓励改变这个配置,这样能使bean与bean之间依赖关系更清晰。

  • 2、byName: Spring容器以属性名作为id来查找对应的bean,调用set方法来完成注入

    
    
    <!--
            (1)、Spring自动装配: byName
                     Spring容器以属性名作为id来查找对应的bean,调用set方法来完成注入,
                     如果找不到,则不注入
    -->
    <bean class="cn.gok.entity.WarehouseMan" id="warehouseMan" autowire="byName"></bean>
    
  • 3、byType :Spring容器查找与 class属性类型一致的bean,然后调用set方法来注入

    <!--
               (2)、Spring自动装配: byType
                        Spring容器查找与 class属性类型一致的bean,然后调用set方法来注入
    
                        如果找不到,则不进行注入
                        如果找到两个以上同类型的bean,则抛出异常
     -->
    <bean class="cn.gok.entity.WarehouseMan" id="warehouseMan" autowire="byType"></bean>
    
  • 4、 constructor: 自动以属性名作为id进行查找,然后调用构造器里完成

    <!--
              (3)、Spring自动装配: constructor
                       动以属性名作为id进行查找,然后调用构造器里完成
                       如果找不到,则不进行注入
    -->
    <bean class="cn.gok.entity.WarehouseMan" id="warehouseMan" autowire="constructor"></bean>
    
  • 5、autodect: Spring容器根据Bean内部结构,自定决定使用constructor或byType策略

13、基于注解的Spring开发(xml + 注解)

​ 在前面的案例中,Spring要管理对象,就必须要在xml文件定义一个bean标签,并配置相关信息,这种方式在Spring管理少量对象的时候还可以接受,但是如果说我们现在有100个对象要让Spring来管理,这种方式就非常繁琐了,光bean标签我们就要书写100个,更不要提对象与对象之间的依赖维护。

​ 因此我们需要一种更简洁的方式来简化我们的配置。

  • 组件扫描

​ 组件扫描是指我们指定一个包路径,Spring会自动扫描该包及其子包所有组件类,当发现这些组件类前有特定的注解标记时,就将该组件拉进Spring容器,其等价于原来XML配置的定义功能;

​ 使用组件扫描可以代替大量xml配置的定义;

​ spring容器启动之后,如果发现配置文件当中包含了 component-scan的配置信息,则spring容器会扫描指定包及其 子包下面的所有的类;如果这些类包含了一些特定的注解,则容器会将其纳入容器进行管理。相当于在配置文件当中,添加了一个bean 的配置;对于扫描到的这些组件(类),Spring会对他们有默认的取名策略,即类名首字母小写。

  • ①、bean实例创建注解

    常见bean实例创建的注解(有以下注解的类会被自动注册成bean):
    @Component : 通用注解,是所有受Spring 管理组件的通用形式
    @ Service: 业务层注解,
    @ Repository : 数据持久层注解(DAO)

    • 具体使用步骤:

      • applicationContext.xml配置文件书写

        <context:component-scan base-package="要扫描的包名"/> 
        
      • 在想要实例化的类前加上注解,添加了注解以后该类就会被Spring自动实例化

        /**
        	当一个组件在扫描过程中被检测到时,会为该类生成一个默认的id标示值,
        	一般值为类名小写,我们也可以在注解标记中自定义该值
        */
        @Component
        public class UserBean{ 。。。。 }
        
  • ②、依赖注入相关注解

    通过注解的方式,我们可以简便的使用对象的依赖注入功能

    • @Autowired(重点) 、 @Qualaifier

      • 1)、这几个注解常用在一起配合使用,主要起作用的是@Autowired注解, 支持set方式注入,也支持构造器注入;

      • 2)、依赖注入时,默认优先按照类型去容器中找对应的组件,如果未找到,则抛出相应的异常,如果找到多个相同类型的组件,再将属性的名称作为组件的id去容器中查找,如果此时bean的id匹配失败,也会抛出异常;

      • 3)、只需要将@Autowired 注解添加到 set方法或构造器上,即可实现set注入或构造器注入效果;

      • 4)、我们平时更常见的做法是将 @Autowired 添加到要注入的属性上,直接调用属性进行注入(此时不会调用set或构造器,实现原理是依靠反射机制打破属性的私有封装,然后直接对属性设置值);

      • 5)、设置 @Autowired(required=false), 使用@Autowired依赖注入时如果一个bean都找不到话,会抛出异常,但是将它的属性required设置为false, 则找不到bean就不进行依赖注入

      • 6)、依赖注入时,当有多个同类型的bean,我们可以使用 @Qualaifier 来指定依赖某个bean, 只需要在该注解中指定bean的id,然后和@Autowire一起使用即可

        @Component
        public class User {
            @Autowired(required = false)
            @Qualifier("dog1")
            private Dog dog;
        }
        
        <context:component-scan base-package="cn.gok"></context:component-scan>
        <bean id="dog1" class="cn.gok.enity.Dog" ></bean>
        
    • @Resource注解

      ​ 该注解是J2EE提供的注解,并不是Spring提供的,也可以实现依赖注入效果,使用时需要先导入依赖

      <dependency>
          <groupId>javax.annotation</groupId>
          <artifactId>javax.annotation-api</artifactId>
          <version>1.3.2</version>
      </dependency>
      

      ​ 当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。

      ​ 我们也可以在该注解中通过name属性直接指定要注入的bean的id

    • @Vlaue注解

      ​ 上述的几个注解都是用来实现对象与对象的依赖注入效果, 通过@Vlaue注解我们可以实现普通参数的值的注入

      @Component
      public class User {
          @Value("zhangsan")
          private String name;
          @Value("厦门")
          private String address;
      }
      

14、基于注解的Spring开发(纯注解方式)

  • 简介

    纯注解方式,即不使用任何xml文件,来实现Spring的对bean的管理,比如在SpringBoot中,使用的是该方式。

    在纯注解方式中,我们会使用java配置类来替代xml配置文件

  • 具体实现

    使用纯注解的方式我们需要借助@Configuration @Bean @ComponentScan 这几个注解:

    • @Configuration 添加在类上,将当前类标记为一个配置类
    • @Bean 添加在方法上,用来获取一个bean对象
    • @ComponentScan 添加在配置类上,用进行包扫描操作
    /**
       @Configuration  注解将当前类标记为一个配置类,用来替代原本applicationContext.xml文件
     */
    @Configuration
    @ComponentScan("cn.gok.entity")
    public class SpringConfig {
    
        /**
         @Bean 注解等价于<bean></bean> 标签, 用来添加到方法上,在方法内部书写bean对象的创建过程,
            Spring会 自动调用此bean方法获取对象,并纳入Spring的管理,并且还会使用方法名作为bean的id
         */
    /*    @Bean
        public User user(){
            User user = new User();
            user.setUsername("zhangsan");
            return user;
    
        }*/
    }
    
    
    @Component
    public class User {
        @Value("zhangsan")
        private String username;
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "username='" + username + '\'' +
                    '}';
        }
    }
    

推荐阅读