有用过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