Loading

Spring实战 一 依赖注入和AOP概述

扯犊子

《Spring实战》(Spring In Action)的笔记。

我这是第二次看这本书,怎么感觉和没看过一样。。。。。?????

Spring是为简化Java开发而出现的一系列解决方案,它有各种场景下的解决方案,Web只是其中的一个部分。针对不同的场景,它提供了不同的组件,我们可以在项目中根据需求引用不同的组件。

依赖注入

Spring最基本的功能,在Spring核心库中的功能就包含了依赖注入(Dependency Injection, DI)。

依赖注入是Java设计模式中的一个,主要目的还是实现解耦。比如你有两个类,这两个类需要协同工作。本示例中,Knight代表一个勇士,它是一个接口,Quest代表一次探险,勇士要调用embarkOnQuest方法开启一次探险,这其中要调用对应Quest类的embark方法。

这是很自然的Java代码,大家都会这么写。但是这有一个问题,就是DamselRescuingKnightRescueDameselQuest紧紧的耦合了起来,这代表一个营救公主的勇士只能够发起营救勇士的探险,对于其它的探险,它无法执行。如果你想要一个杀掉恶龙的勇士,就必须再编写KillDragonKnight并把它和KillDragonQuest紧耦合起来。

这样写很不灵活,而且如果你想为某个勇士替换一个Quest的实现类,就要重新编写这部分代码,重新编译,重新执行。

一个理想的方式是勇士类不应该知道它执行的是什么冒险,这就是多态机制嘛,它只需要知道它要执行一个冒险即可。

BraveKnight是一个勇敢的勇士,它使用最简单的依赖注入方式——使用构造方法注入一个冒险,而不是在类中写死一个冒险的实现类。

public class BraveKnight implements Knight {

    private Quest quest;

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

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

再编写一个淦恶龙的冒险,这里也使用了依赖注入,这里并没有直接调用System.out.println,而是传入一个PrintStream,具体传入的是啥它不关心,这样外部就可以给它传入任何的PrintStream而不只是输出到控制台。

public class SlayDragonQuest implements Quest{

    private PrintStream printStream;

    public SlayDragonQuest(PrintStream printStream) {
        this.printStream = printStream;
    }

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

}

现在提供一个类去调用这俩类,组合它们互相运行:

Knight myKnight = new BraveKnight(
        new SlayDragonQuest(
                System.out
        )
);
myKnight.embarkOnQuest();

现在我们可以从外部传入任何实现类了,松了耦合。但现在还有一个问题,我们的主类不是又和具体的实现耦合了吗??能不能用一个配置文件去指定具体的实现类,把这部分的耦合也松了。

通过Java的反射机制当然可以,但是Spring为我们提供了这个功能,咱不造轮子了。

你先创建一个Maven项目,然后在pom中添加如下内容,你也可以用Jar包,并且我的版本比较老了,对于第一篇笔记还足够,你也可以用更新的版本。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>2.5.6</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>2.5.6</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>2.5.6</version>
</dependency>

然后,在资源文件夹中创建一个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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
	http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	http://www.springframework.org/schema/aop
	http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
	http://www.springframework.org/schema/context
	http://www.springframework.org/schema/context/spring-context-4.0.xsd">

</beans>

beans用来创建并声明JavaBean对象的各种依赖关系,我们刚刚编写的那几个类都是JavaBean对象。

我们在里面创建两个配置项。

<bean id="knight" class="io.lilpig.springlearn.springlearn01.BraveKnight">
    <constructor-arg ref="quest"/>
</bean>

<bean id="quest" class="io.lilpig.springlearn.springlearn01.SlayDragonQuest">
    <constructor-arg value="#{T(System).out}"/>
</bean>

第一个bean中定义了勇士的一个实例,它使用了BraveKnight,并且因为构造方法有参数,所以你要给这个参数指定一个值,这里引用到了另一个bean,也就是quest

第二个bean定义了一个冒险,使用了SlayDragonQuest,并且它的构造方法也有参数,我们把控制台打印流给传递进去了。

这样所有需要的对象都创建了,它们之间的依赖关系也满足了。

那么主方法里应该怎么用它们呢?

所有的Bean在Spring中被Context容器管理,后面会详细的说。

这里我们使用了一个ClassPathXmlApplicationContext来读取应用classpath下的配置文件,也就是我们定义的那个文件,然后用context.getBean就能拿到其中的一个对象。

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("knights.xml");
    Knight knight = context.getBean(Knight.class);
    knight.embarkOnQuest();
}

这样就把那些耦合都去除了,现在如果我们想换其它的实现类,只需要在配置文件中写一写然后重新运行就好了。

AOP编程

我们很多时候会在业务逻辑中调用和业务逻辑无关的一些代码,比如记录日志。这些代码会分散我们的注意力,我们应该将注意力放在核心的业务逻辑上。

AOP(面向切面编程)可以让这些代码从外部织入类,而不出现在类中。

比如我们创建一个吟游诗人类,它的作用就是歌颂勇士,在勇士冒险前歌颂他的勇气,在冒险之后歌颂他的伟大。

public class Minstrel {

    private PrintStream stream;

    public Minstrel(PrintStream stream){
        this.stream = stream;
    }

    public void singBeforeQuest() {
        stream.println("Fa la la, the knight is so brave!!!");
    }

    public void singAfterQuest() {
        stream.println("Tee hee hee, the brave knight did embark on a quest!!!");
    }
}

传统的办法是让勇士持有这个类,然后调用它们,除此之外没啥办法了。

private Minstrel minstrel = new Minstrel(System.out);

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

可是哪有勇士每天不想着天天提升自己,而是想着给自己找个诗人来歌颂自己,并且在冒险前后提醒诗人去歌颂自己呢?太自恋了吧。

这就是我们说的,勇士只应该执行自己的逻辑,不应该让其它的代码分散注意力。

面向切面编程就可以解决这个问题,我们看看如何配置。

首先在pom.xml中添加一些依赖用来支持面向切面。

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>2.5.6</version>
</dependency>

<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>

然后,在Spring的配置文件中配置,首先你要先把吟游诗人的Bean也配置出来。

<bean id="minstrel" class="io.lilpig.springlearn.springlearn01.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>

<aop:aspect>minstrel这个bean创建成了一个切面,然后通过<aop:pointcut>,创建了一个切入点,用于指定何时将切面织入。这个意思就是当embarkOnQuest被调用时植入,这是AspectJ语言的语法。后面也会介绍。

<aop:before><aop:after>很清晰了,就是将切入点和切面中的方法联系起来,在切入点之前执行一个切面中的方法,在切入点之后执行一个切面的方法。

现在就可以实现一样的功能了,并且可以把勇士类中对吟游诗人的定义完全去除,勇士类甚至不知道有吟游诗人的存在。

Spring除了提供DI和AOP这些工具外,还提供了模板等来简化你的Java代码。

容纳你的Bean

Spring中,你的Bean都存在于容器中,容器会创建Bean并构建它们之间的依赖关系,ApplicationContext就是一个容器。

BeanFactory就是一个容器,ApplicationContext就是基于这个类来创建的,但它太底层了,我们就用ApplicationContext。

Spring提供了各式各样的ApplicationContext,用于满足各种各样的加载过程。

如果使用XML配置,那么可以使用ClassPathXmlApplicationContext,它会从classpath中加载xml文件,而FileSystemXmlApplicationContext会基于文件系统来加载xml文件。

Bean生命周期

这个部分我没看太懂

如下是Spring容器中Bean的生命周期

东西我能懂,关键是Spring不是说最小侵入性吗,为啥还让我们的Bean实现接口??

Spring的六大模块

P

posted @ 2021-08-31 17:02  yudoge  阅读(391)  评论(0编辑  收藏  举报