看完上一个章节,相信你已经掌握了一些思考的本领了。今天我们来聊一个新的话题。上一篇文章是一个简约的话题,希望简约而不简单。当然,如果觉得太浅了也请立刻告知猿人工厂君,可以考虑做一些调整来更好的帮助到你,另外真的很感谢大家的支持,和巨兽的斗争暂时进入僵持阶段,猿人工厂君已经说了,虽千万人,吾往矣。中间细节,猿人工厂君,会在方便的时候公开,程序猿鸭,且行且珍惜。
猿思考是一个原创系列文章,帮助你从一个小白快速掌握基础知识,很多基础知识,在于思考的变通,更多精彩内容,敬请大家关注公主号猿人工厂,点击猿人养成获取!
IOC是Inversion ofControl的英文缩写,中文译名控制反转。控制反转?哈哈,是不是看起来怪怪的?控制反转,也是面向对象的一种编程原则,是一种编程思想。它是指将创建对象的管理权交给容器,由容器来装配和创建对象,从而降低代码之间的耦合度。IOC思想的实现,一般有两种方式,一种是依赖查找(Dependency Lookup)一种就是最常见的依赖注入(Dependency Injection) 。依赖查找是指程序运行时,程序使用容器提供的回调接口和上下文条件来查找资源和对象,从而让程序获得相应资源。这个种方式应用不是很多,需要应用服务器的支持,也就是JAVAEE规范的套路,比如JNDI,EJB...本文就不详细讨论了。而依赖注入(Dependency Injection)是什么呢?依赖注入是指,程序在运行时,程序不用做依赖的定位查询,提供普通的java方法去让容器决定依赖关系,容器全权负责程序使用对象的装配,把符合依赖关系的对象,通过属性或者是构造器的方式传递给依赖的对象。
我们看个简单的例子,我们有三个类:分别是TestServiceTestManager TestDao.其中TestService种有一个test()方法,调用了TestManager种的test()方法,而TestManager的test方法又调用了TestDao的test()方法。如果我们要调用TestService的test()方法,在正常情况下我们编写java代码,必然需要new TestService() newTestManager(), new TestDao().然后将TestManager对象作为TestService的一个属性传递给TestService(testService.setXXX或者是构造函数的方式传递),TestDao对象作为TestManager的一个属性,那么在使用TestService的类中,必然就需要在代码中import TestManager,
这样新编写的这个类代码依赖程度就高,耦合性强,不好维护。那现在改为依赖注入的方式,我们要使用TestService,就找容器拿TestService,容器提供的 TestService对象里已经存在TestManager对象了,那么我们使用TestService时,就只用在代码里import TestService就好了!新编写的类耦合性就没那么强了,易于维护多了。
那怎么去做到它呢?
不就是提供一个容器,获取对象嘛,只是获取的对象里的属性,已经由容器设置好了嘛。怎么办?通过名字创建对象!嗯,说白了,还是反射。下面我们来看一个小例子,实现简单的依赖注入。
先定义两个annotation:
package com.pz.study.frame.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于注解需要由容器管理的类
*
* @author pangzi
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
String value() default"";
}
package com.pz.study.frame.ioc;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于注解需要注入的属性
* @author pangzi
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public@interfaceAutowared {
}
定义一个简单的容器类:
package com.pz.study.frame.ioc;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
public class ApplicationContext {
public static final Map<String,Object> applicationContext = new ConcurrentHashMap<String, Object>();
static {
InputStream stream =ApplicationContext.class.getClassLoader().getResourceAsStream("ioc-frame.properties");
Propertiesproperties = new Properties();
try {
properties.load(stream);
} catch (IOException e) {
e.printStackTrace();
}
StringpackageName=properties.getProperty("package").toString();
try {
instanceBean(packageName);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据annotation上的名字获取对象
* @param beanId
* @return
*/
public static Object getBean(String beanId){
returnapplicationContext.get(beanId);
}
/**
* 创建包名下的对象并完成依赖注入
* @param packageName
* @throws Exception
*/
private static void instanceBean(StringpackageName) throws Exception {
instanceByPackageName(packageName);
autoWared();
}
// 获取指定包路径下使用了 ComponentAnnotationBean的实例
private static void instanceByPackageName(String packageName) {
try {
List<String>classNames= getClassName(packageName,true);
for(String className:classNames){
Class<?>clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Component.class)) {
clazz.getAnnotation(Component.class).value();
applicationContext.put(clazz.getAnnotation(Component.class).value(), clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取某包下所有类
*
* @param packageName
* 包名
* @param childPackage
* 是否遍历子包
* @return类的完整名称
*/
private static List<String> getClassName(String packageName, boolean childPackage) {
List<String> fileNames= null;
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String packagePath =packageName.replace(".", "/");
URL url =loader.getResource(packagePath);
if (url != null) {
String type =url.getProtocol();
if (type.equals("file")) {
fileNames = getClassNameByFile(url.getPath(), null, childPackage);
}
}
return fileNames;
}
/**
* 从项目文件获取某包下所有类
*
* @param filePath
* 文件路径
* @param className
* 类名集合
* @param childPackage
* 是否遍历子包
* @return类的完整名称
*/
private static List<String> getClassNameByFile(String filePath,List<String> className, boolean childPackage) {
List<String> myClassName= new ArrayList<>();
File file = new File(filePath);
File[] childFiles =file.listFiles();
for (File childFile :childFiles) {
if (childFile.isDirectory()) {
if (childPackage) {
myClassName.addAll(getClassNameByFile(childFile.getPath(),myClassName, childPackage));
}
} else {
String childFilePath= childFile.getPath();
if (childFilePath.endsWith(".class")) {
childFilePath =childFilePath.substring(childFilePath.indexOf("/classes") + 9,
childFilePath.lastIndexOf("."));
childFilePath =childFilePath.replace("/", ".");
myClassName.add(childFilePath);
}
}
}
return myClassName;
}
/**
* 遍历map注入属性
*/
private static void autoWared() {
Map<String, Object> map= applicationContext;
try {
for(StringbeanId:map.keySet()){
Object obj=map.get(beanId);
Field[] fields =obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowared.class)) {
field.setAccessible(true);
Object fieldObj= map.get(field.getName());
field.set(obj,fieldObj);
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
我们如果要使用容器创建的对象直接使用ApplicationContext.getBean()就好了。
接下来我们编写一单验证代码,使用我们的annotation和ApplicationContext提供的对象。
编写3个接口:
package com.pz.study.frame.test;
public interface TestService {
public void test();
}
package com.pz.study.frame.test;
public interface TestManager {
public void test();
}
package com.pz.study.frame.test;
public interface TestDao {
public void test();
}
package com.pz.study.frame.test;
import com.pz.study.frame.ioc.Autowared;
import com.pz.study.frame.ioc.Component;
@Component(value="testService")
public class TestServiceImpl implements TestService {
@Autowared
private TestManager testManager;
public void test(){
testManager.test();
}
}
package com.pz.study.frame.test;
import com.pz.study.frame.ioc.Autowared;
import com.pz.study.frame.ioc.Component;
@Component(value="testManager")
public class TestManagerImpl implements TestManager {
@Autowared
private TestDao testDao;
publicvoid test(){
testDao.test();
}
}
package com.pz.study.frame.test;
import com.pz.study.frame.ioc.Component;
@Component(value="testDao")
public class TestDaoImpl implements TestDao {
public void test(){
System.out.println("ioc 小框架测试");
}
}
最后编写运行程序
package com.pz.study.frame.test;
import com.pz.study.frame.ioc.ApplicationContext;
public class TestIoc {
public static void main(String args[]){
TestService testService=(TestService) ApplicationContext.getBean("testService");
testService.test();
}
}
运行程序,我们发现代码正确运行,而我们的程序只依赖了TestService这个接口,程序将需要依赖的对象交由自己定义的容器ApplicationContext去创建和装备。我们简单的实现了依赖注入的原理!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。