你真的了解Spring吗(全网最全Spring笔记上)

·  阅读 1391
你真的了解Spring吗(全网最全Spring笔记上)

这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战

一、工厂设计模式

1.1、传统的容器——EJB的缺点

    EJB(Enterprise Java Beans),被称为企业Java Beans。他是上一代使用的容器。我们来看看传统的J2EE的体系。

ShowImage

    EJB具有的缺点是很致命的:

  1. 运行环境苛刻。
  2. 代码移植性很差。
  3. EJB是重量级框架。

1.2、什么是Spring

    Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式,其中最重要的设计模式是——工厂设计模式。他还包含其他的设计模式,比如说:代理设计模式、模板设计模式、策略设计模式等等。

1.3、什么是工厂设计模式

    在传统的创建对象的时候,我们都是调用无参构造函数来创建对象的即new的方式来创建,这样创建对象的方式的耦合程度(指定是代码间的强关联关系,一方的改变会影响到另一方)就十分高。、

    一旦我们需要修改类型,就需要代码中修改,并且重新编译和部署。

1.4、工厂设计模式的实现

package com.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public class BeanFactory {
    private static Properties env = new Properties();

    static{
        try {
            //第一步 获得IO输入流
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //第二步 文件内容 封装 Properties集合中 key = userService ,value = com.service.impl.UserServiceImpl
            env.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /*
        对象的创建的两种方式:
          1. 直接调用构造方法创建对象  UserService userService = new UserServiceImpl();
          2. 通过反射的形式创建对象可以解耦合
               Class clazz = Class.forName("com.service.impl.UserServiceImpl");
               UserService userService = (UserService)clazz.newInstance();
     */
    public static UserService getUserService() {

        UserService userService = null;
        try {                 
            Class clazz = Class.forName(env.getProperty("userService"));
            userService = (UserService) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return userService;

    }

    public static UserDAO getUserDAO(){

        UserDAO userDAO = null;
        try {
            Class clazz = Class.forName(env.getProperty("userDAO"));
            userDAO = (UserDAO) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return userDAO;

    }
}
复制代码
userDAO = com.dao.userDAO

userService = com.service.userService
复制代码

1.5、简单工厂的代码修改

    我们可以发现,上面一个工厂设计模式的代码是又臭又长,我们每创建一个新的对象,都需要重新写一个工厂类,并且代码很大一部分都是相同的,仅仅只是创建的对象不同,于是我们可以抽取出共同的代码,组成一个通用的工厂类。

public class BeanFactory{
  
    public static Object getBean(String key){
         Object ret = null;
         try {
             Class clazz = Class.forName(env.getProperty(key));
             ret = clazz.newInstance();
         } catch (Exception e) {
            e.printStackTrace();
         }
         return ret;
     }

}
复制代码

1.6、总结

    Spring本质上就是一个工厂,只不过我们在日常开发的时候使用的不是自己写的工厂,因为这个工厂的功能很少,性能很低下,Spring帮我们写好了一个大型工厂(ApplicationContext),我们只需要在一个固定的配置文件(applicationContext.xml)中进行配置即可。

二、Spring入门

2.1、Spring简介

    Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 是可以在 Java SE/EE 中使用的轻量级开源框架。

   Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是使用代码关联,而是通过配置来说明。即在 Spring 中说明对象(模块)的关系。

   Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。

2.2、Spring的优点

   Spring 是一个框架,是一个半成品的软件。由 20 个模块组成。它是一个容器管理对象,容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。他的优点主要是以下几个方面。

  1. 轻量。
  2. 面向接口编程。
  3. 面向切面编程
  4. 可以轻易集成其他优秀的框架。

2.2.1、轻量

    Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需的 jar 总共在 3M 左右。     Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar。

2.2.2、面向接口编程

    Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合。

2.2.3、面向切面编程(AOP)

    通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。     在 Spring 中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。

2.2.4、集成其他优秀的框架

    Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如 Shiro、MyBatis)等的直接支持。简化框架的使用。     Spring 像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板。不需要可以轻易的移除。

2.3、Spring的体系结构

    Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP, Aspects)、提供 JVM的代理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。

image-20210204155018332

2.4、Spring的核心API

    Spring的核心就是一个大工厂:ApplicationContext,他的作用是用于对象的创建,且可以解除耦合,他是一个接口,但是ApplicationContext是一个重量级的工厂对象占用大量的内存,所以我们不会频繁得去创建对象,一般一个应用只会创建一个工厂对象。ApplicationContext是线程安全的,可以被多线程并发访问。

    他有两种实现方式:

  1. 适用于非WEB环境的:ClassPathXmlApplication

image-20210204153022888

  1. 适用于WEB环境的:XmlApplicationContext

image-20210204153039058

2.5、Spring的案例

2.5.1、引入依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.1.5.RELEASE</version>
    </dependency>
复制代码

2.5.3、创建类型

package com.domain;

/**
 * @author Xiao_Lin
 * @date 2021/2/4 15:57
 */
public class Person {

}

复制代码

2.5.4、修改配置文件

    在applicationContext.xml的配置文件中更改配置

<!--  id属性:名字-->
<!--  class属性:需要创建对象的全限定名-->
  <bean id="person" class="com.domain.Person"/>
复制代码

2.5.5、创建对象

  /**
  * 用于测试Spring的第一个程序
  */
  @Test
  public void testSpring(){
    // 1. 获得Spring的工厂
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("/applicationContext.xml");
    // 2. 通过工厂类来获得对象
    Person person = (Person)applicationContext.getBean("person");
    System.out.println(person);
  }
复制代码

2.6、细节分析

2.6.1、名词解释

​ Spring工厂创建的对象,叫做bean或者组件(componet)

2.6.2、相关方法

//通过这种方式获得对象,就不需要强制类型转换
Person person = ctx.getBean("person", Person.class);
System.out.println("person = " + person);
        

//当前Spring的配置文件中 只能有一个<bean>标签的class是Person类型
Person person = ctx.getBean(Person.class);
System.out.println("person = " + person);
        

//获取的是 Spring工厂配置文件中所有bean标签的id值  person person1
String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
  System.out.println("beanDefinitionName = " + beanDefinitionName);
}
        

//根据类型获得Spring配置文件中对应的id值
String[] beanNamesForType = ctx.getBeanNamesForType(Person.class);
for (String id : beanNamesForType) {
  System.out.println("id = " + id);
}
        

//用于判断是否存在指定id值的bean
if (ctx.containsBeanDefinition("a")) {
  System.out.println("true = " + true);
}else{
  System.out.println("false = " + false);
}
      

//用于判断是否存在指定id(name)值的bean
if (ctx.containsBean("person")) {
  System.out.println("true = " + true);
}else{
  System.out.println("false = " + false);
}
复制代码

2.7、Spring创建对象的简易原理图

Spring

注意:反射底层调用的是无参构造函数来进行实例化对象的,即使构造方法私有了,依然可以调用进行实例化对象。

2.8、注意

    在未来开发的过程中,理论上所有的对象都是交给Spring工厂来创建,但是有一类特殊的对象——实体对象是不会交给Spring来创建的,它是交给持久层来创建的。

三、注入

3.1、什么是注入

    注入是指 Spring 创建对象的过程中,将对象依赖属性通过配置设值给该对象。

3.2、为什么需要注入

    通过编码的方式(setXxx),为成员变量进行赋值,存在耦合。

3.3、注入的方式

  1. set注入:其类必须提供对应 setter 方法。
  2. 构造器注入:利用构造器进行注入。

3.4、set注入

package com.domain;

/**
 * @author Xiao_Lin
 * @date 2021/2/4 15:57
 */
public class Person {
  private String username;
  private Integer password;


  @Override
  public String toString() {
    return "Person{" +
        "username='" + username + '\'' +
        ", password=" + password +
        '}';
  }

  public Person(String username, Integer password) {
    this.username = username;
    this.password = password;
  }

  public Person() {
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public Integer getPassword() {
    return password;
  }

  public void setPassword(Integer password) {
    this.password = password;
  }
}

复制代码
<bean id="person" class="com.domain.Person">
    <property name="username">
      <value>Xiao_Lin</value>
    </property>
    <property name="password">
      <value>123456</value>
    </property>
  </bean>
复制代码
/**
  * 用于测试注入
  */
  @Test
  public void testDI(){
    ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person person = application.getBean("person", Person.class);
    System.out.println(person);
  }
复制代码

3.4.1、set注入的原理图

    Spring通过底层调用对象属性对应的set方法完成对成员变量的赋值操作。

Spring-第 2 页

3.4.2、set注入详解

    针对不同的不同类型的成员变量,我们不可能一直是使用value标签,我们需要嵌套其他的标签,我们将成员变量可能的类型分类两大类:

  1. JDK内置类型。
  2. 用户自定义类型。

image-20200416090518713

3.4.2.1、JDK内置类型

3.4.2.1.1、String+8种基本数据类型

    都直接使用value标签即可

<property name="password">
	<value>123456</value>
</property>
复制代码
3.4.2.1.2、数组类型

    对于数组类型,我们需要在配置文件中,使用list标签,表明是数组类型,嵌套value标签来进行赋值。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
  private String[] emails;
}

复制代码
 <property name="emails">
      <list>
        <value>124@qq.com</value>
        <value>456@163.com</value>
      </list>
    </property>
复制代码
3.4.2.1.3、Set集合

    对于set集合类型,我们需要在配置文件中,使用set标签,表明是set集合类型,嵌套Set泛型中对应的标签来进行赋值。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private Set<String> tels;
}

复制代码
<property name="tels">
      <set>
        <value>123456</value>
        <value>456789</value>
        <value>13579</value>
      </set>
    </property>
复制代码

   对于set集合由于我们规范了泛型为String,她是8种基本数据类型,所以在set标签中才嵌套value标签。如果没有规定泛型或者说是规定了其他的泛型,set嵌套的标签需要根据具体的情况来具体分析。

3.4.2.1.4、List集合

    对于List集合类型,我们需要在配置文件中,使用list标签,表明是List集合类型,嵌套List泛型中对应的标签来进行赋值。

    list便签中嵌套什么标签,取决于List集合中的泛型。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private List<String> address;
}
复制代码
<property name="address">
      <list>
        <value>sz</value>
        <value>sz</value>
        <value>gz</value>
      </list>
    </property>
复制代码
3.4.2.1.5、Map集合

    对于Map集合,有一个内部类——Entry,所以我们在配置文件中需要使用的标签是用map标签来嵌套entry标签,里面是封装了一对键值对。我们使用key标签来表示键,里面嵌套键对应的标签,值要根据对应的类型来选择对应的标签。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private Map<String,String> qq;
}
复制代码
    <property name="qq">
      <map>
        <entry>
          <key><value>zs</value></key>
          <value>123456</value>
        </entry>
        <entry>
          <key><value>lisi</value></key>
          <value>456789</value>
        </entry>
      </map>
    </property>
复制代码
3.4.2.1.6、Properties集合

    Properties类似是特殊的Map,他的keyvalue都必须是String类型。

    在配置文件中,我们使用props标签,里面嵌套prop标签,一个prop就是一个键值对,键写在key属性中,值写在标签内部。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
 private Properties properties;
}
复制代码
   <property name="properties">
      <props>
        <prop key="username">admin</prop>
        <prop key="password">123456</prop>
      </props>
    </property>
复制代码

3.4.2.2、自定义类型

3.4.2.2.1、第一种注入方式
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hobby {
  private String name;
}
复制代码
    <property name="hobby">
      <bean class="com.domain.Hobby"/>
    </property>
复制代码

    我们可以发现第一种注入方式其实就是在property标签里面写一个bean标签,他的劣势也很明显:

  1. 配置文件代码冗余,我有一万个类需要引用同一个对象的时候,我同一段代码需要写一万次。
  2. 被注入的对象,被多次创建,浪费(JVM)内存资源,因为我每写一个bean标签意味着就创建一个新的对象。
3.4.2.2.2、第二种注入方式

    鉴于第一种注入方式的缺点很明显,我们就需要改进,于是就有了第二种注入方式,这种方式是将我们需要注入的对象提前先创建一份出来,谁需要谁去引用即可。

 <bean> 
	<property name="hobby">
      <ref bean="hobby"/>
    </property>
 </bean>
  <bean id="hobby" class="com.domain.Hobby">
    <property name="name">
      <value>admin</value>
    </property>
  </bean>
复制代码

3.4.3、set注入的简化写法

3.4.3.1、基于属性的简化

JDK类型注入

    我们可以使用value属性来简化value标签的值,但是只可以简化8种基本数据类型➕Stirng类型的值。

<!--以前的方式-->
<property name="name">
	<value>Xiao_Lin</value>
</property>

<!--简化后的方式-->
<property name="name" value="Xiao_Lin"/>
复制代码

用户自定义类型的注入

    我们可以使用ref属性来简化ref标签的值.

<!--以前的方式-->
<property name="hobby">
	<ref bean="hobby"/>
</property>

<!--简化后的方式-->
<property name="hobby" ref="hobby"/>
复制代码

3.4.3.2、基于p命名空间的简化

    我们可以发现,bean标签的很多值都是重复且冗余的,于是可以使用p命名空间来进行简化。

<!--内置数据类型-->
<bean id="person" class="com.domain.Person" p:username="zs" p:password="123456" />

<!--用户自定义类型-->
<bean id="hobbyBean" class="com.domain.Hobby"></bean>

<bean id="hobby" class="com.domain.Person" p:hobby-ref="hobbyBean"
复制代码

3.5、构造注入

    Spring调用构造方法,通过配置文件为成员变量赋值。如果要使用构造注入,必须提供有参的构造方法。

   构造注入使用的标签是constructor-arg标签,一个构造参数就是一对constructor-arg标签。顺序和个数都必须和构造参数一样。

   当出现构造方法重载的时候,我们可以通过控制constructor-arg的个数来进行控制。如果出现构造参数个数相同的重载的时候(如第一个构造方法是给name赋值,第二个构造方法给type赋值),我们需要用type属性来指定类型。

@ToString
@AllArgsConstructor
@NoArgsConstructor
public class Hobby {
  private String name;
  private String type;
}

复制代码
<bean id="hobbyBean" class="com.domain.Hobby">
    <constructor-arg>
      <value>running</value>
    </constructor-arg>
    <constructor-arg>
      <value>dayily</value>
    </constructor-arg>
  </bean>
复制代码
/**
  * 用于测试构造注入
  */
  @Test
  public void testDI2(){
    ClassPathXmlApplicationContext cxt =
        new ClassPathXmlApplicationContext("/applicationContext.xml");
    Hobby hobbyBean = cxt.getBean("hobbyBean", Hobby.class);
    System.out.println(hobbyBean);
  }
复制代码

3.6、注入总结

image-20200416155620897

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

4.1、控制反转(IOC)

    控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。

    简单来说控制反转就是把对于成员变量赋值的控制权,从代码中反转(转移)到Spring工厂和配置文件中完成。

    IoC 是一个概念,是一种思想,其实现方式多种多样。Spring 框架使用依赖注入(DI)实现 IoC。

4.2、依赖注入(DI)

    依赖:classA 类中含有 classB 的实例,在 classA 中调用 classB 的方法完成功能,即 classA对 classB 有依赖。

    依赖注入(Dependency Injection):当一个类需要另一个类时,就可以把另一个类作为本类的成员变量,最终通过Spring的配置文件进行注入(赋值)。简单来说就是指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。

    Spring 的依赖注入对调用者与被调用者几乎没有任何要求,完全支持对象之间依赖关系的管理。

4.3、总结

   Spring 容器是一个超级大工厂,负责创建、管理所有的 Java 对象,这些 Java 对象被称为 Bean。Spring 容器管理着容器中 Bean 之间的依赖关系,Spring 使用“依赖注入”的方式来管理 Bean 之间的依赖关系。使用 IoC 实现对象之间的解耦和。

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改