Spring之旅
一.Spring根本使命
简化java开发
1. 简化开发策略
- 基于POJO的轻量级和最小侵入性编程(需要理解)
- 通过依赖注入和面向接口实现松耦合(需要理解)
- 基于切面和惯例进行声明式编程(需要理解)
- 通过切面和模板减少样板式代码(需要理解)
2. 策略1:基于POJO的轻量级和最小侵入性编程
javaBean使用POJO而不需要继承或实现框架中的接口,就能实现Spring框架的功能。
框架和应用代码之间松耦合。
侵入性: 很多框架需要在应用代码中继承或实现框架的接口,让应用与框架绑定。
实现非侵入性的方式:
- 依赖注入
3. 依赖注入
3.1 概念
对象中可能需要其他的依赖对象,在依赖注入中对象不需要自己创建依赖的对象,而是依赖的对象将依赖关系自动交给对象。
3.2 依赖注入方式
(1) 构造器注入
在javaBean的构造函数中,将所依赖的类传进来。这样所依赖的对象主动交给javaBean,完成javaBean对象的创建。
扩展: 构造函数中传入来的依赖,最好是接口。这样所有实现接口的类都可以注入进来,屏蔽了类的具体实现。
3.3 问题
构造器注入在创建javaBean对象时,需要传依赖的对象,这个依赖的对象如何来?
如果是依赖的对象是接口,传哪一个实现类?
3.4 装配
(1) 概念
创建组件之间的协作行为
即配置javaBean和依赖的类。
(2) Spring装配方式
Spring有多重装配方式,常见的有两种:xml文件和java
例:javaBean:Foo (接口)依赖javaBean:Bar (接口)
FooImp实现了Foo接口,BarImp实现了Bar接口。
(2.1) xml文件
<bean id="foo" class="com.spring.FooImp">
<constructor-arg ref="bar">
</bean>
<bean id="bar" class="com.spring.BarImp"/>
(2.2) java配置
@configuration
public class FooConfig{
@Bean
public Foo foo(){
return new FooImp(bar())
}
@Bean
public Bar bar(){
return new BarImp();
}
}
装配好后的组件如何运行起来的呢?
Spring 通过应用上下文(Application Context)装载Bean的定义并把它们组装起来。
Spring的Application Context全权负责对象的创建与组装。
Spring 有多种实现Application Context的方式,主要区别在于如何加载配置。
完整例子:
- Food接口
public interface Food {
String foodName();
}
- Apple类
public class Apple implements Food{
public String foodName() {
String foodname = "红富士苹果";
System.out.println("这是非常好的" + foodname);
return foodname;
}
}
- Animal接口
public interface Animal {
void eat();
}
- Dog类
public class Dog implements Animal{
private Food food;
public Dog(Food food) {
this.food = food;
}
public void eat() {
System.out.println("dog like" + this.food.foodName());
}
}
- AnimalConfig配置
@Configuration
public class AnimalConfig {
@Bean
public Animal animal(){
return new Dog(food());
}
@Bean
public Food food(){
return new Apple();
}
}
- main方法中加载注解配置上下文,并获取Bean类
public class Demo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
Animal animal = context.getBean(Animal.class);
animal.eat();
}
}
- 打印结果输出:
这是非常好的红富士苹果
dog like红富士苹果
DI可以让各组件松耦合,而AOP可以把散落在系统各处的同类功能分离出来形成通用组件,接下来,让我们了解下AOP
AOP
AOP是啥?
AOP是面向切面编程,aspect-oriented programming.
可以将多个系统都调用的服务,抽离成单独的组件,以消除这些服务散落在系统各处带来的复杂性。
为何要使用AOP?
如果不使用AOP,散落在系统各处的同类功能代码,不仅不利于管理,而且会非常混乱。
如日志服务和安全管理,系统很多业务都需要调用它们的服务。想想看:
- 如果日志服务有改动,所有的业务都需要同时改动。就算抽离成一个通用方法,方法的调用也会出现在各处,不利于维护与管理。
- 而且这些服务又不是核心的业务代码,不应该在核心业务代码中去码这些东西,代码会非常混乱。
AOP的好处在哪里?
AOP能够让这些服务模块化,以声明的方式去应用到需要这些服务的影响的组件中。
这些组件更关注自身的核心业务而不需要关注其他服务。
总之,AOP能让POJO变得简单。
如何理解AOP?
AOP可以想象成包裹核心业务组件的一层外壳,以声明的方式灵活的运用到业务组件中,甚至核心业务层都不知道它们的存在,将核心业务与日志事物等服务功能分离。
例子:
在每个animal eat food之前,都会有doctor过来给animal检查,eat food之后,又会给animal称重。
你可能会想到:在每个eat方法的开头添加检查cheak方法,在结尾添加称重weight方法。
但是仔细想想,animal 中需要管理 doctor的方法?check和weight不是animal的核心业务呀,这些都让doctor自己做就好了。
如果想要管理也OK,你给我将doctor的实例传给我,依赖注入进来。但如果doctor为空呢?
简单的事情就变得很复杂,倒不如是doctor的方法就由doctor自己来管理。
利用AOP,可以让animal在eat food之前必须check,而animal一无所知。
详细代码:
Doctor类
public class Doctor implements Person{
public void check() {
System.out.println("医生来检查啦");
}
public void weight() {
System.out.println("吃胖啦!");
}
}
配置AOP的xml文件
<bean id="food" class="com.example.beans.Apple"/>
<bean id="animal" class="com.example.beans.Dog">
<constructor-arg ref="food"/>
</bean>
<bean id="person" class="com.example.beans.Doctor"/>
<aop:config>
<aop:aspect ref="person">
<aop:pointcut expression="execution(void com.example.beans.Dog.eat(..))" id="doctorSay" />
<aop:before method="check" pointcut-ref="doctorSay" />
<aop:after method="weight" pointcut-ref="doctorSay" />
</aop:aspect>
</aop:config>
输出日志:
医生来检查啦
这是非常好的红富士苹果
dog eat 红富士苹果
吃胖啦!
从这个例子我们可以了解到
- 没有任何代码说明Doctor是一个切面,它依然是一个POJO。
- 没有任何代码说明Dog eat调用了Doctor的方法,但Doctor的方法依然在Dog eat方法执行时启用了。
- Doctor依然是POJO,也就意味着它可以使用DI等其他POJO可以做的事。
使用模板Template消除样板式代码
每次在码东西的时候,经常碰到这些东西是不是在哪见过。没错,你可能真的在重复的码同一个东西,这就是样板式的代码。
最直接的例子就是java创建jdbc的连接,执行sql,再放入Bean类中,最后释放连接,抛出异常。
其中创建jdbc和释放连接抛出异常都是通用的写法,样板式的代码,但又不得不写。其实真正的业务只有执行SQL,并将查询结果放入Bean类中。
使用模板可以消除这些样式代码,可以理解成将这些样式代码封装成了一个模板类,只需码真正的核心业务就行了。