【单体应用】04 基础框架入门

Spring

Spring 简介

Spring 的主要作用就是为了代码“解耦”,降低代码间的耦合度。

根据功能的不同,可以将一个系统中的代码分为主业务逻辑系统级业务逻辑两类。他们各自具有鲜明的特点:主业务逻辑间逻辑联系紧密,有具体的专业业务场景应用,复用性相对较低;系统级业务相对功能独立,没有具体的专业业务应用场景,主要是为主业务提供系统级服务,如日志、安全、事务等,复用性强。

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

Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。简单来说,Spring 是一个分层的 Jave SE/EE full-stack(一站式)轻量级开源框架。


Spring 体系结构

体系结构

Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integeration)、Web、面向切面编程(AOP,Aspects)、应用服务器设备管理(Instrumentation)、消息发送(Messaging)、核心容器(Core Container)和测试(Test)。


Spring 的特点

非侵入式

所谓非侵入式是指,Spring 框架的 API 不会在业务逻辑上出现,即业务逻辑是 POJO。由于业务逻辑中没有 Spring 的 API,所以业务逻辑可以从 Spring 框架快速的移植到其他框架,即与环境无关。

容器

Spring 作为一个容器,可以管理对象的生命周期、对象与对象之间的依赖关系。可以通过配置文件,来定义对象,以及设置与其他对象的依赖关系。

IoC

控制反转 (Inversion of Control),即创建被调用者的实例不是由调用者完成,而是由 Spring 容器完成,并注入调用者。

当应用了 IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。即,不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。

AOP

面向切面编程(AOP,Aspect Orient Programming),是一种编程思想,是面向对象 OOP 的补充。很多框架都实现了对 AOP 编程思想的实现。Spring 也提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如日志和事务管理)进行开发。应用对象只实现它们应该做的–完成业务逻辑–仅此而已。它们并不负责其它的系统级关注点,例如日志或事务支持。

我们可以把日志、安全、事务管理等服务理解成一个“切面”,那么以前这些服务一直是直接写在业务逻辑的代码当中的,这有两点不好:首先业务逻辑不纯净;其次这些服务被很多业务逻辑反复使用,完全可以剥离出来做到复用。那么 AOP 就是这些问题的解决方案,可以把这些服务剥离出来形成一个“切面”,以期复用,然后将“切面”动态的“织入”到业务逻辑中,让业务逻辑能够享受此“切面”的服务。


Spring 与 IoC

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

IoC 是一个概念,是一种思想,其实现方式多种多样。当前比较流行的实现方式有两种:依赖注入和依赖查找。依赖注入方式应用更为广泛。

  • 依赖查找:Dependency Lookup,DL,容器提供回调接口和上下文环境给组件,程序代码则需要提供具体的查找方式。比较典型的是依赖于 JNDI 系统的查找。
  • 依赖注入:Dependency Injection,DI,程序代码不做定位查询,这些工作由容器自行完成。

依赖注入DI是指程序运行过程中,若需要调用另一个对象协助是,无须再代码中创建被调用者,而是依赖于外部容器,有外部容器创建后传递给程序。

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

依赖注入是目前最优秀的解耦方式。依赖注入让 Spring 的 Bean 之间以配置文件的方式组织在一起,而不是以硬编码的方式耦合在一起的。


第一个 Spring 应用程序

POM

创建一个工程名为hello-spring的项目,pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>

    <groupId>com.yyh</groupId>
    <artifactId>hello-spring</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.17.RELEASE</version>
        </dependency>
    </dependencies>
</project>

主要增加了org.springframework:spring-context依赖

创建接口与实现

创建 UserService 接口

package com.yyh.hello.spring.service;

public interface UserService {
    public void sayHi();
}

创建 UserServiceImpl 实现

package com.yyh.hello.spring.service.impl;

public class UserService implements UserService {
     public void sayHi() {
         System.our.println("Hello Spring");
         }
}
创建 Spring 配置文件

src/main/resources 目录下创建 spring-context.xml 配置文件,从文件开始类的实例化工作交给 Spring 容器(IoC),配置文件如下:

<?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="userService" class="com.yyh.hello.spring.service.impl.UserServiceImpl"/>
</beans>
  • <bean / >:用于定于一个实例对象。一个实例对应一个 bean 元素。
  • id :该属性是 Bean 实例的唯一标识,程序通过 id 属性访问 Bean ,Bean 与 Bean 间的依赖关系也是通过 id 属性关联的。
  • class:指定该 Bean 所属的类,注意这里只能是类,不能是接口。
测试Spring IoC

创建一个 MyTest 测试类,测试对象是否能够通过 Sprign 来创建,代码如下:

package com.yyh.hello.spring;

import com.funtl.hello.spring.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {

    public static void main(String[] args) {
        // 获取 Spring 容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");

        // 从 Spring 容器中获取对象
        UserService userService = (UserService) applicationContext.getBean("userService");
        userService.sayHi();
    }
}

JUnit 单元测试

JUnit 简介

JUnit 是用于编写和运行可重复的自动化测试的开源测试框架,这样可以保证我们的代码按预期工作。JUnit 可广泛用于工作和作为支架(从命令行)或 IDE(如 IDEA)内单独的 Java 程序。

JUnit 提供:

  • 断言测试预期结果
  • 测试功能共享通用的测试数据
  • 测试套件轻松地组织和运行测试
  • 图形和文本测试运行

JUnit 用于测试:

  • 整个对象
  • 对象的一部分-交互的方法或一些方法
  • 几个对象之间的互动(交互)
JUnit 特点
  • JUnit 是用于编写和运行测试的开源框架。
  • 提供了注释,以确定测试方法
  • 提供断言测试预期结果
  • 提供了测试运行的运行测试
  • JUnit 测试让您可以更快地编写代码,提高质量
  • JUnit 是优雅简洁。它是不那么复杂以及不需要花费太多的时间。
  • JUnit 测试可以自动运行,检查自己的结果,并提供即时反馈。没有必要通过测试结果报告来手动梳理。
  • JUnit 测试可以组织成测试套件包含测试案例,甚至其他测试套件。
  • JUnit 显示测试进度的,如果测试时没有问题条形是绿色的,测试失败则会编程红色。

第一个 JUnit 单元测试

POM

pom.xml 文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.funtl</groupId>
    <artifactId>hello-spring</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.17.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

主要增加了 junit:junit 依赖

创建测试类

在测试包 src/main/test 创建一个名为 MyTest 的测试类,代码如下:

package com.yyh.hello.spring.test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class MyTest {

    /**
     * 执行测试方法前执行
     */
    @Before
    public void before() {
        System.out.println("执行 before() 方法");
    }

    /**
     * 执行测试方法后执行
     */
    @After
    public void after() {
        System.out.println("执行 after() 方法");
    }

    @Test
    public void testSayHi() {
        System.out.println("Hi Log4j");
    }

    @Test
    public void testSayHello() {
        System.out.println("Hello Log4j");
    }
}

JUnit 注解

注解 描述
@Test public void method() 测试注释指示该公共无效方法它所附着可以作为一个测试用例。
@Before public void method() Before 注释表示,该方法必须在类中的每个测试之前执行,以便执行测试某些必要的先决条件。
@BeforeClass public static void method() BeforeClass 注释指出这是附着在静态方法必须执行一次并在类的所有测试之前。发生这种情况时一般是测试计算共享配置方法(如连接到数据库)。
@After public void method() After 注释指示,该方法在执行每项测试后执行(如执行每一个测试后重置某些变量,删除临时变量等)
@AfterClass public static void method() 当需要执行所有的测试在 JUnit 测试用例类后执行,AfterClass 注解可以使用以清理建立方法,(从数据库如断开连接)。注意:附有此批注(类似于 BeforeClass)的方法必须定义为静态。
@Ignore public static void method() 当想暂时禁用特定的测试执行可以使用忽略注释。每个被注解为 @Ignore 的方法将不被执行。

JUnit 断言

什么是断言

断言是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题是时以重新启用断言。

使用断言可以创建更稳定、品质更好且不易于出错的代码。当需要在一个值为false时中段当前操作的话,可以使用断言。单元测试必须使用断言(JUnit/JUnitX)。

常用断言方法
断言 描述
void assertEquals([String message], expected value, actual value) 断言两个值相等。值可能是类型有 int, short, long, byte, char or java.lang.Object. 第一个参数是一个可选的字符串消息
void assertTrue([String message], boolean condition) 断言一个条件为真
void assertFalse([String message],boolean condition) 断言一个条件为假
void assertNotNull([String message], java.lang.Object object) 断言一个对象不为空(null)
void assertNull([String message], java.lang.Object object) 断言一个对象为空(null)
void assertSame([String message], java.lang.Object expected, java.lang.Object actual) 断言,两个对象引用相同的对象
void assertNotSame([String message], java.lang.Object unexpected, java.lang.Object actual) 断言,两个对象不是引用同一个对象
void assertArrayEquals([String message], expectedArray, resultArray) 断言预期数组和结果数组相等。数组的类型可能是 int, long, short, char, byte or java.lang.Object.
测试断言效果

在之前的单元测试类中创建一个名为 testAssert 方法来查看断言效果

/**
 * 测试断言
 */
@Test
public void testAssert() {
    String obj1 = "junit";
    String obj2 = "junit";
    String obj3 = "test";
    String obj4 = "test";
    String obj5 = null;
    int var1 = 1;
    int var2 = 2;
    int[] arithmetic1 = {1, 2, 3};
    int[] arithmetic2 = {1, 2, 3};

    assertEquals(obj1, obj2);

    assertSame(obj3, obj4);

    assertNotSame(obj2, obj4);

    assertNotNull(obj1);

    assertNull(obj5);

    assertTrue("为真", var1 == var2);

    assertArrayEquals(arithmetic1, arithmetic2);
}

Log4j 日志框架

Log4j 简介

一个完整的软件,日志是必不可少的。程序从开发、测试、维护、运行等环节,都需要向控制台或文件等位置输出大量信息。这些信息的输出,在很多时候是使用 System.out.println() 无法完成的。

日志信息根据用途与记录内容的不同,分为 调试日志、运行日志、异常日志 等。

Log4j 的全称为 Log for java,即专门用于 Java 语言的日志记录工具。

Log4j 日志级别

为了方便对于日志信息的输出显示,对日志内容进行了分级管理。日志级别由高到低,共分 6 个级别:

  • fatal(致命的)
  • error
  • warn
  • info
  • debug
  • trace(堆栈)
为什么要对日志进行分级

无论是将日志输出到控制台,还是文件,其输出都会降低程序的运行效率。但由于调试、运行维护的需要,客户的要求等原因,需要进行必要的日志输出。这时就必须要在代码中加入日志输出语句。

这些输出语句若在程序运行时全部执行,则势必会降低运行效率。例如,使用 System.out.println() 将信息输出到控制台,则所有的该输出语句均将执行。会大大降低程序的执行效率。而要使其不输出,唯一的办法就是将这些输出语句逐个全部删除。这是一个费时费力的过程。

将日志信息进行分级管理,便可方便的控制信息输出内容及输出位置:哪些信息需要输出,哪些信息不需要输出,只需要在一个日志输出控制文件中稍加修改即可。而代码中的输出语句不用做任何修改。

从这个角度来说,代码中的日志编写,其实就是写大量的输出语句。只不过,这些输出语句比较特殊,它们具有级别,在程序运行期间不一定被执行。它们的执行是由另一个控制文件控制。

Log4j 日志输出控制文件

日志输出简介

Log4j 的日志输出控制文件,主要由三个部分构成:

  • 日志信息的输出位置:控制日志信息将要输出的位置,是控制台还是文件等。
  • 日志信息的输出格式:控制日志信息的显示格式,即以怎样的字符串形式显示。
  • 日志信息的输出级别: 控制日志信息的显示内容,即显示哪些级别的日志信息。

有了日志输出控制文件,代码中只要设置好日志信息内容及其级别即可,通过控制文件便可控制这些日志信息的输出

日志属性配置文件

日志属性文件 log4j.properties 是专门用于控制日志输出的。其主要进行三方面控制:

  • 输出位置:控制日志将要输出的位置,是控制台还是文件等。
  • 输出布局: 控制日志信息的显示形式。
  • 输出级别: 控制要输出的日志级别

日志属性文件由两个对象组成:日志附加器与根日志

根日志,即为 Java 代码中的日志记录器,其主要由两个属性构成: 日志输出级别与日志附加器。

日志附加器,则由日志输出位置定义,由其它很多属性进行修饰,如输出布局、文件位置、文件大小等。

什么是日志附加器?

所谓日志附加器,就是为日志记录器附加上很多其它设置信息。附加器的本质是一个接口,其定义语法为: log4j.appender.appenderName = 输出位置

常用的附加器实现类
  • org.apache.log4j.ConsoleAppender :日志输出到控制到
  • org.apache.log4j.FileAppender:日志输出到文件
  • org.apache.log4j.RollingFileAppender:当日志文件大小到达指定尺寸的时候将产生一个新的日志文件
  • org.apache.log4j.DailyRollingFileAppender:每天产生一个日志文件
常用布局类型
  • org.apache.log4j.HTMLLayout :网页布局,以 HTML 表格形式布局
  • org.apache.log4j.SimpleLayout :简单布局,包含日志信息的级别和信息字符串
  • org.apache.log4j.PatternLayout:匹配器布局,可以灵活地指定布局模式。其主要是通过设置 PatternLayout 的 ConversionPattern 属性值来控制具体输出格式的。

打印参数:Log4J 采用类似 C 语言中 printf 函数的打印格式化日志信息

  • %m:输出代码中指定的消息
  • %p:输出优先级,即 DEBUG,INFO,WARN,ERROR,FATAL
  • %r:输出自应用启动到输出该 log 信息耗费的毫秒数
  • %c:输出所属的类目,通常就是所在类的全名
  • %t:输出产生该日志事件的线程名
  • %n:输出一个回车换行符,Windows 平台为 /r/n,Unix 平台为 /n
  • %d:输出日志时间点的日期或时间,默认格式为 ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss , SSS},输出类似:2002年10月18日 22:10:28,921
  • %l:输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数。举例:Testlog4.main(TestLog4.java: 10 )

第一个 Log4j 日志文件

Slf4j 简介

slf4j 的全称是 Simple Loging Facade For Java,即它仅仅是一个为 Java 程序提供日志输出的统一接口,并不是一个具体的日志实现方案,就比如 JDBC 一样,只是一种规则而已。所以单独的 slf4j 是不能工作的,必须搭配其他具体的日志实现方案,比如 apache 的 org.apache.log4j.Logger,JDK 自带的 java.util.logging.Logger 以及 log4j 等。

POM

继续之前的项目,pom.xml 配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.funtl</groupId>
    <artifactId>hello-spring</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.17.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.25</version>
        </dependency>
    </dependencies>
</project>

主要增加了 org.slf4j:slf4j-log4j12 依赖

创建 log4j.properties 配置文件

src/main/resources 目录下创建名为 log4j.properties 的属性配置文件

log4j.rootLogger=INFO, console, file

log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d %p [%c] - %m%n

log4j.appender.file=org.apache.log4j.DailyRollingFileAppender
log4j.appender.file.File=logs/log.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.A3.MaxFileSize=1024KB
log4j.appender.A3.MaxBackupIndex=10
log4j.appender.file.layout.ConversionPattern=%d %p [%c] - %m%n

日志配置相关说明:

  • log4j.rootLogger:根日志,配置了日志级别为 INFO,预定义了名称为 consolefile 两种附加器
  • log4j.appender.console:console 附加器,日志输出位置在控制台
  • log4j.appender.console.layout:console 附加器,采用匹配器布局模式
  • log4j.appender.console.layout.ConversionPattern:console 附加器,日志输出格式为:日期 日志级别 [类名] - 消息换行符
  • log4j.appender.file:file 附加器,每天产生一个日志文件
  • log4j.appender.file.File:file 附加器,日志文件输出位置 logs/log.log
  • log4j.appender.file.layout:file 附加器,采用匹配器布局模式
  • log4j.appender.A3.MaxFileSize:日志文件最大值
  • log4j.appender.A3.MaxBackupIndex:最多纪录文件数
  • log4j.appender.file.layout.ConversionPattern:file 附加器,日志输出格式为:日期 日志级别 [类名] - 消息换行符
测试日志输出

创建一个测试类,并测试日志输出效果,代码如下:

package com.yyh.hello.spring;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyTest {

    public static final Logger logger = LoggerFactory.getLogger(MyTest.class);

    public static void main(String[] args) {
        logger.info("slf4j for info");
        logger.debug("slf4j for debug");
        logger.error("slf4j for error");
        logger.warn("slf4j for warn");

        String message = "Hello SLF4J";
        logger.info("slf4j message is : {}", message);
    }
}

此时控制台显示为:

项目根目录下也会多出 logs/log.log 目录及文件

附: 占位符说明

打日志的时候使用了 {} 占位符,这样就不会有字符串拼接操作,减少了无用 String 对象的数量,节省了内存。并且,记住,在生产最终日志信息的字符串之前,这个方法会检查一个特定的日志级别是不是打开了,这不仅降低了内存消耗而且预先降低了 CPU 去处理字符串连接命令的时间。

猜你喜欢

转载自blog.csdn.net/qq_37581282/article/details/82378858