1、什么是IOC?
控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心。 控制反转一般分为两种类型,依赖注入(Dependency Injection,简称DI)和依赖查找(Dependency Lookup)。依赖注入应用比较广泛(来自百度百科)。
自己的理解:不是开发者自己通过new的方式来实例化一个对象,而是把对象的创建交给框架来完成。
2、IOC的优缺点?
- 优点:
解决了程序之间的松耦合问题,当我们需要换一个接口实现类的子类时,我们只需要修改配置文件即可(一般代码中都是基于接口编程,而在配置文件中使用具体的实现),这样就无需修改源代码。(实际上我认为其实并没有解决这种耦合关系,只是把这种耦合关系从代码转移到了配置文件,我想这就是所谓的控制反转吧)。 - 缺点:
(1)IOC是基于反射的原理,来实现对象的实例化的,可能效率上会有所损耗;
(2)可能对某些人来说,生成对象的方式变复杂了,其实也没那么复杂。
3、IOC的简单实现?
最简单的方式就是,首先获取所有的BeanMap(是一个map,记录了这个类的字节码与这个类的实例的映射关系)。然后遍历这个映射关系,分别取出Bean类与Bean实例,进而通过反射获取类中的所有的成员变量。继续遍历这些成员变量,在循环中判断当前成员变量是否带有Inject注解(自己定义的一个注解,可参考后面的代码),若带有该注解,则从BeanMap中根据Bean类取出Bean实例。最后通过反射来修改该成员变量的值。
- 首先我们需要获取指定包名下的所有类,这个方法可以参考spring中的实现,另外网上有很多现成的代码。
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.jmx.LayoutDynamicMBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClassUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ClassUtil.class);
/*
* 获取类加载器
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
/*
* 加载类
*/
public static Class<?> loadClass(String className,boolean isInitialized){
Class<?> cls;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
//Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
LOGGER.error("load class failure",e);
throw new RuntimeException(e);
}
return cls;
}
/*
* 获取指定包名下的所有类
*/
public static Set<Class<?>> getClassSet(String packageName){
Set<Class<?>> classSet = new HashSet<Class<?>>();
try {
//获取指定包下的资源
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
while(urls.hasMoreElements()){
URL url = urls.nextElement();
if(url != null){
//获取协议的名称(file或者jar)
String protocol = url.getProtocol();
if(protocol.equals("file")){
//获取url的路径并解码,空格的url编码值就是%20
String packagePath = url.getPath().replaceAll("%20"," ");
addClass(classSet,packagePath,packageName);
}else if(protocol.equals("jar")){
JarURLConnection jarURLConnection = (JarURLConnection)url.openConnection();
if(jarURLConnection != null){
//如果协议是jar,获取一个jarFile
JarFile jarFile = jarURLConnection.getJarFile();
if(jarFile != null){
//从jarFile中得到一个枚举类
Enumeration<JarEntry> jarEntries = jarFile.entries();
while(jarEntries.hasMoreElements()){
//获取一个实体
JarEntry jarEntry = jarEntries.nextElement();
String jarEntryName = jarEntry.getName();
//如果以.class结尾,则截取类的全限定名
if(jarEntryName.endsWith(".calss")){
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
//将类实例化,并加入classSet中
doAddClass(classSet,className);
}
}
}
}
}
}
}
} catch (IOException e) {
LOGGER.error("get class set failure", e);
throw new RuntimeException(e);
}
return classSet;
}
private static void doAddClass(Set<Class<?>> classSet, String className) {
//获取类的实体
Class<?> cls = loadClass(className, false);
//加入classSet集合中
classSet.add(cls);
}
private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
File[] files = new File(packagePath).listFiles(new FileFilter() {
//过滤
@Override
public boolean accept(File file) {
return file.isFile()&&file.getName().endsWith(".class")||file.isDirectory();
}
});
for(File file:files){
String fileName = file.getName();
if(file.isFile()){
//截取类名
String className = fileName.substring(0,fileName.lastIndexOf("."));
if(StringUtils.isNotEmpty(packageName)){
//获取类的全限定名
className = packageName+"."+className;
}
//取得类的实例并加入classSet
doAddClass(classSet, className);
}else{
//如果是目录,则递归调用函数
String subPackagePath = fileName;
if(StringUtils.isNotEmpty(packagePath)){
subPackagePath = packagePath+"/"+subPackagePath;
}
String subPackageName = fileName;
if(StringUtils.isNotEmpty(packageName)){
subPackagePath = packageName+"."+subPackagePath;
}
addClass(classSet, subPackagePath, subPackageName);
}
}
}
2.简单起见,我们可以将带有controller和service注解的类所产生的对象,理解为框架需要管理的bean,下面的工具类用来获取应用包名下的所有Bean类的方法。
import java.lang.annotation.Annotation;
import java.util.HashSet;
import java.util.Set;
import org.smart4j.framework.ConfigHelper;
import org.smart4j.framework.annotation.Controller;
import org.smart4j.framework.annotation.Service;
import org.smart4j.framework.utils.ClassUtil;
public class ClassHelper {
private static final Set<Class<?>> CLASS_SET;
static{
//自己定义的应用包常量,你也可以定义在配置文件中
String basePackage = ConfigConstant.BASE_PACKAGE;
CLASS_SET = ClassUtil.getClassSet(basePackage);
}
/*
* 获取应用包下的所有类
*/
public static Set<Class<?>> getClassSet(){
return CLASS_SET;
}
/*
* 获取应用包下的所有service类
*/
public static Set<Class<?>> getServiceClassSet(){
Set<Class<?>> classSet = new HashSet<Class<?>>();
for(Class<?> cls: CLASS_SET){
if(cls.isAnnotationPresent(Service.class)){
classSet.add(cls);
}
}
return classSet;
}
/*
* 获取应用包下的所有Controller
*/
public static Set<Class<?>> getControllerClassSet(){
Set<Class<?>> classSet = new HashSet<Class<?>>();
for(Class<?> cls: CLASS_SET){
if(cls.isAnnotationPresent(Controller.class)){
classSet.add(cls);
}
}
return classSet;
}
/*
* 获取应用包名下的所有Bean类
*/
public static Set<Class<?>> getBeanClassSet(){
Set<Class<?>> beanclassSet = new HashSet<Class<?>>();
//认为所有的service类和controller类是我们需要管理的bean
beanclassSet.addAll(getServiceClassSet());
beanclassSet.addAll(getControllerClassSet());
return beanclassSet;
}
/*
* 获取应用包名下某父类(或接口)的所有子类(或实现类)
*/
public static Set<Class<?>> getClassSetBySuper(Class<?> superClass){
Set<Class<?>> classSet = new HashSet<Class<?>>();
for(Class<?> cls : CLASS_SET){
if(superClass.isAssignableFrom(cls)&&!superClass.equals(cls)){
classSet.add(cls);
}
}
return classSet;
}
/*
* 获取应用包名下所有带有某注解的所有类
*/
public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass){
Set<Class<?>> classSet = new HashSet<Class<?>>();
for(Class<?> cls : CLASS_SET){
if(cls.isAnnotationPresent(annotationClass)){
classSet.add(cls);
}
}
return classSet;
}
}
3.使用ClassHelper类可以获取所有加载的类,这里提供一个反射工具类,通过反射来实例化相应的类
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReflectionUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionUtil.class);
/*
* 创建实例
*/
public static Object newInstance(Class<?> cls){
Object instance;
try {
instance = cls.newInstance();
} catch (Exception e) {
LOGGER.error("new instance failure",e);
throw new RuntimeException(e);
}
return instance;
}
/*
* 调用方法
*/
public static Object invokeMethod(Object obj,Method method,Object...args){
Object result;
try {
method.setAccessible(true);
result = method.invoke(obj, args);
} catch (Exception e) {
LOGGER.error("invoke method failure", e);
throw new RuntimeException(e);
}
return result;
}
//设置成员变量的值
public static void setField(Object obj,Field field,Object value){
field.setAccessible(true);
try {
field.set(obj, value);
} catch (Exception e) {
LOGGER.error("set field failure",e);
throw new RuntimeException(e);
}
}
4.该工具类用于生成bean类和bean实例的映射关系
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.smart4j.framework.utils.ReflectionUtil;
public class BeanHelper {
//用于存放bean类及bean实例之间的映射关系
private static final Map<Class<?>,Object> BEAN_MAP = new HashMap<Class<?>,Object>();
static{
Set<Class<?>> beanClassSet = ClassHelper.getBeanClassSet();
for(Class<?> cls : beanClassSet){
Object obj = ReflectionUtil.newInstance(cls);
BEAN_MAP.put(cls, obj);
}
}
/*
* 获取bean映射
*/
public static Map<Class<?>,Object> getBeanMap(){
return BEAN_MAP;
}
/*
* 获取bean实例
*/
public static <T> T getBean(Class<T> cls){
if(!BEAN_MAP.containsKey(cls)){
throw new RuntimeException("can not get bean by class");
}
return (T)BEAN_MAP.get(cls);
}
public static void setBean(Class<?> cls,Object obj){
BEAN_MAP.put(cls, obj);
}
}
5.实现依赖注入
import java.lang.reflect.Field;
import java.util.Map;
import org.smart4j.framework.annotation.Inject;
import org.smart4j.framework.utils.ArrayUtil;
import org.smart4j.framework.utils.ReflectionUtil;
public class IocHelper {
static{
//获取bean类及bean实例之间的映射关系
Map<Class<?>,Object> beanMap = BeanHelper.getBeanMap();
for(Map.Entry<Class<?>,Object> entry: beanMap.entrySet()){
//获取bean类
Class<?> beanClass = entry.getKey();
//获取bean实例
Object beanInstance = entry.getValue();
//通过反射机制获取bean类的所有成员变量
Field[] fields = beanClass.getDeclaredFields();
if(ArrayUtil.isNotEmpty(fields)){
for(Field field : fields){
//如果该成员变量有Inject注解,则注入该bean实例
if(field.isAnnotationPresent(Inject.class)){
Class<?> beanFieldClass = field.getType();
Object beanFiledInstance = beanMap.get(beanFieldClass);
//通过反射注入该成员变量的实例
ReflectionUtil.setField(beanInstance, field, beanFiledInstance);
}
}
}
}
}
}
总结:
其实整个IOC的实现逻辑并不复杂,只是工具类写的很多,实际上有以下大概几步:
(1)首先获取所有bean类及bean实例的映射关系,本文实现方式:首先扫描并获取应用包下的所有类;
(2)然后获取需要管理的bean类,这里认为所有拥有service注解和controller注解的类都是需要管理的bean类;
(3)然后获取bean类及其bean的映射关系(通过反射)——beanMap结构,这里也是为了后来注入时方便;
(4)然后遍历beanMap,扫面每一个bean里的成员变量,查看是否拥有Inject注解,如果拥有,则通过反射注入。
本文实现的IOC当然和Spring的还有很大差距,重点是让大家了解如何使用注解实现IOC的,另外本文大量参考了《架构探险,从零开始写java web 框架》。