标签:
“Don’t call us, we’ll call you(不要联系我,我会主动联系你)”
这是好莱坞很经典的一句话,应用在ioc(控制反转)领域,发现理解起来相得益彰——你作为用户不需要控制业务实体的生成,交给我容器来控制,这就是控制反转。不过,这样理解起来也有点麻烦,套用面向对象大师Martin Fowler的说法更为贴切:
“Dependency Inversion(依赖注入)”
当容器中所有的实体bean 都被管理起来的时候,他们之间的依赖关系不需要你手动的写死在程序中,可以写进配置文件,由容器帮你将这些依赖对象主动注入到你定义的业务bean中,也叫依赖注入(DI)。
package org.loda.ioc.demo.controller; import org.loda.ioc.annotation.Component; import org.loda.ioc.annotation.Inject; import org.loda.ioc.demo.model.User; import org.loda.ioc.demo.service.UserService; @Component public class UserController { @Inject private UserService userService; public void add(User user){ userService.add(user); } }
以上不是spring提供的依赖注入的Demo,这是我自己实现的,但是原理一致。@Component表示该UserController被spring容器管理起来了,@Inject表示这个UserService类型的属性的具体实现将有spring帮我注入。
由上面简单的程序,我们马上就意识到了spring在这里做了层隔离,隔离了接口(UserService)和实现(new UserService())。
在传统的三层架构中,有controller,service,dao三层。这三层之间controller中拥有service实例,service拥有dao实例,这样才能通过controller,直接进行一连串调用,访问到最后的dao,并由dao来向数据库发起访问。这样,整个web后台开发的流程就串了起来。
在我们当前程序中,也是如此,这里我定义了@Component注解表示该类是要被我的spring管理的,定义了@Inject表示该属性是要被spring注入的。如图所示
如果完成了ioc/di,经过一层层的调用,我的controller就能访问到dao,执行dao中的内容,看看dao中的代码:
package org.loda.ioc.demo.dao; import org.loda.ioc.annotation.Component; import org.loda.ioc.demo.model.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component("userDao") public class UserDaoImpl implements UserDao { private static final Logger logger = LoggerFactory.getLogger(UserDao.class); @Override public void add(User user) { logger.info("添加了用户{}", user); } }
能不能打印这句话呢?首先让我们看看我们测试Demo的入口:
@Test public void testIoc() throws Exception { // 创建工厂对象,并将配置信息DemoConfig设置进去,工厂对象会自动在后台完成对象装载 BeanFactory bf = new AnnotationContext(new DemoConfig()); // 从bean工厂中根据类型(也可以根据名字)取出对象 UserController uc = bf.getBean(UserController.class); // 断言不能为空 Assert.assertNotNull(uc); // 完成添加动作(如果成功依赖注入,那么这个流程肯定可以走完) uc.add(new User(1, "jack", "123456")); }
BeanFactory作为对象工厂,将控制着整个bean对象的访问。这里的工厂其实是个借口,他有个实现类,我们在他们实现类中完成对象容器的填充和从容器中获取对象本身的功能。完成bean对象的填充,我们需要读取“填充数据源”,即我们从哪里获取填充对象或者填充对象的路径。主流的实现方式是读取xml配置文件,根据配置文件中定义的bean配置来初始化这些bean对象到工厂中去。这里AnnotationContext表示我们使用扫描注解的方式,扫描指定包中的所有拥有@Component配置的类,实例化这些类,并放到bean工厂中。(这里new DemoConfig()中指定了从哪里开始扫描),这里是从org.loda.ioc包开始扫描,其下所有包都会被扫描到。
package org.loda.ioc.demo; import org.loda.config.SpringConfig; public class DemoConfig implements SpringConfig{ @Override public String[] basePath() { return new String[]{"org.loda.ioc"}; } }
存储容器需要使用合理的数据结构,基于散列表的hashMap很好的满足了快读快取的功能,然后我们定义了接口getBean(String beanName)和getBean(Class<?> targetClass)两种方法来获取我们的bean对象。
这是BeanFactory实现类中的对象获取的实现:
/** * 使用map作为容器存储bean对象 */ protected Map<String, Object> map = new HashMap<String, Object>(); /** * 以下两个方法都是获取bean容器中的bean对象 */ @Override public Object getBean(String beanName) { return map.get(beanName); } @SuppressWarnings("unchecked") @Override public <T> T getBean(Class<T> requireType) { return (T) map .get(StringUtils.uncapitalize(requireType.getSimpleName())); }
还记得上面我们进行
BeanFactory bf = new AnnotationContext(new DemoConfig());
步骤完成了项目的启动么,这里我们会刷新容器,然后将指定的路径作为根路径,查找需要管理的bean对象,并注册到map中。
public AnnotationContext(SpringConfig config) { // 刷新spring存储对象的bean容器(map对象) refresh(); // 注册bean文件(ioc流程) register(config.basePath()); }
在注册过程中,我们会先扫描根路径下被@Component注解了的实体类,将这些实体类放到map里面去。完成这个步骤后,我们再将所有的属性依赖一一注入进来。
// 刷新spring存储对象的bean容器(map对象) refresh(); // 注册bean文件(ioc流程) register(config.basePath());
其中会碰到如下几个问题:
1. 根据jdk获取包对象的api在这里无法使用,需要使用自定义的类加载器加载这个包路径。
2. 加载进来的都是以key,value的形式,这里key该如何取呢?我采取的形式是,如果@Component中的value属性没有配置,就将key定位类名小写开头+剩余字母:如UserController的key就是userController。如果
@Component中value配置了值,就以这个值作为key。
3. 当属性上拥有@Inject注解时,表示这个属性需要依赖注入,他会从已经装满了bean对象的map中取。这里依赖从map中取时,key值可以是@Inject中注解配置的name、type(名称、类型)来获取。
4. 注入属性在spring中采用两种方式,set注入和构造函数注入。这里为了书写方便,抛弃了set和get方法,也没有利用构造函数注入,而
是采取的简单粗暴、喜闻乐见的破坏封装性的暴力注入给private属性。
完成加载bean对象和依赖注入后,我们的ioc基本功能就此完成。很简单是吧,确实,没什么复杂的思想,只要熟悉 反射,人人都能写一个ioc!
之后,附上源码下载地址。https://github.com/mjaow/my_spring
标签:
原文地址:http://my.oschina.net/u/1378920/blog/465787