Spring学习笔记

学习b站视频《孙哥说spring5》时写的,传送门:001_简介_哔哩哔哩_bilibili

第一章 引言

1 大纲

Spring学习笔记_第1张图片

  1. 核心、基石,Spring所有的特性都由工厂衍生而来

  2. 面试重点,重点:Spring动态代理的底层实现

  3. Spring通过持久层整合,与现有的持久化方案集成

  4. 重点内容
  5. 与strus2或MVC整合

  6. Spring开发的主流,Springboot开发的前置性知识

2 什么是Spring

1、Spring是一个轻量级的JavaEE解决方案,整合了众多优秀的设计模式

  • 轻量级:

    1. 对运行环境没有额外要求

    2. 代码移植性高

  • JavaEE解决方案

    1. Spring可以解决JavaEE开发每一层的问题

  • 封装和整合了诸多设计模式

    1. 工厂

    2. 代理

    3. 模板

    4. 策略

3 设计模式

什么是设计模式?

  1. 广义:面向对象设计中,解决特定问题的经典代码

  2. 狭义:GOF四人帮定义的23种设计模式:工厂、适配器、装饰器、门面、代理······。具体需要根据项目和代码特点合理的选择设计模式,不一定全用。

4 工厂设计模式

4.1 什么是工厂设计模式?

  1. 概念:通过工厂类创建对象,习惯上直接new一个对象,但Spring不提倡,推荐使用工厂类

  2. 好处:解耦合,耦合:代码见的强关联关系,一方改变将影响到另一方,耦合度高不利于代码维护。

简单工厂实现:通过工厂类中共提供的工厂方法创建对象

public class BeanFactory {
    public static UserServiceImpl getUserService(){
        return new UserServiceImpl();
    }
}
//调用
class SingleApplicationTests {
​
    @Test
    void test() {
        UserService userService = BeanFatory.getUserService();
        userService.resetPassword(1);
    }
​
}

4.2 简单工厂设计:

对象创建的方式(工厂反射):

  1. 调用构造方法创建对象,就是直接new,缺点是耦合度高。上述代码中虽然用到了工厂方法,但工厂方法中依然是通过调用构造方法来创建对象,没有达到解耦合的目的。

  2. 通过反射的形式创建对象,能够达到解耦合的目的。创建方式分为两步:

    • 通过Class.forName(String name)方法获取类对象,方法的入参为全限定名(全限定名指包名加类名)。

    • 通过类对象的newInstance方法获取类对象,该方法的返回值是Object,因此需要经过参数类型转换。

    • //例:反射存在异常,因此在使用时需要将异常抛出
      public class BeanFatory {
          public static UserService getUserService(){
              UserService userService = null;
              try {
                  Class aClass = Class.forName("com.example.spring.service.impl.UserServiceImpl");
                  userService = (UserService) aClass.newInstance();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              } catch (InstantiationException e) {
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              }
              return userService;
          }
      }

    • Class.forName(String name)方法传入的是类的全限定名,一旦类名更改,则该方法的入参也要修改,因此需要将耦合字符串也处理掉(通过配置文件)

      ## properties文件的读取思路
      ## 通过properties类型的集合存储properties文件的内容
      ## properties类型的集合的特点:特殊的Map,都是key-value结构,特殊之处在于properties的key和value都必须是字符串类型
      ## 将properties的键和值封装到properties集合当中
      ## 调用properties的getProperty()方法,以userService为键,就可以获取到值,至此就完成了文件内容的读取
      userService = com.example.spring.service.impl.UserServiceImpl
      调用:
      
      //在工厂类中创建properties的集合
          public static Properties properties = new Properties();
          //使用静态代码块的方式来读取properties文件的内容
          static {
              try {
                  //首先获取IO的输入流
                  InputStream inputStream = BeanFatory.class.getResourceAsStream("/application.properties");
                  //将文件的内容封装到 properties集合中
                  properties.load(inputStream);
                  
                  inputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      //Class.forName();方法的参数改为userService

      完整的BeanFatory代码如下:

      public class BeanFatory {
          //在工厂类中创建properties的集合
          public static Properties properties = new Properties();
          //使用静态代码块的方式来读取properties文件的内容
          static {
              try {
                  //首先获取IO的输入流
                  InputStream inputStream = BeanFatory.class.getResourceAsStream("/application.properties");
                  //将文件的内容封装到 properties集合中
                  properties.load(inputStream);
                  inputStream.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
      ​
          public static UserService getUserService(){
              UserService userService = null;
              try {
                  Class aClass = Class.forName(properties.getProperty("userService"));
                  userService = (UserService) aClass.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 aClass = Class.forName(properties.getProperty("userDao"));
                  userService = (UserService) aClass.newInstance();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              } catch (InstantiationException e) {
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              }
              return userDao;
          }
      }

4.3 通用工厂的设计:

通过4.2的学习可以发现,通过简单工厂的工厂方法确实可以达到解耦合的目的,但需要为每个对象都提供一个对象方法,工厂方法中大部分代码都是重复的,因此会造成代码冗余。可通过设计通用工厂解决上述的问题。

通用工厂中的工厂方法设计如下:

  • 在通用工厂中设计一个通用的工厂方法来创建对象,方法的返回值为Object,入参为String类型过的key,调用工厂方法时需要key,将结果的数据类型强制转换为需要的数据类型即可。

  • 方法如下 :

    //使用通用工厂可以创建一切想要创建的对象(前提是先在配置文件中配置好)
    public static Object getObject(String key){
            Object object = null;
            try {
                Class aClass = Class.forName(properties.getProperty(key));
                object = aClass.newInstance();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return object;
        }

  • 调用方式如下:

    void test() {
            UserService userService = (UserService) BeanFatory.getObject("userService");
            userService.getUser();
        }

4.4 通用工厂的使用方式

使用步骤:

  1. 定义要创建的类

  2. 在配置文件中配置要创建的类,读取配置告知工厂要创建的类型

  3. 调用工厂方法,通过key获取类对象,将结果强制转换成自己要创建的类型(类)

5 总结

  1. 学习了Spring的基本概念

  2. 了解了设计模式的基本概念,诸侯中学习了工厂设计模式

  3. Spring的本质就是工厂,Spring提供了一个名为ApplicationContext的工厂及相应的配置文件applicationContext.xml,实现的思路与4.3相同但更加强大。

第二章 创建Spring项目

1 软件版本

  1. jdk:1.8+(17)

  2. Maven:3.5+(3.6.3)

  3. IntelliJ IDEA:2018+(2021.2.2)

  4. SpringFramework:5+(5.1.4)

2 环境搭建

环境搭建主要有以下两点

  1. Spring的jar包

    Spring是通过基于Maven来管理jar包,只需要设置好pom的依赖,依赖是通过Maven的中心仓库来查找Spring相关jar包的坐标来进行设置。

    Maven仓库的地址: Maven Repository: Search/Browse/Explore (mvnrepository.com)

  2. Spring的配置文件

    • 配置文件的放置位置:任意位置

    • 配置文件命名:随意命名,Spring建议使用applicationContext.xml(只是建议,不是硬性规定)

    • 配置文件的命名和位置随意,但需要设置配置文件所处的路径

    • 配置文件的创建:右击resources目录new -> XMLConfiguration File -> Spring config

3 Spring的核心API

核心API即核心类,框架最核心的类型

ApplicationContext工厂

  • Spring提供的ApplicationContext工厂主要用于对象的创建,能达到解耦合的目的。

  • ApplicationContext接口类型

    1. 主要为了屏蔽实现的差异,由于工厂应用到的开发场景不用,其特点也略有不同,将工厂定义成接口可以屏蔽工厂之间具体的差异。

    2. Spring提供两种ApplicationContext工厂的实现,分别是非web环境的ClassPathXmlApplicationContext(主要指main或junit单元测试,不需要启动服务器,所以是非web环境)和web环境的XmlWebApplicationContext

  • 重量级资源

    1. ApplicationContext工厂的对象会占用大量的内存,

    2. 不要频繁的创建对象,一个应用只创建一个对象。

    3. ApplicationContext工厂一定是线程安全的,可以被多线程并发访问。

4 程序开发

回顾工厂的使用步骤:

  1. 创建想要调用的类(想调用先得有)。

  2. applicatiionContext.xml配置文件中配置想要调用的类,key-value形式,key必须唯一,值为类的全限定名具体的创建方式为在标签内部写标签,北部设置两个属性,分别是id和class,id存放key,class存放value。

  3. 通过Spring提供的ApplicationContext工厂类可以获得对象。测试时需要选择对应的工厂子类来创建工厂,在使用工厂子类时需要设置配置文件的全限定名。

创建实例如下:

  1. 用UserServiceImpl测试,已有无需创建。

  2. applicationContext.xml文件配置:

    
    
    ​
        
  3. 方法中使用工厂创建对象:

    void contextLoads() {
            //配置文件在resources文件夹的根目录下,所以全限定名为/applicationContext.xml
            ApplicationContext app =new ClassPathXmlApplicationContext("/applicationContext.xml");
            //使用工厂创建对象
            UserService user = (UserService) app.getBean("User");
            //调用get方法测试
            user.get();
        }

5 细节分析

  • 名词解释

    1. Spring工厂创建的对象叫做bean或者组件(componet)。

  • Spring工厂的相关方法:

    void test() {
            //配置文件在resources文件夹的根目录下,所以全限定名为/applicationContext.xml
            ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
            //getbean的第一种重构形式,通过指定id来创建对象
            UserService user = (UserService) app.getBean("User");
            
            //getbean的第二种重构形式,通过指定id和类型来获取对象,省略强制转换
            UserService user1 = app.getBean("User", UserService.class);
            
            //getbean的第三种重构形式,直接传入class对象,使用此方法需确保在配置文件中只能有一个标签的值为class
            UserService user2 = app.getBean( UserService.class);
            
            //获取工厂配置文件中所有标签id的值,返回值的类型为数组
            String[] beanDefinitionNames = app.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                System.out.println(beanDefinitionName);
            }
            
            //获取工厂配置文件中所有class值为UserServiceImpl的标签id的值,返回值的类型为数组
            String[] beanNamesForType = app.getBeanNamesForType(UserServiceImpl.class);
            for (String s : beanNamesForType) {
                System.out.println(s);
            }
            //判断是否存在指定id值的bean,只能通过id来判断
            boolean b = app.containsBeanDefinition("U");
            System.out.println(b);
            
            //判断是否存在指定id值的bean,可以通过id和name来判断
            boolean u = app.containsBean("U");
            System.out.println(u);
    ​
            //调用方法测试
            //user.get();
        }
  • 配置文件需要注意的细节

    1. class配置

      1. 配置文件中创建标签只配置class属性是可以的,Spring认可这种语法

      2. 标签只配置class而没有配置id,Spring会配置一个默认的id值

      3. 上述配置的应用场景:这个只需使用一次则不用设置id,如果这个使用了多次或者被其他的引用,则需要设置id值。

    2. name属性

      1. name属性为标签的别名,使用方式与id类似

      2. 相同:调用getbean()方法创建对象时,可以使用name创建,创建标签是可以用name来取代id

      3. 区别:

        标签的id只能有一个值,但是name可以有多个值。

        xmlid属性的值的命名必须以字母为开头,后面可以接字母、数字、下划线、连字符,name属性没有命名要求,因此name属性通常应用在特殊命名的场景

        如今,xmlid命名限制已经没有了

6 Spring工厂的源码分析

执行顺序:

  1. 通过ClassPathXmlApplicationContext工厂读取applicationContext.xml配置文件信息。

  2. 获得标签的相关属性id和class的值,通过反射创建对象,将标签的class属性的值作为参数传递到Class.forName()方法中去,调用newInstance方法即可创建成功。

  3. 通过反射创建对象时,底层同样会调用该对象的无参构造方法,通过因此反射创建对象与new等效。

  4. 即使对象的构造方法是私有的,Spring依旧可以调用,因为反射可以调用类型的私有的属性、方法及构造方法

7 思考

    问:什么情况下会用工厂来创建对象?
    答:除实体类以外,所有的对象都由工厂创建,实体类对象本身意义不大,其对应的数据库的数据更为重要,获取这些数据需要操作数据库,因此实体类由持久层创建

第三章 spring整合日志框架

1 为什么要整合日志框架

Spring整合日志框架之后,日志可以在控制台输出Spring执行过程中的一些重要信息,
优点利于了解Spring的执行过程,便于开发人员对程序进行调试。

2 Spring如何整合日志框架

默认: 
Spring的早期版本1.X、2.X、3.X都是与commons-logging整合
Spring4.X、5.X两个版本默认与logback、log4j2整合
​
手动整合log4j
1.引入相关jar包
2.引入log4j.properties配置文件

pom依赖:



    org.slf4j
    slf4j-log4j12
    1.7.26



    log4j
    log4j
    1.2.17

配置文件(此处使用yaml格式):

log4j:
  rootLogger: debug.console
  appender:
    console: org.apache.log4j.ConsoleAppender
      target: System.out
      layout: org.apache.log4j.PatternLayout
        ConversionPattern: %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

第四章 注入

1 什么是注入

通过Spring工厂及配置文件为对象的成员变量赋值

1.1 为什么需要注入

通过编码的方式为成员变量赋值存在耦合,利如set。

1.2 注入的步骤

  • 类为成员变量提供get/set方法。

  • 配置Spring的配置文件

    
        
            56
        
    

1.3 注入的好处

解耦合!

2 注入的原理分析(简易)

1 注入的两点要求

  1. 类为成员变量提供get/set方法。

  2. 配置Spring的配置文件,在标签中创建标签来调用成员变量的set方法,其name属性用来指定成员变量,内部创建标签用来设置成员变量的值。

2 注入的原理

  1. 读取标签,获取id和要创建的类型。通过反射创建好对象

  2. 解析property标签,读取到类拥有的成员变量,进行赋值操作,等效于通过对象调用了set方法,再通过标签来读取要赋的值,至此,赋值操作完成。

Spring通过底层调用对象成员变量对应的set方法来为成员变量赋值,英雌也被称之为色图注入

你可能感兴趣的:(java,spring)