前言
什么是IOC:IOC的英文全称是Inversion of Control,翻译一下就是控制反转,是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的实现方式叫做依赖注入(Dependency Injection,简称DI),其它实现方式有“依赖查找”(Dependency Lookup)和依赖拖拽(Dependency Pull)。更多Spring内容进入【Spring解读系列目录】。
IOC的实现
在日常面试中有一个经典问题,IOC和DI有什么区别。其实在上面的解释中,其实很容易能够看出,能够问出这种问题,本身就是对IOC概念一知半解。这个问题就类似于:最高时速超过200km的车和兰博基尼跑车有什么区别。前者是对一个概念的描述,而后则是这个概念的一个实现,这两个东西怎么能放在一起比较呢?兰博基尼出了多少钱,我保时捷能给两倍对不对,那布加迪的脸往哪放呢?一个道理。IOC就是我们在编码过程中的一个目标,而DI就是实现IOC的一种技术手段。这种手段也可以换成DL,或者DP。
依赖注入 Dependency Injection
我们这次就重点讲解DI,毕竟Spring框架DI用的最多,也最方便。什么是依赖?比如Class A中有一个Class B的属性,那么我们可以理解为A依赖了B。当然这种依赖关系也可以通过构造参数传递,方法很多随便举两个例子。
Class A{
Private B b; //A依赖了B
}
Class B{
Private C c; //这样B就又依赖了C
Public B(Class c){
this.c=c;
}
}
那么为什么我们会有依赖呢?因为这个就是面向抽象编程的思想,这样做可以灵活的控制我们的类和接口。比如我们有个类IOCDemo依赖了IOCTest。IOCDemo有一个方法在里面又调用了IOCTest里面的方法。如果我们直接把IOCTest这个类new出来使用,如下:
public class IOCDemo {
IOCTest test = new IOCTest();
public void doDemo(){
test.dotest();
}
}
加入未来某一天我们想要对IOCTest做个代理,或者我们想重写这个类,那就得去IOCDemo里面修改new代码。这就要耗费很大的精力去做这个事情,而且改完还不能保证对别的地方完全没有影响。但是如果不写死,就是声明一下。将来有一天要修改,也只是需要把新的实现类注入进来就可以了。注入就好操作多了,可以选择setter方法,也可以选择构造方法。无论外面要做一个什么修改,只需要注入进来,就可以改变具体的实现。这样就做到面向抽象编程。那么什么是注入也就好理解了,所谓的注入其实就是传递new好的实例进入目标方法。
public class IOCDemo {
IOCTest test;
public void setTest(IOCTest test) {
//注入进来新的实现类
this.test = test;
}
public IOCDemo(IOCTest test) {
//注入进来新的实现类
this.test = test;
}
public void doDemo(){
test.dotest();
}
}
简单来说就是依赖写死对于以后的修改比较麻烦,而编程抽象化则是简化这个过程,但是抽象化以后就必然会产生依赖。为什么大家都用Spring框架呢?因为Spring框架本质上就是一个十分强大的依赖管理容器。类的产生过程交给了容器,那么自己写的代码则可以不需要去关心这些对象的产生和依赖,转而关注业务的实现,大大减轻了开发的成本。
Spring框架实现IOC的思路是提供一些配置信息用来描述类之间的依赖关系,然后由容器去解析这些配置信息,继而维护好对象之间的依赖关系。当然这些描述和真实代码中的依赖关系,必须一一对应不能有错。
Spring编程的风格
Spring框架给我们提供了三种不同但是互不干扰的编程风格。就是说这三种编程风格你可以混着用,没有冲突,也没有矛盾。
第一种:schemal-based
其实也就是我们说的xml格式的配置,大概就是下面的这种。以下摘自官方范例,具体的可以参考【官方文档:aop-schema】里面的内容。
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
第二种:annotation-based
简单来说就是用@Required或者@Autowired这样的风格编程,都是老熟人了。以下摘自官方范例,具体参考【官方文档:beans-annotation-config】。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
第三种:java-based
Java Configuration就是基于@Bean 和 @Configuration 这两个注解来的,相信这些代码,大家一定也是很熟悉的。以下摘自官方范例,具体参考【官方文档:beans-beans-java】
@Configuration
public class AppConfig {
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
<beans>
<bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
这里三种编码风格没有本质的区别,因为可以混用所以每个人都有自己的理解,怎么习惯怎么来,但是我相信大部分的项目都是基于annotation-based和java-based来的,或者混用。因为这两个最符合大多数人的编程习惯。
Spring Dependency Injection
注入的两种方法:setter和Constructor两种,老版本的Spring还有基于接口interface的,但是现在已经不用了,在Spring4时代就已经被抛弃了。记清楚,只有两种了。以下摘自Spring官方文档:DI exists in two major variants: Constructor-based dependency injection and Setter-based dependency injection.
。以下代码摘自官方范例,小标题直达官方网站,具体参考【官方文档:beans-factory-collaborators】
Constructor-based Dependency Injection
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
Setter-based Dependency Injection
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
实现注入
那么我们怎么实现这个注入呢?举个简单的例子:我们现在有一个接口DemoDao,一个实现类DemoDaoImpl,一个业务类DemoService。这里声明下,这篇博客的所有例子都是在SpringBoot框架下的Web项目,不知道怎么搭建的,请参考【Idea创建一个JSP web项目】。
DemoDao
public interface DemoDao {
void test();
}
DemoDaoImpl
public class DemoDaoImpl implements DemoDao{
@Override
public void test() {
System.out.println("test");
}
}
DemoService
public class DemoService {
private DemoDao dao;
public void myDaoService(){
dao.test();
}
}
可以看出DemoService依赖了DemoDao,我们在代码里提供了一个依赖关系。那么下一步就是需要把这几个类交给Spring框架容器管理。怎么交出去呢?Spring给出的描述文件是一个.xml文件。名字无所谓,resource文件夹下创建出来,这里我们命名为spring.xml。
实现基于Setter的注入
首先我们演示下setter方法是怎么注入的。在DemoService创建Setter方法:
public class DemoService {
private DemoDao dao;
public void setDao(DemoDao dao) {
this.dao = dao;
}
public void myDaoService(){
dao.test();
}
}
那么我们开始编写spring.xml,首先把DemoDao和DemoService都告诉容器。然后在DemoService中维护这个方法。因为DemoService依赖于DemoDao。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--先把我们dao类告诉spring容器-->
<bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
<!--把我们service类告诉spring容器-->
<bean id="service" class="com.example.demo.DemoService">
<!--配置依赖关系,第一个dao是DemoService中的setter,第二个dao就是上面的dao-->
<property name="dao" ref="dao"></property>
</bean>
</beans>
除此之外,还要有一个test类用来测试:
public class DemoTest {
public static void main(String[] args) {
//加载配置
ClassPathXmlApplicationContext cpth
= new ClassPathXmlApplicationContext("classpath:spring.xml");
//拿到service
DemoService service= (DemoService) cpth.getBean("service");
service.myDaoService();
}
}
如果说是通过一个classpath下面的一个xml配置文件来维持spring框架的初始化的,那么就需要用ClassPathXmlApplicationContext这个类,就看名字也非常的直:在ClassPath下的Xml来运行我们应用Application的环境内容Context。运行这个测试类拿到打印的test。
[main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7c53a9eb
[main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [spring.xml]
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'service'
test //拿到打印的test
test被打印出来,说明我们的依赖就维护好了。这个就是setter的注入例子。
实现基于Constructor的注入
我们再演示下Constructor方法是怎么注入的。在DemoService创建Constructor方法:
public class DemoService {
private DemoDao dao;
public DemoService(DemoDao dao) {
//构造方法
this.dao = dao;
}
public void myDaoService(){
dao.test();
}
}
spring.xml也要修改,改成构造方法的依赖:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--先把我们dao类告诉spring容器-->
<bean id="dao" class="com.example.demo.DemoDaoImpl"></bean>
<!--把我们service类告诉spring容器-->
<bean id="service" class="com.example.demo.DemoService">
<constructor-arg ref="dao"></constructor-arg>
</bean>
</beans>
跑一遍,同样拿到拿到打印的test。
[main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7c53a9eb
[main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 2 bean definitions from class path resource [spring.xml]
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dao'
[main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'service'
test //拿到打印的test
这里就完成了Constructor的注入。
其他
当然你也可以注入很多其他的东西,比如一个String类型的静态值等等。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
或者再一个property中给多个值等等。
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
例子很多甚至可以直接定义一个list,一个map。但是这就太变态了,谁会用一个写死的集合呢,如果要写死还不如直接用枚举。具体可以参考:【官方文档:beans-value-element】这里就不多说了。