我的 Spring 学习笔记

Spring 学习笔记(一):简化 Java 开发

2018-11-11  本文已影响6人  Ethan_zyc

今天开始,Spring 笔记开始更新,主要还是作为阅读《Spring 实战》(第4版)的学习笔记,该系列的内容大多也是引用自该书中。

虽然工作中大量接触 Spring,但回顾发现自己只是知道怎么做,而并没有去深究,导致有时候遇到问题不知从何下手,所以想要系统学习 Spring。

Spring 的使命便是如标题所说:简化 Java 开发。具体怎么简化的呢?分为以下四个方面:

这四点现在也不用花太多时间去深究,就像面向对象一样,有个大概的了解,在实际中我们会慢慢体会到的。

接下来就简单了解下:

POJO

POJO:全程 Plain Old Java Object,直译就是简单老式 Java 对象。

更详细的一点的解释就是:一个简单的Java类,这个类没有实现/继承任何特殊的java接口或者类,不遵循任何主要java模型,约定或者框架的java对象。在理想情况下,POJO不应该有注解。

public class MyPOJO {
    public String sayHello() {
        return "Hello";
    }
}

这就是一个 POJO,很简单,但 Spring 可以使用 DI 来装配它们,让它们变得强大。接下来就讲一下 DI,也就是依赖注入。

依赖注入

目的解耦

注:以下代码只为表现本文中心理念,不能作实际编译用

/**
 * 拯救少女的骑士
 */
public class DamselRescuingKnight implements Knight {

    //拯救少女的任务
    private RescueDamselQuest quest;

    public DamselRescuingKnight() {
        this.quest = new RescueDamselQuest();
    }

    public void embarkOnQuest() {
        quest.embark();
    }
}

以上代码就是按正常逻辑编写的,DamselRescuingKnight在他的构造函数中 new 了一个RescueDamselQuest,于是这两者便紧紧耦合在一起了,这个骑士只能去拯救少女,而别的任务就完成不了了。

step1:如何实现 DI


public class BraveKnight implements Knight{
    private Quest quest;
    
    public BraveKnight(Quest quest) {
        this.quest = quest;
    }

    public void embarkOnQuest() {
        quest.embark();
    }
}

这段代码便一定程度上降低了耦合性,骑士没有自己创建任务,而且在构造的时候把任务作为参数传递进来,这便是依赖注入的方式之一,即构造器注入 constructor injection

而且上述代码中构造器传入的是一个Quest接口,这样骑士便能随心所欲的完成继承了Quest接口的任何任务。这样BraveKnight没有和任何Quest发生耦合,这就是 DI 带来的最大收益——松耦合

step2:如何注入 Quest


首先需要一个Quest的实现类,如下,屠龙勇士再次出现了:

import java.io.PrintStream;

public class SlayDragonQuest implements Quest {
    
    private PrintStream stream;
    
    public SlayDragonQuest(PrintStream stream) {
        this.stream = stream;
    }

    @Override
    public void embark() {
        stream.println("Embarking on quest to slay the dragon");
    }
}

接下来便有两个问题,如何把SlayDragonQuest交给BraveKnight、如何把PrintStream交给SlayDragonQuest。这个过程称为装配 wiring,Spring 提供了很多种装配 Bean 的方法,其中 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/scheme/beans
       http://www.springframework.org/scheme/beans/spring-beans.xsd">
    
    <!-- 注入 Quest bean-->
    <bean id="knight" class="BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>

    <!-- 创建 SlayDragonQuest-->
    <bean id="quest" class="SlayDragonQuest">
        <constructor-arg value="#{T(System.out)}"/>
    </bean>
</beans>

这样就能把BraveKnightSlayDragonQuest声明为 spring 中的 bean 了,这里只是展示下大概怎么配置,具体在书中后面的章节会讲,我到时候也会整理成笔记的。

虽然他们之间会想依赖,但并不知道会有什么样的 Quest 传给他,只有通过 spring 的配置文件才能知道他们之间是如何装配起来的。而且我们可以通过修改 xml 直接修改依赖关系。

step3:如何工作的


Spring 通过应用上下文 Application Context 装载 bean 并把他们组装起来。

xml 对应的上下文是 ClassPathXmlApplicationContext,示例代码如下:

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
        // 获取 Knight bean
        Knight knight = context.getBean(Knight.class);
        // 使用 knight
        knight.embarkOnQuest();
        context.close();
    }
}

这里通过knights.xml创建了 Spring 应用上下文,然后从该应用上下文中获取一个 ID 为 knight 的 bean,然后就可以调用他的方法。这个类完全不知道那种类接受了哪种任务,只有 xml 知道。

应用切面

DI 是为了让相互协作的组件保持松耦合,而面向切面编程 aspect-oriented-programming 允许你把那些遍布应用各处的功能分离出来形成可重用的组件。

最常用的需要切面的便是日志、事务管理、安全。

还是骑士的例子,屠龙骑士应该只需要专注于如何去屠龙,而屠龙后怎么把他的事迹记录下来不应该需要他操心。而是有专门的记录历史的。用代码来看一下,我们把记录屠龙勇士的人称为吟游诗人。

首先需要创建一个吟游诗人的类:

import java.io.PrintStream;

/**
 * 吟游诗人
 */
public class Minstrel {
    private PrintStream stream;
    
    public Minstrel(PrintStream stream) {
        this.stream = stream;
    }
    
    public void singBeforeQuest() {
        stream.println("这个骑士要上了");
    }
    
    public void singAfterQuest() {
        stream.println("骑士探险结束,真牛逼");
    }
}

代码很简单,就是需要在骑士执行任务前后分别输出两句话。

似乎我们把 braveKnight 稍加改造就能实现记录了,如下

public class BraveKnight implements Knight{
    private Quest quest;
    private Minstrel minstrel;

    public BraveKnight(Quest quest,Minstrel minstrel) {
        this.quest = quest;
        this.minstrel = minstrel;
    }

    public void embarkOnQuest() {
        minstrel.singBeforeQuest();
        quest.embark();
        minstrel.singAfterQuest();
    }
}

但就如同一开始所说,我们应该让骑士自己去调用吟游诗人然后记录吗?答案肯定是不,骑士不应该考虑这些,他只要专心完成他的任务就行了。

这时候我们需要把 Minstrel 抽象为一个切面,并在 spring 的配置文件中声明他

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd">

    <!-- 注入 Quest bean-->
    <bean id="knight" class="BraveKnight">
        <constructor-arg ref="quest"/>
    </bean>

    <!-- 创建 SlayDragonQuest-->
    <bean id="quest" class="SlayDragonQuest">
        <constructor-arg value="#{T(System.out)}"/>
    </bean>

    <bean id="minstrel" class="Minstrel">
        <constructor-arg value="#{T(System.out)}"/>
    </bean>

    <aop:config>
        <aop:aspect ref="minstrel">
            <!-- 定义切点-->
            <aop:pointcut id="embark" expression="execution(* *.embarkOnQuest(..))"/>
            <!-- 声明前置通知-->
            <aop:before method="singBeforeQuest" pointcut-ref="embark"/>
            <!-- 声明后置通知-->
            <aop:after method="singAfterQuest" pointcut-ref="embark"/>
        </aop:aspect>
    </aop:config>

</beans>

首先和之前一样把 Minstrel 声明 为一个 bean,然后在 aop:config 中定义切面,并定义什么时候调用方法。具体配置也是后面再详细介绍,这里先大致看懂意思就行。

这样的话,BraveKnight 不需要显示的调用它,甚至不知道它的存在,这就是 AOP 的妙处。

使用模板消除样板式代码

简单的说就是减少重复代码,很常见的一个例子就是用 JDBC 访问数据库,正常我们可能需要写如下代码:

public static void queryTeacher() {
    String driverName = "com.mysql.jdbc.Driver";
    String url = "jdbc:mysql://localhost:3306/zyc";
    String username = "root";
    String password = "123456";

    Connection connection = null;
    Statement statement = null;
    try {
        Class.forName(driverName);
        connection = (Connection) DriverManager.getConnection(url,username,password);
        statement = connection.createStatement();
        ResultSet rs = statement.executeQuery("SELECT tno,tname from teacher");
        while (rs.next()) {
            String no = rs.getString("tno");
            String name = rs.getString("tname");
            System.out.println(no + ":" + name);
        }
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

相似代码会在同一个项目中出现很多遍,这时候可以使用 Spring 中 JDBCTemplate 来简化代码

public static void insertTeacher() {
    
    return jdbcTemplate.queryForObject("SELECT tno,tname from teacher",
                                      ...,
                                      ...); 
    // 代码省略,意思很简单,就是把模板中变的因素作为参数传进来

这种思想在代码中很常见,不管是 Java 还是别的编程语言。

上一篇下一篇

猜你喜欢

热点阅读