Spring 是啥?
Spring是个大家都听过的东西,或者在许多web项目中都有看到过的东西,然而,要清楚地说出Spring是个啥?Spring存在的意义?可能大多数人只能够照本宣科的说出一些。我希望能够更简单的去解释Spring。
其实,在我看来,Spring的众多特性,想要达到的目的是:让开发者更好的基于POJO编程。将重复性、依赖性的代码从类中剥离出来。
什么是上面所说的POJO(Plan Ordinary Java Object)?简单来说,就是不实现特定的接口或继承特定父类的java类(这里的特定指的是为了使用某框架而必须进行的继承或者实现,实现自己定义的接口当然可以啦),且仅包含简单属性及他的Getter、Setter方法。
基于POJO变成有啥好处?将类写成简单的POJO类,可以使用特定的方式(ioc、aop),在不改变代码的情况下,轻松地对这些POJO进行组装或者添加特性。例如,
- 为POJO增加了持久化的方法(Insert、Update、Delete……)之后,POJO就变成了PO。
- 为POJO增加了数据绑定功能之后,POJO就变成了View Object,即UI Model。
- 为POJO增加业务逻辑的方法(比如单据审核、转帐……)之后,POJO就变成了Domain Model。
- 而通过Spring,可以对这些POJO类进行ioc、aop来添加特性,使得这些类具有高度的灵活性,可以更好的实现面向接口编程的需求。
ioc特性如何辅助POJO?
假设我们要实现一个案例,各个演奏者(钢琴、吉他)演奏不同的乐器。
最简单的方式是建立钢琴演奏者和吉他演奏者的类,并分别调用play实现演奏(代码就不写了,自行yy),这种方式没有扩展性,一旦某个演奏者需要添加一个乐器、或者需要添加一类演奏者,都需要修改大量代码。
面向接口的设计
下面是一个较好的设计,首先,有个乐器的接口
public interface Instrument {
void play();
}
然后是吉他和钢琴的实现类。
public class Guitar implements Instrument {
String name = null;
public Guitar(String name) {
this.name = name;
}
@Override
public void play() {
System.out.println("guita play.." + name);
}
}
public class Piano implements Instrument {
String name = null;
public Piano(String name) {
this.name = name;
}
@Override
public void play() {
System.out.println("piano play.." + name);
}
}
然后是表演者的抽象类
import java.util.List;
import spring.instrument.Instrument;
public abstract class Performer {
List<Instrument> instruments;
public void play() {
for (Instrument ins : instruments) {
ins.play();
}
}
public void setInstrument(List<Instrument> ins) {
instruments = ins;
}
}
然后是吉他和钢琴表演者的实现类
import java.util.ArrayList;
public class Guitaror extends Performer {
public Guitaror() {
instruments = new ArrayList<Instrument>();
instruments.add(new Guitar("吉他"));
}
@Override
public void play() {
System.out.print("吉他家在演奏:");
super.play();
}
}
import java.util.ArrayList;
public class Pianist extends Performer {
public Pianist() {
instruments = new ArrayList<Instrument>();
instruments.add(new Piano("钢琴"));
}
@Override
public void play() {
System.out.print("小提琴家在演奏:");
super.play();
}
}
然后,剧场类
import java.util.ArrayList;
import java.util.List;
public class Concert {
List<Performer> performers;
void start() {
performers = new ArrayList<Performer>();
performers.add(new Pianist());
performers.add(new Guitaror());
for (Performer p : performers)
p.play();
}
}
调用时是这样的。
public class MainClass {
public static void main(String[] args) {
new Concert().start();
}
}
以上就开始了剧场里表演者的表演。
较最初设计的优点是,抽出了乐器的接口和表演者的抽象类,使得程序面向接口编程了,并且添加额外的乐器或者表演者时,改动代价小了。
不足也很明显,改动代价小但还是需要对现有的逻辑进行修改,根本原因是因为在Concert、Pianist和Guitaror中分别对演奏者和乐器进行了实例化(new),导致若有新的演奏者加入或者某个演奏者需要加一个或者建一个乐器还需要修改对应代码。
将实例化的代码抽离出来
首先,我们改造两个表演者类,通过setter的方式将乐器设置到类中,而不是在构造函数中初始化。
public class Guitaror extends Performer {
@Override
public void play() {
System.out.print("吉他家在演奏:");
super.play();
}
@Override
public void setInstrument(List<Instrument> ins) {
super.setInstrument(ins);
}
}
public class Pianist extends Performer {
@Override
public void play() {
System.out.print("小提琴家在演奏:");
super.play();
}
@Override
public void setInstrument(List<Instrument> ins) {
super.setInstrument(ins);
}
}
然后是剧场类,同样的改造方式
public class Concert {
List<Performer> performers;
void start() {
for (Performer p : performers)
p.play();
}
public void setPerformers(List<Performer> performers) {
this.performers = performers;
}
}
这样,我们在main函数中需要这样调用。
public class MainClass {
public static void main(String[] args) {
Guitaror guitaror = new Guitaror();
ArrayList<Instrument> insts = new ArrayList<Instrument>();
insts.add(new Guitar("吉他"));
guitaror.setInstrument(insts);
Pianist pianist = new Pianist();
insts = new ArrayList<Instrument>();
insts.add(new Piano("钢琴"));
pianist.setInstrument(insts);
List<Performer> performers = new ArrayList<Performer>();
performers.add(guitaror);
performers.add(guitaror);
Concert concert = new Concert();
concert.setPerformers(performers);
concert.start();
}
}
我们把所有的实例化代码都剥离到main中进行,这样,我们在修改演奏者或者乐器时候,只需要修改main函数的实例化代码就可以了。
到这里,我们基本完成了将一个程序改造成spring 风格的过程,于是,我们可以使用spring的ioc对我们的程序进行操控了。
spring的ioc风格
Spring的ioc,叫做控制反转(或者依赖注入),即将原本由程序控制的new对象的控制权(上一个示例是main,上上个示例是concert、pianist和guitaror),交由spring来替你控制,控制方式是将这些初始化语句写在spring的配置文件中,这样,你在修改逻辑时,连main也不需要改了,直接改配置文件就可以了。
下面是spring的配置内容。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans >
<bean id = "piano" class="spring.instrument.Piano">
</bean>
<bean id = "guitar" class="spring.instrument.Guitar">
</bean>
<bean id = "pianist" class="spring.performer.Pianist">
<property name="instrument" ref="piano"></property>
</bean>
<bean id = "guitaror" class="spring.performer.Guitaror">
<property name="instrument" ref="guitar"></property>
</bean>
<bean id = "concert" class="spring.Concert">
<property name="performers">
<list>
<ref bean="pianist"/>
<ref bean="guitaror"/>
</list>
</property>
</bean>
</beans>
然后,你这么调用就可以了:
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring/springconfig.xml");\
Concert concert = (Concert) context.getBean("concert");
concert.start();
}
}
所有的变量创建都配置在xml文件中,让spring进行管理,将对象注入到另一个对象中,使得对象的创建过程与程序的逻辑解耦,使我们在写代码时能够更关注业务逻辑,逻辑更清晰,程序在修改时涉及的代码更少。
还能再少点?
上面的代码,如果说要加一个乐器或者加一个表演者,是通过新建一个类的方式完成的,如果想在配置文件中完成这一切呢?可以试着把钢琴和吉他两个具体变成一个乐器的实体类,把钢琴家和吉他演奏家变成一个具体的表演者类。于是,有了下面的代码。
public class ConcreteInstrument implements Instrument {
String name = null;
String instrumentType = null;
public ConcreteInstrument(String instrumentType, String name) {
this.name = name;
this.instrumentType = instrumentType;
}
@Override
public void play() {
System.out.println(instrumentType + " play.." + name);
}
}
public class ConcretePerformer extends Performer {
String name;
public ConcretePerformer(String name) {
this.name = name;
}
@Override
public void play() {
System.out.print(name + "在演奏:");
super.play();
}
@Override
public void setInstrument(List<Instrument> ins) {
super.setInstrument(ins);
}
}
然后,我们可以直接在配置文件中建立钢琴、吉他乐器对象,同时建立钢琴家、吉他演奏家对象。并注入对应的实体中。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans >
<bean id = "piano1" class="spring.ioc.perfect.ConcreteInstrument">
<constructor-arg value="钢琴"></constructor-arg>
<constructor-arg value="电子琴"></constructor-arg>
</bean>
<bean id = "guitar1" class="spring.ioc.perfect.ConcreteInstrument">
<constructor-arg value="吉他"></constructor-arg>
<constructor-arg value="电吉他"></constructor-arg>
</bean>
<bean id = "pianist" class="spring.ioc.perfect.ConcretePerformer">
<constructor-arg value="钢琴家"></constructor-arg>
<property name="instrument" ref="piano1"></property>
</bean>
<bean id = "guitaror" class="spring.ioc.perfect.ConcretePerformer">
<constructor-arg value="吉他表演者"></constructor-arg>
<property name="instrument" ref="guitar1"></property>
</bean>
<bean id = "concert" class="spring.ioc.better.Concert">
<property name="performers">
<list>
<ref bean="pianist"/>
<ref bean="guitaror"/>
</list>
</property>
</bean>
</beans>
同样,使用以下方式进行调用。
public class TestSpring {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"spring/ioc/perfect/concert_config.xml");
Concert concert = (Concert) context.getBean("concert");
concert.start();
}
}
这样,我们就逐步完成了将代码转换为spring风格的过程,当你一开始就使用spring来构建自己的项目,对于一些需要考虑到扩展的地方,你能够自然的写出POJO类,用配置文件而非硬编码的方式进行依赖对象的注入。
Spring的ioc核心就是这了。他使得多个POJO不直接产生依赖,而是使用Spring框架将依赖关系注入实体间,这样就达到了松耦合的POJO。感觉就像是将一块块积木根据自己的需要进行组合,那些积木就是那些POJO类,而你只需要用xml配置文件画出图纸,Spring就会帮你组装好。
一切都从bean开始
aop特性如何辅助POJO?
然后来讲讲AOP特性,ioc是将创建对象的控制权交给spring,这样,由spring负责对象的创建,而spring通过asm、cglib等类库,能够对java的.class文件的字节码进行操作,所以能够做到构造器注入、setter注入等、另外,能够进行动态代理的实现,以实现AOP的特性。
通过AOP,spring能够将bean剖开,找到那些符合要求的方法们(spring只能做到方法级而做不到变量级的),当满足某个触发条件时,便执行相应的操作。
AOP术语
要进行AOP,设想要我们需要在某些方法触发某些动作时执行我们自定义的方法。那么,我们需要告诉spring,1、我们需要监控哪些方法?2、当监控的实体做了哪些操作时需要触发我的自定义方法?
所以,spring定义了下面的术语用于定义一个切面
- 通知(Advice):就是告诉在调用方法的哪个环节触发操作。spring定义了五种的通知:before、after、after-returning、after-throwing、around。根据这些名字,很容易看出他跟方法的关系。
- 连接点(JoinPoint):这个就是Spring允许通知(Advice)的地方。比如,spring的连接点是调用方法、异常抛出时。而必须AspectJ的连接点还包含了构造器的调用、类、包等,比spring多出不少。
- 切点(PointCut):上面讲的所有的连接点都可以在实际项目中对应到许多的实体(比如调用方法的切点,可以对应类中所有的方法),但每个项目中有那么多的方法,你一定不希望所有的方法都作为触发实体,你只想将某一些方法加入你的监控体系。切点便是筛选出这些你想要的连接点的。
- 切面(切点):切点仅仅定义了你需要监控哪些方法,并没有指明你是在方法调用前?后?返回… 时候执行你的自定义方法,所以,他需要与通知相结合,这样,就组成一个切面了。
- 引入(Introduce):将一个新方法引入类中,可以理解为为某个类添加新的接口方法。其实实际上就是做了个代理,由于通过spring创建对象时,实际上都是创建了一个代理对象,因此可以方便的拦截方法调用。所以当调用到这个引入的接口方法时,转而调用对应的实现类。于是,将实现了这种方法引入或者我把他叫做接口引入了。
- 织入(Weaving):将切面应用到目标对象来创建新的代理对象的过程。Spring是在运行期动态的创建一个代理对象来实现织入切面的。其他如AspectJ是在编译器完成织入的。还可以在类加载器进行织入。
配置
案例是:我想在每次演员表演后,观众都进行鼓掌。
于是,我先建立一个Audience类。
public class Audience {
private String name;
public Audience(String name) {
this.name = name;
}
public void clap() {
System.out.println("audience " + name + " is clapping.");
}
}
xml配置方式的切面格式如下
<bean id = "audience1" class="spring.audience.Audience" >
<constructor-arg value="小明"></constructor-arg>
</bean>
<aop:config>
<aop:aspect ref='audience1'>
<aop:after-returning
pointcut="execution(* spring.performer.Pianist.play(..)) "
method="clap"/>
</aop:aspect>
</aop:config>
首先, 标签指定了自定义方法的对象。
然后使用通知+切点定义了一个切面:
- 通知:
Spring 提供的其他东西
数据库结合
Spring可以和ORM(Object Relational Mapping)框架整合进行使用。
Spring中,还提供了模板的方式进行数据库的访问。
事务管理
远程服务
RESTful
消息
参考:
《Spring实战》