框架技术之对象池与注入

有用过spring的朋友肯定知道,spring里面是有对象注入的,通过@Autowired或者是@Resource来注入对象,称之为控制反转的东西。这个东西确实是挺不错的,咋一看上去,这似乎看上去很神奇,但是它的基本原理其实并不复杂,今天我们就自己写一个出来。

OK,首先我们要写个注解类,用来标示,不用太复杂的

package com.kaibes.object.annotation;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;

@Target({ ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface BesResource {
	String value() default "";
}

这样一个注解就写好了,那么接下来,就要写一个过滤器,这是一个工具,用于过滤我们的注解

package com.kaibes.object.code;

import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class BesClassScanning {

	/**
	 * 获取某包下(不包括该包的所有子包)所有类
	 * 
	 * @param packageName
	 *            包名
	 * @return 类的完整名称
	 */
	public static List<String> getClassNameWithoutChild(String packageName) {
		return getClassName(packageName, false);
	}

	/**
	 * 获取某包下(包括该包的所有子包)所有类
	 * 
	 * @param packageName
	 *            包名
	 * @return 类的完整名称
	 */
	public static List<String> getClassNameWithChild(String packageName) {
		return getClassName(packageName, true);
	}

	/**
	 * 获取某包下所有类
	 * 
	 * @param packageName
	 *            包名
	 * @param withChild
	 *            是否遍历子包
	 * @return 类的完整名称
	 */
	public static List<String> getClassName(String packageName, boolean withChild) {
		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(), packageName, withChild);
			} else if (type.equals("jar")) {
				fileNames = getClassNameByJar(url.getPath(), withChild);
			}
		} else {
			fileNames = getClassNameByJars(((URLClassLoader) loader).getURLs(), packagePath, withChild);
		}
		return fileNames;
	}

	/**
	 * 从项目文件获取某包下所有类
	 * 
	 * @param filePath
	 *            文件路径
	 * @param packageName
	 *            包名
	 * @param withChild
	 *            是否遍历子包
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByFile(String filePath, String packageName, boolean withChild) {
		List<String> myClassName = new ArrayList<String>();
		File file = new File(filePath);
		File[] childFiles = file.listFiles();
		for (File childFile : childFiles) {
			if (childFile.isDirectory()) {
				if (withChild) {
					myClassName.addAll(getClassNameByFile(childFile.getPath(), packageName + "." + childFile.getName(), withChild));
				}
			} else {
				String childFilePath = childFile.getPath();
				if (!childFilePath.contains("$") && childFilePath.endsWith(".class")) {
					String name = childFile.getName();
					name = packageName + "." + name.substring(0, name.length() - 6);
					myClassName.add(name);
				}
			}
		}

		return myClassName;
	}

	/**
	 * 从jar获取某包下所有类
	 * 
	 * @param jarPath
	 *            jar文件路径
	 * @param withChild
	 *            是否遍历子包
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByJar(String jarPath, boolean withChild) {
		List<String> myClassName = new ArrayList<String>();
		String[] jarInfo = jarPath.split("!");
		String jarFilePath = jarInfo[0].substring(jarInfo[0].indexOf("/"));
		String packagePath = jarInfo[1].substring(1);

		try {
			JarFile jarFile = new JarFile(jarFilePath);
			Enumeration<JarEntry> entrys = jarFile.entries();
			while (entrys.hasMoreElements()) {
				JarEntry jarEntry = entrys.nextElement();
				String entryName = jarEntry.getName();

				if (!entryName.contains("$") && entryName.endsWith(".class")) {
					if (withChild) {
						if (entryName.startsWith(packagePath)) {
							entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
							myClassName.add(entryName);
						}
					} else {
						int index = entryName.lastIndexOf("/");
						String myPackagePath;
						if (index != -1) {
							myPackagePath = entryName.substring(0, index);
						} else {
							myPackagePath = entryName;
						}
						if (myPackagePath.equals(packagePath)) {
							entryName = entryName.replace("/", ".").substring(0, entryName.lastIndexOf("."));
							myClassName.add(entryName);
						}
					}
				}
			}
			jarFile.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return myClassName;
	}

	/**
	 * 从所有jar中搜索该包,并获取该包下所有类
	 * 
	 * @param urls
	 *            URL集合
	 * @param packagePath
	 *            包路径
	 * @param withChild
	 *            是否遍历子包
	 * @return 类的完整名称
	 */
	private static List<String> getClassNameByJars(URL[] urls, String packagePath, boolean withChild) {
		List<String> myClassName = new ArrayList<String>();
		if (urls != null) {
			for (int i = 0; i < urls.length; i++) {
				URL url = urls[i];
				String urlPath = url.getPath();
				// 不必搜索classes文件夹
				if (urlPath.endsWith("classes/")) {
					continue;
				}
				String jarPath = urlPath + "!/" + packagePath;
				myClassName.addAll(getClassNameByJar(jarPath, withChild));
			}
		}
		return myClassName;
	}
}

嗯,这样过滤器也写好了,那么接下来我们要写一个类解析器,用来解析我们的类

package com.kaibes.object.code;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import com.kaibes.object.annotation.BesResource;

public interface BesClassParser {

	public static List<Class<?>> getMXClass(String packgeName) {
		List<String> clazzList = BesClassScanning.getClassNameWithChild(packgeName);
		List<Class<?>> classList = new ArrayList<>();
		for (String clazzName : clazzList) {
			try {
				Class<?> clazzC = Class.forName(clazzName);
				if (clazzC.isAnnotationPresent(BesResource.class)) {
					classList.add(clazzC);
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
		return classList;
	}

	public static List<Method> getMXMethod(Class<?> clazz) {
		Method[] methods = clazz.getDeclaredMethods();

		List<Method> methodList = new ArrayList<>();
		for (Method method : methods) {
			if (method.isAnnotationPresent(BesResource.class)) {
				methodList.add(method);
			}
		}
		return methodList;
	}

	public static List<Field> getMXField(Class<?> clazz) {
		Field[] fields = clazz.getDeclaredFields();
		List<Field> fieldList = new ArrayList<>();
		for (Field field : fields) {
			if (field.isAnnotationPresent(BesResource.class)) {
				fieldList.add(field);
			}
		}
		return fieldList;
	}
}

如此一来,我们需要的东西也都准备好了,接下来就要进行组装了,我们通过注解,先过滤需要的类出来,然后解析这些类,所以接下来我们写一个处理类,就是对象上下文

package com.kaibes.object;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.kaibes.object.annotation.BesResource;
import com.kaibes.object.code.BesClassParser;

public class BesObjectContext {

	private static BesObjectContext instance;

	private Map<String, ObjectPlan> namePlanMap = new HashMap<>();
	private Map<String, ObjectPlan> typePlanMap = new HashMap<>();

	public static void run(Class<?> clazz) {
		run(clazz.getPackage().getName());

	}

	public static void run(String packgeName) {
		instance = new BesObjectContext();
		List<Class<?>> classList = BesClassParser.getMXClass(packgeName);
		for (Class<?> clazzC : classList) {
			try {
				Object obj = clazzC.newInstance();
				List<Method> methods = BesClassParser.getMXMethod(clazzC);
				for (Method method : methods) {
					instance.addPlan(obj, method);
				}
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

	}

	@SuppressWarnings("unchecked")
	public static <T> T getValue(String name) {
		ObjectPlan plan = instance.namePlanMap.get(name);
		if (plan != null) {
			return (T) plan.getValue();
		}
		return null;
	}

	public static void clearValue(String name) {
		if (instance.namePlanMap.containsKey(name)) {
			instance.namePlanMap.get(name).value = null;
		}
	}

	public static Object getValue(Class<?> type) {
		ObjectPlan plan = instance.typePlanMap.get(type.getName());
		if (plan == null) {
			plan = instance.addPlan(type);
		}
		return plan.value;
	}

	public static void clearValue(Class<?> clazz) {
		if (instance.typePlanMap.containsKey(clazz.getName())) {
			instance.typePlanMap.remove(clazz.getName());
		}
	}

	public static void initValue(Object obj) {
		List<Field> fields = BesClassParser.getMXField(obj.getClass());
		for (Field field : fields) {
			BesResource resource = field.getDeclaredAnnotation(BesResource.class);
			String name = field.getName();
			if (!resource.value().isEmpty()) {
				name = resource.value();
			}

			try {
				boolean accessible = field.isAccessible();
				field.setAccessible(true);
				Object value = getValue(name);
				if (value == null) {
					value = getValue(field.getType());
				}
				field.set(obj, value);
				field.setAccessible(accessible);
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	private ObjectPlan addPlan(Class<?> type) {
		ObjectPlan plan = null;
		try {
			Object value = type.newInstance();
			plan = new ObjectPlan();
			plan.value = value;
			typePlanMap.put(type.getName(), plan);
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return plan;
	}

	private void addPlan(Object obj, Method method) {
		ObjectPlan plan = new ObjectPlan();
		plan.obj = obj;
		plan.createMethod = method;

		BesResource resource = method.getDeclaredAnnotation(BesResource.class);
		String name = method.getName();
		if (!resource.value().isEmpty()) {
			name = resource.value();
		}
		namePlanMap.put(name, plan);
	}

	private class ObjectPlan {
		private Object obj;
		private Method createMethod;
		private Object value;

		private Object getValue() {
			if (value == null) {
				try {
					Parameter[] ps = createMethod.getParameters();
					Object[] objs = new Object[ps.length];
					for (int i = 0; i < ps.length; i++) {
						Parameter parameter = ps[i];
						String name = parameter.getName();
						if (parameter.isAnnotationPresent(BesResource.class)) {
							BesResource resource = parameter.getDeclaredAnnotation(BesResource.class);
							String value = resource.value();
							if (!value.isEmpty()) {
								name = value;
							}
						}
						Object obj = BesObjectContext.getValue(name);
						if (obj == null) {
							obj = BesObjectContext.getValue(parameter.getType());
						}
						objs[i] = obj;
					}
					value = createMethod.invoke(obj, objs);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (InvocationTargetException e) {
					e.printStackTrace();
				}
			}
			return value;
		}
	}
}

至此我们的对象注入库已经完全写完了,那么要怎么进行使用呢,接下来我们进行测试

首先是写个普通的数据类

package com.kaibes.object.data;

public class UserData {

	private String username = "aaa";
	private String password = "bbb";

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}

然后再写一个创建类,用来创建数据的,在spring里面它可以直接写在xml配置文件里,我并不喜欢这种方法,xml总让我觉得难以接受,这里我们直接就写在普通的java类里

package com.kaibes.object;

import java.util.ArrayList;
import java.util.List;

import com.kaibes.object.annotation.BesResource;
import com.kaibes.object.data.UserData;

@BesResource
public class Creater {

	@BesResource
	public UserData user1() {
		UserData data = new UserData();
		data.setUsername("yolinfeng");
		data.setPassword("123456");
		return data;
	}

	@BesResource
	public UserData user2(@BesResource("user1") UserData user1) {
		UserData data = new UserData();
		data.setUsername(user1.getUsername() + "2");
		data.setPassword(user1.getPassword() + "2");
		return data;
	}

	@BesResource
	public List<UserData> userDatas(@BesResource("user1") UserData arg1, @BesResource("user2") UserData arg2) {
		List<UserData> userDatas = new ArrayList<>();
		userDatas.add(arg1);
		userDatas.add(arg2);
		return userDatas;
	}

}

ok,上面这个类写了3个方法,第一个方法很普通的创建了一个userdata对象,第二个方法有个参数它引用了对象池里一个叫“user1”的对象,第三个方法它同时引用了user1和user2

值得一提的是,这里面其实可以进行,简化的比如user2这个方法其实可以写成

public UserData user2(UserData user1) {

而不需要显式地注入,为什么呢?因为在java8里面是可以获取到参数的名字的,于是这里可以不用注入,而直接由参数的名字去决定,这样会更加优雅。

如果你使用的是eclipse,那你可以打开工程的属性,然后点开java compiler ==> enable project specific settings ==> store information about method parameters , 这样就可以获得方法的参数名字了

ok,接着我们继续写一个控制器

package com.kaibes.object.controller;

import java.util.List;

import com.kaibes.object.BesObjectContext;
import com.kaibes.object.annotation.BesResource;
import com.kaibes.object.data.UserData;

public class Controller {

	@BesResource
	private List<UserData> userDatas;
	@BesResource("userDatas")
	private List<UserData> userDatas2;

	public Controller() {
		//注入数据,这个可以被AOP技术替换掉
		BesObjectContext.initValue(this);
	}
	
	public void say() {
		System.out.println("hello world!" + userDatas.get(0).getUsername());
		System.out.println("hello world!" + userDatas.get(1).getUsername());
		System.out.println("hello world!" + userDatas.get(0).getPassword());
		System.out.println("hello world!" + userDatas.get(1).getPassword());
	}
	
	public String getHello() {
		return "hello "+userDatas.get(0).getUsername();
	}

}

控制器里面注入了两个变量,第一个注入的是userDatas(就是Creater里写的第三个方法),第二个注入的是“userDatas”,也就是指向了第一个变量,其中值得一提的是构造函数里面有一个数据注入的方法,这个方法其实可以被AOP技术替换,比如aspectj就能替换掉这一段,后续我也会写出相应的aspectj代码,这里就先不写了

好了,控制器也写完了,接着我们进行测试

package com.kaibes.object;

import org.junit.Test;

import com.kaibes.object.controller.Controller;

public class MyTest {

	@Test 
	public void testSomeLibraryMethod() {
        BesObjectContext.run(MyTest.class);
		Controller controller = new Controller();
		controller.say();
//		assertEquals("", ");
    }
}

输出结果是

hello world!yolinfeng
hello world!aaa2
hello world!123456
hello world!bbb2

ok,到此结束,代码我已经放到git上了,大家可以直接下源码看

https://gitee.com/yolinfeng/BesObject

转载于:https://my.oschina.net/yolinfeng/blog/1608610

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值