Spring 通过IOC,控制反转实现依赖注入。
依赖注入的三种方式:
(1)构造方法注入:即被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IOC容器)知道它需要哪些依赖对象,然后IOC容器会检查被注入对象的构造方法,
取得其所需要的依赖对象列表,进而为其注入相应对象。
(2)setter方法注入:即当前对象只需要为其依赖对象所对应的属性添加setter方法,IOC容器通过此setter方法将相应的依赖对象设置到被注入对象的方式即setter方法注入。
还有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
(3)接口注入:接口注入有点复杂,被注入对象如果想要IOC容器为其注入依赖对象,就必须实现某个接口,这个接口提供一个方法,用来为被注入对象注入依赖对象,IOC容器通过接口方法将依赖对象注入到被注入对象中去。相对于前两种注入方式,接口注入比繁琐和死板,被注入对象就必须专声明和实现另外的接口。
常用的是setter方法注入。通过注解或者XML都是可以的,具体方法网上很多不再赘述了。
xml配置
<bean id="priceorder" class="com.hjj.source.math.PriceOrder">
<property name="money" value="1000"/>
<property name="payway" ref="WeChat"/>
</bean>
注解配置在下面的代码中可以直接看到,在PriceOrder类中。
这个依赖注入到底解决了什么问题呢?假如我们现在进行订单支付。
那么实际上具体业务实现的时候,开发者不需要知道用户是通过网银还是支付宝还是微信支付,这些是另外一个人的工作。只需要知道是否支付成功。(同时,这三者支付的方式在实现上肯定是不相同的,需要调用不同的API来实现)。
但是这个支付的方法是确定的,实现是不确定的情况下。不牵扯到具体属性的时候尽量多使用接口而非抽象类继承。
抽象类:强调的是把共同(共有、相同)的属性方法, 抽象出来,统一写在一个地方(他们的实现代码是一样的),方便维护。(面向对象三大特性中的继承特性)
接口: 抽象的是行为 - 同一种行为的不同实现方式。当多个对象都拥有相同的行为,但是行为的具体实现方式不一样的时候可以用接口抽象(面向对象中的多态特性)
善用接口和继承一直是一个设计上很难的问题,我的理解也只是初步的。可能是有误的,但是现在这种理解能帮助我的编程开发。
声名pay接口public interface pay {
public boolean paymoney(int money);
}
实现了WeChat和Zfb两个支付方式,implements了pay接口
@Repository(value = "WeChat")
public class WeChat implements pay{
public boolean paymoney(int money) {
//这里调用WeChat的支付接口
System.out.println("通过WeChat支付成功,支付金额为:"+money);
return true;
}
}
@Repository(value = "Zfb")
public class ZFB implements pay{
public boolean paymoney(int money) {
//这里调用ZFB的支付接口进行支付
System.out.println("通过ZFB支付成功,支付金额为:"+money);
return true;
}
}
Service订单类
@Service("priceorder")
public class PriceOrder {
private int money = 1000;
@Resource(name = "WeChat")
private pay payway;
public void setMoney(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public pay getPayway() {
return payway;
}
public void setPayway(pay payway) {
this.payway = payway;
}
public void pay(){
if (payway.paymoney(money)) money -= 1000;
}
public void rest(){
System.out.println("剩余余额为:"+money);
}
}
Test测试
public class Test {
public static void main(String[] args) throws InterruptedException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
math1 math = (math1) ctx.getBean("math");
PriceOrder priceOrder = (PriceOrder)ctx.getBean("priceorder");
priceOrder.pay();
priceOrder.rest();
}
}
可以看到我这里在声名priceOrder的时候,没用new一个新的pay对象,对象由spring自动生成并注入(见前面@Resource)。我对注入的对象不需要了解,只需要对业务进行开发。
现在只是一个对象,当需要创建的对象复杂且繁多,并且很多都是别的package里的对象的时候,程序员很难了解每个对象创建需要依赖的对象,需要不断查找创建赋值,这个行为是很浪费时间并且极易出错的,因此DI(依赖注入)给予了程序员开发上的极大方便。
这里我又用了一下AOP结合着来做
@Before(value = "execution(* com.hjj.source.math.WeChat.paymoney(..))")
public void before(JoinPoint point){
Date date = new Date();
System.out.println(date+"一笔新的支付订单");
System.out.println("@Before:模拟权限检查...");
System.out.println("@Before:目标方法为:" +
point.getSignature().getDeclaringTypeName() +
"." + point.getSignature().getName());
System.out.println("@Before:参数为:" + Arrays.toString(point.getArgs()));
System.out.println("@Before:被织入的目标对象为:" + point.getTarget());
// 获取方法名
}
在每次支付之前,输出时间和一些日志消息。
Thu Jun 21 10:25:48 CST 2018一笔新的支付订单
@Before:模拟权限检查...
@Before:目标方法为:com.hjj.source.math.WeChat.paymoney
@Before:参数为:[1000]
@Before:被织入的目标对象为:com.hjj.source.math.WeChat@3e96bacf
通过WeChat支付成功,支付金额为:1000
剩余余额为:0
总结一下,DI依赖注入避免了程序员去寻找类的依赖对象,也避免了多次创建依赖对像引起的混乱问题,使得程序员对编码之外的情况不需要进行关心,提高了开发效率。
AOP则是在不影响业务的情况下,为整个程序添加系统级别的功能,特别是日志记录,非常好用和方便,对框架的侵入性低。