前言
作为一名java开发人员,我们接触最早最多的框架肯定就是spring了,一次次不停的使用spring框架提供的功能帮助我们快速开发,然而其中的核心功能IOC(控制反转)和AOP能详细阐明的却不多,我们知其然,却不知其所以然,那么下面我们就一起探索一下spring中的IOC吧
正篇
-
IOC概念
例:在现实生活中我的头发长了,影响了我的帅气,那么我就需要理发,我不可能自己给自己理发,首先我没有理发的工具,其次我可能把自己的头发剪得像狗啃的一样,那么影响我帅气的外表,就没有妹子愿意跟我一起约会了,这肯定是我不愿意发生的。其次,我也不是王思聪,我也不肯能拥有一名只为自己服务的人员,那么正确的做法就是去村口的理发店找Tony老师来给我打造帅气的发型。我们来分解一下剪发的过程,我是需求提出者,头发变帅是要的结果,首先如果我自己为自己理发,不仅可能达不到预期的效果,而且还费时费力,那么我就需要一名专业的理发师来为我服务,达到最终的目的,这个过程我们可能称之为解耦合的过程。
在Java开发中,一个功能的实现往往是由多个类和方法来共同协作完成的,在没有IOC之前我们创建一个类的依赖类的方式是new object(),控制权在本类手中,这些依赖关系将会使系统的复杂度提高,不利于维护和开发,这是我们不愿意看到的,在有了IOC之后,一个类所需要的依赖类由IOC来管理,那么我们只要关心本身类的功能和方法即可,将控制权交给了IOC容器,这就是我理解的控制反转。
- IOC容器 在探索Spring系列(一)Spring容器和Bean的生命周期这篇文章中我们已经介绍到了spring的2种基本容器BeanFactory和ApplicationContext的接口
其中BeanFactory是IOC容器的基本实现,ApplicationContext是BeanFactory的子接口,提供更高级的特性。 而下面几个类则是具体的实现类,可以加载配置文件中定义的bean,管理所有加载的bean,有请求的时候分配bean。
-
DI(依赖注入)
创建应用对象之间的关联关系的传统方法(通过构造器或者查找)通常会导致结构复杂的代码,这些代码难以被复用也很难进行单元测试,如果情况不严重的话,这些对象所做的事情只是超出了应该做的范围,而最坏的情况是,这些对象的彼此之间高度耦合,难以复用和测试。
在spring中,对象无需自己查找或创建与其所关联的其他对象。相反,容器负责把需要互相协作的对象引用赋予各个对象。例如,一个订单管理组件需要信用卡认证组件,但它不需要自己创建信用卡认证组件。订单管理组件只需要表明自己两手空空,容器就会主动赋予它一个信用卡认证组件。创建应用对象之间协作关系的行为通常称为装配(wiring),这也是依赖注入(DI)的本质。(摘录spring实战第四版第二章)
-
自动化配置Bean
1:在Java中进行显式配置 (个人喜欢)
我们定义接口以及其实现类
package com.lly.springtest1.ioc;
/**
* @Author lly
* @Description 水果接口类
* @Date 2019/1/29 10:23 AM
* @Param
* @return
**/
public interface IFruitService {
/**
* @Author lly
* @Description 显示水果信息
* @Date 2019/1/29 10:25 AM
* @Param []
* @return
**/
void showFruitInfo();
}
复制代码
package com.lly.springtest1.ioc;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @ClassName OrangeServiceImpl
* @Description 水果实现类-橘子
* @Author lly
* @Date 2019/1/29 10:17 AM
* @Version 1.0
**/
@Component
@Data
@Slf4j
public class OrangeServiceImpl implements IFruitService {
@Override
public void showFruitInfo() {
log.info("橘子的重量是:{}kg",10);
}
}
复制代码
扫描组件配置类,@ComponentScan注解会默认扫描与其配置类相同的包以及这个包下面的所有的子包,查找带有@Component注解的类,当然我们可以直接定@ComponentScan扫描的包
#单个包
@ComponentScan("包名")
@ComponentScan(basePackage="包名")
#多个包
@ComponentScan(basePackage={"包名","包名"})
复制代码
package com.lly.springtest1.ioc;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName TestIoc
* @Description 扫描组件配置类
* @Author lly
* @Date 2019/1/29 10:30 AM
* @Version 1.0
**/
@ComponentScan
@Configuration
public class TestIoc {
}
复制代码
单元测试
package com.lly.springtest1.ioc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestIoc.class)
public class TestIocTest {
@Autowired
private IFruitService iFruitService;
@Test
public void showInfo(){
iFruitService.showFruitInfo();
}
}
复制代码
结果
可以看到我们的组件orangeEntity已经成功被spring容器管理了,成功注入到测试类中关于 @Autowired 自动装配的方式除了上述还有通过构造器和setter方法注入效果都是一样的
2:隐式的bean发现机制和自动装配 (个人喜欢)
其实这种方式我们在 探索Spring系列(一)Spring容器和Bean的生命周期 这里章节已经见到过了,下面贴出核心代码
package com.lly.springtest1.entity;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanLifeCycle {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanLifeCycle.class);
context.close();
}
@Bean
public MyBeanPostProcessor getBean() {
return new MyBeanPostProcessor();
}
@Bean(initMethod = "myInit",destroyMethod = "myDestroy")
public GirlFriendEntity getGirl() {
GirlFriendEntity girl = new GirlFriendEntity();
girl.setName("颖宝");
return girl;
}
}
复制代码
这种配置的方式也可以将bean纳入spring容器的管理中,下面我们来测试一下
package com.lly.springtest1.entity;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = BeanLifeCycle.class)
public class BeanLifeCycleTest {
@Autowired
private MyBeanPostProcessor myBeanPostProcessor;
@Test
public void show(){
Assert.assertNotNull(myBeanPostProcessor);
}
}
复制代码
可以看到bean已经被容器管理了,成功注入到测试类中去了
3:xml中显示配置(个人不喜欢这种方式,配置很繁琐,有兴趣的同学自行了解学习)
4:总结,上述几种装配bean的方式,都可以实现同样的功能,也可以混合使用,使用哪种方式完全可以按照开发者个人的习惯和喜好来决定,但是作者目前使用的都是前两种,消除配置式编程,更快乐,在目前比较流行的springboot开发中也是推荐前两种
-
高级装配Bean
1:消除歧义性 在上文中我们定义个一个IFruitService,orangeEntity 这个类实现了这个接口,我们在测试类中是直接注入的,那么我可以想想一下,在实际的开发中,可能存在一个接口对应多个实现类的情况,spring在帮我自动注入的时候是怎么帮我们选择的呢,下面我们来实验一下。
我们再创建一个水果的实现类 香蕉
package com.lly.springtest1.ioc;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @ClassName BananaServiceImpl
* @Description 水果实现类-橘子
* @Author lly
* @Date 2019/1/29 10:17 AM
* @Version 1.0
**/
@Component
@Data
@Slf4j
public class BananaServiceImpl implements IFruitService {
@Override
public void showFruitInfo() {
log.info("香蕉的重量是:{}kg",100);
}
}
复制代码
然后我们再次启动测试类发现
错误信息如下:Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lly.springtest1.ioc.IFruitService' available: expected single matching bean but found 2: bananaServiceImpl,orangeServiceImpl
复制代码
通过查看我们发现IFruitService这个接口有2个实现类,我们没有指定要使用哪个,那么spring是不会知道我们将要使用哪个的。
解决方法:
spring提供2中方式来解决这个问题
第一种:使用@Qualifier注解来指定我们要使用的具体实现类
我们可以看到在我们指定了具体实现类后测试用例顺利通过。
第二种:使用@Primary注解来指定哪个实现类作为首选实现类,我们在香蕉类上来加上这个注解
package com.lly.springtest1.ioc;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
/**
* @ClassName BananaServiceImpl
* @Description 水果实现类-橘子
* @Author lly
* @Date 2019/1/29 10:17 AM
* @Version 1.0
**/
@Component
@Data
@Slf4j
@Primary
public class BananaServiceImpl implements IFruitService {
@Override
public void showFruitInfo() {
log.info("香蕉的重量是:{}kg",100);
}
}
复制代码
测试类改造如下
package com.lly.springtest1.ioc;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestIoc.class)
public class TestIocTest {
@Autowired
private IFruitService iFruitService;
@Test
public void showInfo(){
iFruitService.showFruitInfo();
}
}
复制代码
启动查看结果
测试用例通过,当然这个注解在接口实现类上市互斥的,只有一个实现类上可以加,如果超过1个实现类上使用此注解,同样会出现我们刚开始出现的异常信息-
Bean的作用域
1)单例(singleton)在整个应用中,只创建一个实例;(默认情况下,spring应用上下文中所有的bean都是单例模式)。 2)原型(prototype)每次注入或者通过spring应该上下文获取的都是一个新的实例。 3)会话(session)在web应用中,为每个会话创建一个bean实例。 4)请求(request)在web应用中。为每个请求创建一个bean实例。
注解式指定bean作用域
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.INTERFACES)
复制代码
另外还可以使用xml配置式,这里不做详解
总结
工欲善其事必先利其器,spring作为我们使用最频繁的框架,熟悉其主要功能和原理是很有必有的,不然我们一直摸着石头过河,下面一章我们将要介绍spring另外一个重要功能AOP(面向切面编程)