Spring源码解读:引言、第一个Spring程序、使用日志框架Logback

参考自B站UP主视频《孙哥说Spring5》

文章目录

  • 什么是Spring Framework?
  • EJB存在的问题
  • 什么是Spring?
  • 设计模式简介
  • Spring的核心:工厂模式
  • 第一个 Spring 程序
  • Spring 的核心API
  • 程序开发应用
  • 配置文件中需要注意的细节
  • Spring5.x 整合 logback框架
    • logback简介
    • 与log4j的区别
  • 导入依赖
    • 配置文件

什么是Spring Framework?

Spring源码解读:引言、第一个Spring程序、使用日志框架Logback_第1张图片

EJB存在的问题

  • 运行环境苛刻
  • 代码可移植性差(只要运行的服务器改变了,代码就要随之而改变,且服务器因为是闭源收费的,对服务器就无法进行深度定制)

所以也就称为重量级框架

什么是Spring?

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

  • 轻量级:对于运行环境是没有额外的要求

  • 代码移植性高:不需要实现额外的接口(EJB代码需要专门实现服务器的接口)

  • JavaEE解决方案:有众多产品可以整合进入(SpringMVC、Mybatis等)
    Spring源码解读:引言、第一个Spring程序、使用日志框架Logback_第2张图片

  • 合理运用了优秀的设计模式

1. 工厂模式(核心)
2. 代理模式
3. 模板模式
4. 策略模式

设计模式简介

  1. 广义概念:面向对象设计中,解决特定问题的经典代码
  2. 狭义概念:GOF4人帮定义的23种设计模式:工厂、适配器、装饰器、外观、代理、单例等

Spring的核心:工厂模式

关于工厂模式,可以查看之前写的一篇更加详细的笔记:让代码来告诉你什么叫工厂模式

#---------------- applicationContext.properties 文件 --------------------
# 在读取配置文件的时候,是使用 一个 Properties  集合来存储 Properties文件内容
# Properties是一种特殊Map:key要是String类型的 value也是String类型的
# 例如下面这两行配置,我们可以在代码中使用key获得value Properties.getProperties("userService"); 
# 当有一个新的类要使用的话,只需要改配置文件中的内容即可,也就是实现了解耦

userService = com.rick.serviceImpl.UserServiceImpl
userDao = com.rick.daoImpl.UserDaoImple

通过Bean工厂来获得一个类

----------------------------------BeanFactory.java-------------------------------------
package com.rick.factory;

import com.rick.dao.UserDao;
import com.rick.daoImpl.UserDaoImpl;
import com.rick.service.UserService;

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

import static java.lang.Class.*;

/**
 * 工厂类
 * @author Rick
 */
public class BeanFactory {
    private static Properties env = new Properties();
    static{
    	// 静态代码块,在系统启动的时候就加载配置文件
        try {
            //获得IO输入流,读取文件
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //文件中的内容封装进 Properties集合
            env.load(inputStream);
            // 关闭输入流
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /*
        对象的创建方式:
            1、直接通过调用构造方法 UserService userService = new UserService();
            2、通过反射的形式,创建对象,解耦合 getInstance()
     */
    public static UserService getUserService(){
        //return new UserServiceImpl();
        UserService userService = null;
        try {
        	// 获得Class模板
            Class clazz = Class.forName(env.getProperty("userService"));
            // 通过模板创建
            userService = (UserService) clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return userService;
    }

    public static UserDaoImpl getUserDao(){
        UserDaoImpl userDaoImpl = null;
        try {
            Class clazz  = Class.forName(env.getProperty("userDao"));
            userDaoImpl = (UserDaoImpl)clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return userDaoImpl;
    }

}
-------------------------------UserService.java--------------------------
package com.rick.service;

import com.rick.domain.User;

/**
 * 接口
 * @author Rick
 */
public interface UserService {
    public void saveUser(User user);
    public void querryUserByNameAndPassword(User user);
}
------------------------------UserServiceImpl.java--------------------------------------
package com.rick.serviceImpl;

import com.rick.dao.UserDao;
import com.rick.factory.BeanFactory;
import com.rick.service.UserService;
import com.rick.domain.User;

/**
 * 实现类
 * @author Rick
 */
public class UserServiceImpl implements UserService {

//    UserDao userDao = new UserDaoImpl(); // 这样对于两个类的耦合性很高,不利于代码的维护
    UserDao userDao = BeanFactory.getUserDao(); // 使用 Bean工厂 来获得 UserDao,能够降低类之间的耦合性

    @Override
    public void saveUser(User user) {
        userDao.saveUser(user);
    }

    @Override
    public void querryUserByNameAndPassword(User user) {
        userDao.querryUserByNameAndPassword(user);
    }
}
------------------------------------------------UserDao.java-------------------------------
package com.rick.dao;

import com.rick.domain.User;

/**
 * 接口
 * @author Rick
 */
public interface UserDao {
    public void saveUser(User user);
    public void querryUserByNameAndPassword(User user);
}
-----------------------------------------UserDaoImpl.java------------------------------
package com.rick.daoImpl;

import com.rick.dao.UserDao;
import com.rick.domain.User;

/**
 * 实现类
 * @author Rick
 */
public class UserDaoImpl implements UserDao {

    @Override
    public void saveUser(User user) {
        System.out.println("saving" + user.toString());
    }

    @Override
    public void querryUserByNameAndPassword(User user) {
        System.out.println(user.toString());
    }
}
---------------------------------AppTest.java------------------------------------------
package com.ric

import static org.junit.Assert.assertTrue;

import com.rick.service.UserService;
import com.rick.factory.BeanFactory;
import org.junit.Test;

// 测试类
public class AppTest 
{
    @Test
    public void test1() throws Exception{
        // 如果有一个新的实现类 UserServiceImplNew 就需要改动代码来使用新的类
        //UserService userService = new UserServiceImplNew();
        //UserService userService = new UserServiceImpl();
        // 通过配置文件来集中管理
        UserService userService = BeanFactory.getUserService(); // 通过工厂类获得类
        User user = new User();
        userService.querryUserByNameAndPassword(user);
    }
}

将上面的 BeanFactory 中的代码优化一下,使其能够适合更多的类去使用

package com.rick.factory;

import com.rick.dao.UserDao;
import com.rick.daoImpl.UserDaoImpl;
import com.rick.service.UserService;

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

import static java.lang.Class.*;

/**
 * 通用的工厂类
 * @author Rick
 */
public class BeanFactory {
    private static Properties env = new Properties();
    static{
        try {
            //获得IO输入流
            InputStream inputStream = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //文件内容分装
            env.load(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /*
     * key : 配置文件中的 key
     * 通过传入 key 来获得指定的全限定类名,从而获得类
     * 这样就可以实现,通过 BeanFactory 获得任意自己想要的类名
     */
    public static Object getBean(String key){
        Object obj = null;
        Class clazz = null;
        try {
            clazz = Class.forName(env.getProperty("key"));
            obj = clazz.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return obj;
    }
}

第一个 Spring 程序

软件版本

  • JDK: 8
  • Maven: 3.6.3
  • IDEA: 2020.1.1/
  • SpringFranework: 5.1.15.RELEASE
  • 官方网站:www.spring.io

环境搭建


<dependency>
    <groupId>org.springframeworkgroupId>
	<artifactId>spring-contextartifactId>
    <version>5.1.15.RELEASEversion>
dependency>
  1. 配置文件的放置位置:任意位置 没有硬性要求
  2. 配置文件命名:没有硬性要求 建议:applicationContext.xml
  3. 注意:日后应用Spring框架时,需要进行配置文件路径的设置。(IDEA中配置即可)

Spring 的核心API

ApplicationContext

  • 作用:Spring提供的ApplicationContext工厂,用于对象的创建
  • 好处:解耦合
  • 特性:
ApplicationContext 为 【接口类型】
	接口:可屏蔽实现的差异
	提供了两种接口的实现
		非web环境:ClassPathXmlApplicationContext
		web环境:XmlWebApplicationContext

ApplicationContext 为 【重量级资源】--> ApplicationContext工厂的对象会【占用大量内存】
	不会频繁的创建对象:(单例)
		一个应用只会创建一个工厂对象
	ApplicationContext工厂:
		一定是【线程安全】的(多线程并发访问)

Spring源码解读:引言、第一个Spring程序、使用日志框架Logback_第3张图片

程序开发应用

  1. 创建类型
  2. 配置文件的配置:applicationContext.xml
  3. 通过工厂类,获得对象:ApplicationContextClassPathXmlApplication

实体类

//-----------------------------------Person.java
package com.rick.domain;
/**
 * @author Rick
 * 通过工厂创建
 */
public class Person {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", age=" + age +
                ", dog=" + dog +
                '}';
    }
}

创建配置文件



<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="person" class="com.rick.domain.Person"/>

beans>

测试获得类

//--------------------------------Test.java
package com.rick;

import com.rick.domain.Person;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Unit test for simple App.
 */
public class AppTest 
    @Test
    public void test1(){
        //获得Spring的工厂,并传入配置文件的路径
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext.xml");	
    	/**
		 *需要强制转换类型
         *Person user = (Person) ctx.getBean("person");
    	 *不需要强制转换类型 但是要求只能由一个Bean标签是Person类型
    	 *Person person = ctx.getBean(Person.class);
 		 */
    	// 通过传入 Class 模板来获得,可以避免强制类型转换
    	Person user = ctx.getBean("person",Person.class);
        System.out.println(person.toString());

		//获取 Spring工厂配置文件 中所有 bean标签 的 id值,但 不能判断 name值
		String[] beanDefinitionNames = ctx.getBeanDefinitionNames(Person.class);
		//根据 类型 获得 Spring配置文件 中对应的 id值,也 能判断 name值
		String[] beanNamesForType = ctx.getBeanNamesFOrType(Person.class);
		//判断是否存在指定的 id值 的 bean
		boolean isExist = ctx.containBeanDefinition("a");
		//判断是否存在指定的 id值 的 bean
		boolean isExist = ctx.containBean("a");
    }
}

配置文件中需要注意的细节

只配置class属性:

  • 那上述这种配置,有无id值?其实是有的,会自动生成:com.rick.domain.Person#0
  • 应用场景:如果这个bean只用一次则可以省略id值,反之若需要多次使用则必须设置id值

name属性

  • 作用:用于在Spring的配置文件中,为 bean对象 定义别名(小名)【id属性是大名
  • 区别:
    • 别名可以定义多个name="p,p1,p2",但 id属性只能有一个值id="person"
    • XML的 id属性值 有命名要求:必须以字母开头,由字母、数字、连字符、下划线组成,不能以特殊字符,如:/person或者2person
    • XML的 name属性值 无命名要求,name属性一般应用在特殊的场景下
  • XML发展到今天,id属性的限制已不存在
// 引入 name 属性之后,这两者就有一定的区别的
// 不能识别 name
boolean isExist = ctx.containBeanDefinition("a");
// 可以识别 name
boolean isExist = ctx.containBean("a");

对Spring工厂底层实现原理的简单分析

1. 通过 ClassPathXmlApplicationContext() 或 XmlWebApplicationContext() 工厂读取配置文件 applicationContext.xml
2. 获得bean标签的相关信息(id的值,class的值),通过反射创建对于的对象
		Classs<?> clazz = Class.forName(Person.class);
		Person person = clazz.newInstance();
3. 反射创建对象,底层也是会调用对象自己的构造方法
	    Classs<?> clazz = Class.forName(Person.class);
		Person person = clazz.newInstance();
	等效于
		Person person = new Person();
  • 未来在开发过程中,是不是所有的对象,都会交给Spring工厂来创建?
    • 理论上,是的,但也有特例
    • 实体对象(entity)是不会交给Spring创建,实体对象是由持久层框架进行创建的

Spring5.x 整合 logback框架

整合日志框架后,日志框架就可以在控制台中,输出执行过程中的一些重要的信息,可以便于我们了解运行过程,也可以利于程序的调试

Spring 1、2、3早期都是使用 commin-logging.jar,Spring 5.x 默认整合的日志框架logback log4j2

logback简介

logback是由log4j创始人设计的又一个开源日志组件

logback当前分成三个模块:

  • logback-core:是其它两个模块的基础模块。
  • logback- classic:是log4j的一个 改良版本,且完整实现Slf4j API,使你可以很方便地更换成其它日志系统如log4jJDK14 Logging
  • logback-access:与Servlet容器集成提供通过Http来访问日志的功能

logback-corelogback-classic:此外logback-classic完整实现Slf4J API使你可以很方便地更换成其它日志系统如log4jJDK14 Logging

与log4j的区别

1. 更快的实现 Logback的内核重写了,在一些关键执行路径上性能提升10倍以上。而且logback不仅性能提升了,初始化内存加载也更小了。

2. 非常充分的测试 Logback经过了几年,数不清小时的测试。Logback的测试完全不同级别的。这是简单重要的原因选择logback而不是log4j。

3. Logback-classic非常自然实现了SLF4j Logback-classic实现了 SLF4j。在使用SLF4j中,你都感觉不到logback-classic。而且因为logback-classic非常自然地实现了SLF4J, 所 以切换到log4j或者其他,非常容易,只需要提供成另一个jar包就OK,根本不需要去动那些通过SLF4JAPI实现的代码。

4. 非常充分的文档 官方网站有两百多页的文档。

5. 自动重新加载配置文件 当配置文件修改了,Logback-classic能自动重新加载配置文件。扫描过程快且安全,它并不需要另外创建一个扫描线程。这个技术充分保证了应用程序能跑得很欢在JEE环境里面。

6. Lilith Lilith是log事件的观察者,和log4j的chainsaw类似。而lilith还能处理大数量的log数据 。

7. 谨慎的模式和非常友好的恢复 在谨慎模式下,多个FileAppender实例跑在多个JVM下,能 够安全地写道同一个日志文件。RollingFileAppender会有些限制。Logback的FileAppender和它的子类包括 RollingFileAppender能够非常友好地从I/O异常中恢复。

8. 配置文件可以处理不同的情况 开发人员经常需要判断不同的Logback配置文件在不同的环境下(开发,测试,生产)。而这些配置文件仅仅只有一些很小的不同,可以通过,和来实现,这样一个配置文件就可以适应多个环境。

9. Filters(过滤器) 有些时候,需要诊断一个问题,需要打出日志。在log4j,只有降低日志级别,不过这样会打出大量的日志,会影响应用性能。在Logback,你可以继续 保持那个日志级别而除掉某种特殊情况,如alice这个用户登录,她的日志将打在DEBUG级别而其他用户可以继续打在WARN级别。要实现这个功能只需 加4行XML配置。可以参考MDCFIlter 。

10. SiftingAppender(一个非常多功能的Appender) 它可以用来分割日志文件根据任何一个给定的运行参数。如,SiftingAppender能够区别日志事件跟进用户的Session,然后每个用户会有一个日志文件。

11. 自动压缩已经打出来的log RollingFileAppender在产生新文件的时候,会自动压缩已经打出来的日志文件。压缩是个异步过程,所以甚至对于大的日志文件,在压缩过程中应用不会受任何影响。

12. 堆栈树带有包版本 Logback在打出堆栈树日志时,会带上包的数据。

13. 自动去除旧的日志文件 通过设置TimeBasedRollingPolicy或者SizeAndTimeBasedFNATP的maxHistory属性,你可以控制已经产生日志文件的最大数量。如果设置maxHistory 12,那那些log文件超过12个月的都会被自动移除。

总结:logback 比 log4j 优秀,可以取代之。

导入依赖


<dependency>
	<groupId>ch.qos.logbackgroupId>
    <artifactId>logback-coreartifactId>
    <version>1.2.3version>
 dependency>
 <dependency>
    <groupId>ch.qos.logbackgroupId>
    <artifactId>logback-classicartifactId>
    <version>1.2.3version>
dependency>
<dependency>
     <groupId>ch.qos.logbackgroupId>
     <artifactId>logback-accessartifactId>
     <version>1.2.3version>
dependency>


配置文件

放在 resources 文件夹根目录下





<configuration scan="true" scanPeriod="60 seconds" debug="false">
    
    <property name="LOG_PATH" value="${catalina.base}/logs"/>
    
    <property name="PROJECT_NAME" value="spring-starter"/>

    
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %level ${PROJECT_NAME} [%thread] [%logger{50}:%line] %msg%npattern>
            <charset>utf-8charset>
        encoder>
    appender>

    
    <appender name="FILE-ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${LOG_PATH}/${PROJECT_NAME}/${PROJECT_NAME}.log.error.logfile>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            
            <fileNamePattern>${LOG_PATH}/${PROJECT_NAME}/log-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            
            <maxFileSize>10MBmaxFileSize>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        
        <append>trueappend>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %level ${PROJECT_NAME} [%thread] [%logger{50}:%line] %msg%npattern>
            <charset>utf-8charset>
        encoder>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERRORlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    <appender name="FILE-WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${LOG_PATH}/${PROJECT_NAME}/${PROJECT_NAME}.log.warn.logfile>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            
            <fileNamePattern>${LOG_PATH}/${PROJECT_NAME}/log-warn-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            
            <maxFileSize>10MBmaxFileSize>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        
        <append>trueappend>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %level ${PROJECT_NAME} [%thread] [%logger{50}:%line] %msg%npattern>
            <charset>utf-8charset>
        encoder>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>WARNlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    <appender name="FILE-INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${LOG_PATH}/${PROJECT_NAME}/${PROJECT_NAME}.log.info.logfile>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            
            <fileNamePattern>${LOG_PATH}/${PROJECT_NAME}/log-info-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            
            <maxFileSize>10MBmaxFileSize>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        
        <append>trueappend>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %level ${PROJECT_NAME} [%thread] [%logger{50}:%line] %msg%npattern>
            <charset>utf-8charset>
        encoder>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>INFOlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    <appender name="FILE-DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${LOG_PATH}/${PROJECT_NAME}/${PROJECT_NAME}.log.debug.logfile>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            
            <fileNamePattern>${LOG_PATH}/${PROJECT_NAME}/log-debug-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            
            <maxFileSize>10MBmaxFileSize>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        
        <append>trueappend>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %level ${PROJECT_NAME} [%thread] [%logger{50}:%line] %msg%npattern>
            <charset>utf-8charset>
        encoder>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>DEBUGlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    
    <appender name="OTHER" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>WARNlevel>
        filter>
        
        <File>${LOG_PATH}/${PROJECT_NAME}/${PROJECT_NAME}.log.other.logFile>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            
            <fileNamePattern>${LOG_PATH}/${PROJECT_NAME}/log-other-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            
            <maxFileSize>35MBmaxFileSize>
            
            <maxHistory>30maxHistory>
        rollingPolicy>
        
        <append>trueappend>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %level ${PROJECT_NAME} [%thread] [%logger{50}:%line] %msg%npattern>
            <charset>utf-8charset>
        encoder>
    appender>

    
    <logger name="com.tao" level="INFO" additivity="false">
        <appender-ref ref="FILE-ERROR" />
        <appender-ref ref="FILE-WARN" />
        <appender-ref ref="FILE-INFO" />
        <appender-ref ref="FILE-DEBUG" />
        
        <appender-ref ref="STDOUT" />
    logger>

    
    <root level="WARN">
        <appender-ref ref="OTHER" />
    root>
configuration>

你可能感兴趣的:(深入理解Spring)