Spring实战 一 依赖注入和AOP概述
扯犊子
《Spring实战》(Spring In Action)的笔记。
我这是第二次看这本书,怎么感觉和没看过一样。。。。。?????
Spring是为简化Java开发而出现的一系列解决方案,它有各种场景下的解决方案,Web只是其中的一个部分。针对不同的场景,它提供了不同的组件,我们可以在项目中根据需求引用不同的组件。
依赖注入
Spring最基本的功能,在Spring核心库中的功能就包含了依赖注入(Dependency Injection, DI)。
依赖注入是Java设计模式中的一个,主要目的还是实现解耦。比如你有两个类,这两个类需要协同工作。本示例中,Knight
代表一个勇士,它是一个接口,Quest
代表一次探险,勇士要调用embarkOnQuest
方法开启一次探险,这其中要调用对应Quest
类的embark
方法。
这是很自然的Java代码,大家都会这么写。但是这有一个问题,就是DamselRescuingKnight
与RescueDameselQuest
紧紧的耦合了起来,这代表一个营救公主的勇士只能够发起营救勇士的探险,对于其它的探险,它无法执行。如果你想要一个杀掉恶龙的勇士,就必须再编写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