mvc模式是现在流行的一种软件设计,将表现层,业务层和控制层划分开来,使得软件结构更加清晰,各层间的耦合度降低,便于各开发单元协同工作。通过ioc容器,进一步实现各层之间的依赖的解耦,可以大大提高程序可配置性,灵活性,便于扩展和维护。
如何理解并创建一个ioc容器,实现其依赖注入的特性呢?一个ioc容器实际上是一个保存着所有依赖项的集合,也就是所谓的be an的集合,DI的特性其实是容器中依赖项间的关系,即各bean之间的持有关系。容器本身并不知道自己要管理哪些bean,也无法事先将bean准备好,这就需要规定规则,指定容器要管理的bean,以及这些bean存在的位置,通过配置文件来指定这些规则。容器需要有读取配置文件的能力,然后根据配置文件进行bean的管理操作。
public final class ConfigUtil {
/**
* @Description 获得基础包路径
* @Param []
* @Return java.lang.String
* @Author takou
* @Date 2018/4/6
* @Time 上午11:37
*/
public static String getBasePackage() {
return PropUtil.getString(ConfigConstant.BASE_PACKAGE,"org.smartweb");
}
/**
* @Description 获得jsp文件路径
* @Param []
* @Return java.lang.String
* @Author takou
* @Date 2018/4/6
* @Time 上午11:37
*/
public static String getJspPath() {
return PropUtil.getString(ConfigConstant.JSP_PATH,"/WEB-INF/view/");
}
/**
* @Description 获得资源文件路径
* @Param []
* @Return java.lang.String
* @Author takou
* @Date 2018/4/6
* @Time 上午11:38
*/
public static String getAsset() {
return PropUtil.getString(ConfigConstant.ASSET,"/asset");
}
}
框架启动后,容器通过配置文件指定的基础包名,运行时获得包名下的所有要管理的bean,加入到容器中。此时就需要java的运行时技术:反射了。反射的前提首先要获得类的class文件,通过类加载器加载class文件,然后通过反射创建类的实例bean。
/**
* @Description 获得类加载器
* @Param []
* @Return java.lang.ClassLoader
* @Author takou
* @Date 2018/4/4
* @Time 下午8:19
*/
public static ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
/**
* @Description 加载指定类名的类
* @Param [className, isInitialized]
* @Return java.lang.Class
* @Author takou
* @Date 2018/4/4
* @Time 下午8:19
*/
public static Class loadClass(String className, boolean isInitialized) {
Class<?> cls = null;
try {
cls = Class.forName(className, isInitialized, getClassLoader());
} catch (ClassNotFoundException e) {
LOGGER.error(className + " load failure",e);
throw new RuntimeException(e);
}
return cls;
}
/**
* @Description 获得所有的class文件放入集合
* @Param [packageName]
* @Return java.util.Set<java.lang.Class<?>>
* @Author takou
* @Date 2018/4/6
* @Time 上午11:47
*/
public static Set<Class<?>> getClassSet(String packageName) {
Set<Class<?>> classSet = new HashSet<Class<?>>();
//根据包名获得类文件存放的位置
try {
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
if (urls != null) {
for (;urls.hasMoreElements();) {
URL url = urls.nextElement();
String protocol = url.getProtocol();
if ("file".equalsIgnoreCase(protocol)) {
String filePath = url.getPath().replaceAll("%20"," ");
//根据类文件的路径加载其下的所有类文件
addClass(filePath,packageName,classSet);
} else if ("jar".equalsIgnoreCase(protocol)) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
if (jarURLConnection != null) {
JarFile jarFile = jarURLConnection.getJarFile();
Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
if (jarEntryEnumeration != null) {
for (;jarEntryEnumeration.hasMoreElements();) {
JarEntry jarEntry = jarEntryEnumeration.nextElement();
String jarEntryName = jarEntry.getName();
if (jarEntryName.endsWith(".class")) {
String className = jarEntryName.substring(0,jarEntryName.lastIndexOf("."))
.replaceAll("/",".");
doAddClass(className,classSet);
}
}
}
}
}
}
}
} catch (IOException e) {
LOGGER.error("get resources failure by " + packageName,e);
throw new RuntimeException(e);
}
return classSet;
}
/**
* @Description 根据类文件获得类全限定名
* @Param [packagePath, packageName, classSet]
* @Return void
* @Author takou
* @Date 2018/4/6
* @Time 上午11:48
*/
public static void addClass(String packagePath, String packageName, Set<Class<?>> classSet) {
File[] files = new File(packagePath).listFiles(new FileFilter() {
public boolean accept(File pathname) {
return (pathname.isFile() && pathname.getName().endsWith(".class")) || pathname.isDirectory();
}
});
for (File f : files) {
String fileName = f.getName();
if (f.isFile()) {
String className = fileName.substring(0,fileName.lastIndexOf("."));
className = packageName + "." + className;
doAddClass(className,classSet);
} else {
String subPackagePath = fileName;
if (StringUtils.isNotEmpty(subPackagePath)) {
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (StringUtils.isNotEmpty(subPackageName)) {
subPackageName = packageName + "." + subPackageName;
}
addClass(subPackagePath,subPackageName,classSet);
}
}
}
/**
* @Description 实例bean,放入容器
* @Param [className, set]
* @Return void
* @Author takou
* @Date 2018/4/6
* @Time 上午11:49
*/
public static void doAddClass(String className, Set<Class<?>> set) {
set.add(loadClass(className,false));
}
类文件的获取,既包括了单个的.class文件,同时也会有打包后的jar文件,由基础包名替换分割符后获得相对文件路径后,递归的检查该目录下的所有目录,找到.class文件或者jar文件,实例化并加入容器。但是在通过扫描基础包路径的方式会加载一些无关的class文件进入内存,会有一些不准确,在这种方式下,所有的bean实际上都是单例的,因为只有一次,且只实例化了一个。
依赖注入(采用注解方式)的实现,就是将容器中的bean建立起本来的依赖关系,即为bean的依赖属性设值。可以在第一次或取bean时进行注入,也可在容器创建时就注入。在使用getbean()获得bean时,若容器还没有该bean实例则进行实例化,并对其依赖项进行注入,检查bean的所有属性,有依赖注入注解标示的,获得依赖项实例,为属性赋值,其过程是一个递归的过程。
/**
* @Description 初始化bean容器
* @Param []
* @Return void
* @Author takou
* @Date 2018/4/6
* @Time 下午12:11
*/
private static void initBeanSet() {
Set<Class<?>> set = ClassUtil.getClassSet(ConfigUtil.getBasePackage());
for (Class cls : set) {
if (cls.isAnnotationPresent(Controller.class)) {
controllerSet.add(cls);
} else if (cls.isAnnotationPresent(Service.class)) {
serviceSet.add(cls);
}
}
beanSet.addAll(controllerSet);
beanSet.addAll(serviceSet);
}
/**
* @Description 从容器中获得bean
* @Param [cls]
* @Return T
* @Author takou
* @Date 2018/4/6
* @Time 下午12:11
*/
public static <T> T getBean(Class<?> cls) {
T bean = null;
if (beanSet.contains(cls) && beanMap.containsKey(cls)) {
bean = (T) beanMap.get(cls);
} else if (beanSet.contains(cls) && !beanMap.containsKey(cls)) {
bean = initBean(cls);
}
return bean;
}
/**
* @Description 实例化bean
* @Param [cls]
* @Return T
* @Author takou
* @Date 2018/4/6
* @Time 下午12:12
*/
private static <T> T initBean(Class<?> cls) {
T obj = (T) ClassUtil.newInstance(cls);
beanMap.put(cls,obj);
initDependency(cls,obj);
return obj;
}
/**
* @Description bean的依赖注入
* @Param [cls, obj]
* @Return void
* @Author takou
* @Date 2018/4/6
* @Time 下午12:12
*/
private static <T> void initDependency(Class<?> cls, T obj) {
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AutoInject.class)) {
Object bean = getBean(field.getType());
if (obj == null) {
bean = initBean(field.getType());
}
field.setAccessible(true);
try {
field.set(obj,bean);
} catch (IllegalAccessException e) {
LOGGER.error("illegal access",e);
throw new RuntimeException(e);
}
}
}
}
所有加载过程基于静态块完成,因此在容器启动时,只需加载容器的类,在执行容器类的静态块时,完成所有操作。
/**
* @PROJECT_NAME smartweb
* @PACKAGE_NAME org.smartweb.container
* @USER takou
* @CREATE 2018/4/5
**/
public final class BeanContainerBuilder {
private static Class<?>[] containers = new Class<?>[]{BeanContainer.class,WebController.class};
/**
* @Description 构建bean容器
* @Param []
* @Return void
* @Author takou
* @Date 2018/4/6
* @Time 下午12:17
*/
public static void buildWebContainer() {
for (Class<?> cls : containers) {
ClassUtil.loadClass(cls.getName(),true);
}
}
}
spring采用的xml配置方式,对依赖进行配置,能更灵活的进行依赖项的变更和注入,只不过进行框架配置的工作量就更大了,同时spring也提供了纯注解的开发方式,开发简便,但失去了一些灵活性。
去掉程序中的new关键字,通过配置实现功能的插拔式装配,让功能扩展和变更不再困难,整个就像拼接过程,更加简洁轻快。