一、背景介绍
结合上回说的开门小例子的学习(开门小例子学习十种用例图),本次将开门小例子实例化的部分通过自定义注解+注解解析器来实现一把
二、思路&方案
原始业务:
- 1.client 调用 Notice
- 2.Notice中实例化米老师,何老师
- 3.Notice调用米老师”开门“方法,将何老师传入
- 4.米老师内部调用何老师开门方法
- 5.何老师真正执行开门的操作
第一变:
将所有手动new的对象都改造成所在类的属性
第二变:
定义注解:
- 1.定义需要被实例化的注解标记(MyComponent通过反射进行实例化、MyBean通过类中的方法进行实例化)
- 2.定义注入的注解标记(MyResource通过反射将实例赋值给对象的属性)
- 3.定义扫描包中类文件中注解,的注解(MyComponentScan通过这个注解所在的类为扫描的边界)
第三变:
1.将注解写到对应的地方
第四变:
- 1.编写注解标记的实现(通过反射)
- 1.1.读取MyComponentScan注解,判断要扫描的文件夹位置
- 1.2.读取标记位下所有的文件,遍历每一个文件(进行实例化,将实例化内容放到map中)
1.2.1.判断存在MyComponent注解,就通过反射将该类进行实例化,并且将最终实例化的对象添加到map中
1.2.2.判断存在MyBean注解,通过反射执行该注解所在的方法,获得实例将对象添加到map中
- 1.3.将每个类中存在MyResource注解的属性,获取map中的实例进行赋值
三、过程
原始例子代码:
package oldMark; public class Client { public static void main(String[] args) { Notice notice = new Notice(); notice.send(); } }
package oldMark; public class Notice { public void send(){ this.privateSend(); } private void privateSend(){ Milaoshi milaoshi = new Milaoshi("米老师"); milaoshi.OpenDoor(new Helaoshi("何老师")); } }
package oldMark; public class Milaoshi { public Milaoshi(String name) { this.name = name; } private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private void MiOpenDoor(Helaoshi helaoshi){ System.out.println(this.name+"调用"+helaoshi.getName()+"开门的方法"); helaoshi.OpenDoor(); } public void OpenDoor(Helaoshi helaoshi){ this.MiOpenDoor(helaoshi); } }
package oldMark; public class Helaoshi { public Helaoshi(String name) { this.name = name; } public String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void OpenDoor(){ System.out.println("我是"+this.name+",我去开门了!"); } }
加上注解和注解解析器的代码:
package oldMark4; import oldMark4.util.*; /** * 1.将所有手动new的对象都改造成该类的属性 */ @MyComponent @MyComponentScan("oldMark4") public class Client { //这里为了让main调用,所以把它用static修饰; 做了对应优化,将Client对象也放到了map集合中,获取Client对象之后执行 //TODO 这里的属性应该使用private进行标记,这里用public是为了临时解决赋值报错的问题,后续还需要优化调整 @MyResource("notice") public Notice notice; public static void main(String[] args)throws Exception{ SpringRun.run(new Client()); Client client = (Client) IOCContainer.get("client"); client.notice.send(); } }
package oldMark4; import oldMark4.util.MyComponent; import oldMark4.util.MyResource; @MyComponent public class Notice { //TODO 这里的属性应该使用private进行标记,这里用public是为了临时解决赋值报错的问题,后续还需要优化调整 @MyResource("milaoshi") public Milaoshi milaoshi; //TODO 这里的属性应该使用private进行标记,这里用public是为了临时解决赋值报错的问题,后续还需要优化调整 @MyResource("helaoshi") public Helaoshi helaoshi; public void send(){ this.privateSend(); } private void privateSend(){ // Milaoshi milaoshi = new Milaoshi("米老师"); milaoshi.setName("米老师"); // milaoshi.OpenDoor(new Helaoshi("何老师")); helaoshi.setName("何老师"); milaoshi.OpenDoor(helaoshi); } }
package oldMark4; import oldMark4.util.MyComponent; @MyComponent public class Milaoshi { public Milaoshi(String name) { this.name = name; } public Milaoshi(){} private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } private void MiOpenDoor(Helaoshi helaoshi){ System.out.println(this.name+"调用"+helaoshi.getName()+"开门的方法"); helaoshi.OpenDoor(); } public void OpenDoor(Helaoshi helaoshi){ this.MiOpenDoor(helaoshi); } }
package oldMark4; import oldMark4.util.MyBean; public class Helaoshi { public Helaoshi(String name) { this.name = name; } public Helaoshi(){} @MyBean("helaoshi") public Helaoshi getHelaoshiBean(){ return new Helaoshi(); } public String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void OpenDoor(){ this.HeOpenDoor(); } private void HeOpenDoor(){ System.out.println("我是"+this.name+",我去开门了!"); } }
package oldMark4.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyComponentScan { String value(); }
package oldMark4.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface MyComponent { }
package oldMark4.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyBean { String value(); }
package oldMark4.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface MyResource { String value(); }
package oldMark4.util; import java.util.concurrent.ConcurrentHashMap; public class IOCContainer { private static ConcurrentHashMap<String,Object> objectHashMap = new ConcurrentHashMap<String,Object>(); public static void put(String key,Object value){ objectHashMap.put(key,value); } public static Object get(String key){ return objectHashMap.get(key); } }
package oldMark4.util; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URL; import java.util.HashSet; public class SpringRun { /** * 1.扫描MyComponentScan类 */ public static void run(Object c) throws Exception{ String scanPackage = getScanPackage(c); HashSet<String> scanSet = new HashSet<>(); doScanPackage(c,scanSet,scanPackage); // 遍历扫描包下的所有类,进行实例化操作 //TODO 这个循环中的代码,你能看到的可以优化的点是什么? for (String className : scanSet) { // 通过类的全限定名获取 Class Class<?> clazz = Class.forName(className); //1.通过类上的MyComponent注解实现实例化操作 // 判断该类是否实现了 MyComponent 注解 if (clazz.isAnnotationPresent(MyComponent.class)) { // 方式1:通过构造器实例化 IOCContainer.put(classNameSUb(className), clazz.newInstance()); // TODO 这里的代码少写东西了吗? } //2.通过方法中的MyBean注解实现实例化操作 Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { // 判断方法是否有 MyBean 注解 if (method.isAnnotationPresent(MyBean.class)) { // 获取 bean 值 String beanName = method.getAnnotation(MyBean.class).value(); // 判断该方法是否是静态方法或实例方法 if (Modifier.isStatic(method.getModifiers())) { // 方式2:通过静态工厂实例化 IOCContainer.put(beanName, method.invoke(null)); } else { // 方式3:通过实例工厂实例化 // 首先获取该类的实例对象,再调用实例方法进行实例化 // TODO 问题:这里已经通过clazz.newInstance() 拿到该类的对象了,为啥还要再调用方法实例化? IOCContainer.put(beanName, method.invoke(clazz.newInstance())); } } } } // 遍历扫描包下的所有类,进行属性赋值操作 for (String className : scanSet) { // 通过类的全限定名获取 Class Class<?> clazz = Class.forName(className); Field[] fields = clazz.getFields(); for (Field field:fields) { if(field.isAnnotationPresent(MyResource.class)){ String beanName = field.getAnnotation(MyResource.class).value(); setField(IOCContainer.get(classNameSUb(className)),beanName.replace(".",""), IOCContainer.get(beanName)); } } } } private static String lowerFirst(String str) { // 同理 char[] cs=str.toCharArray(); cs[0]+=32; return String.valueOf(cs); } private static String classNameSUb(String className){ return lowerFirst(className.substring(className.lastIndexOf(".")+1,className.length())); } private static void setField(Object obj, String name, Object value) { try { Field field = obj.getClass().getDeclaredField(name); field.setAccessible(true); field.set(obj, value); } catch (Exception e) { e.printStackTrace(); } } private static String getScanPackage(Object c) { Class<?> clazz = c.getClass(); if (!clazz.isAnnotationPresent(MyComponentScan.class)) { return ""; } MyComponentScan scanPackage = clazz.getDeclaredAnnotation(MyComponentScan.class); return scanPackage.value(); } private static void doScanPackage(Object c,HashSet<String> classPathSet, String scanPackage) { // 通过正则表达式将包名中的 . 替代为 /,并获取到该路径的 class url URL url = c.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/")); // 获取该 url 下的所有 File(目录/文件) File classDir = new File(url.getFile()); // 遍历所有 File for (File file : classDir.listFiles()) { // 判断该 file 如果是目录的话 if (file.isDirectory()) { // 拼接该目录的名字并递归遍历该目录 doScanPackage(c,classPathSet, scanPackage + "." + file.getName()); } else { // 如果文件不是以 .class 结尾 if (!file.getName().endsWith(".class")) { continue; } // 通过 包名+目录名+除去.class的类名 拼接该类的全限定名 String clazzName = (scanPackage + "." + file.getName().replace(".class", "")); // 将该类的全限定名放入 classPathSet classPathSet.add(clazzName); } } } }
四、总结
- 1.对于自定义注解有了更加深刻的认识与了解
- 2.对于注解的作用、意义,品味到它的一部分内涵了
- 3.从原始的代码中写死——>xml配置文件——>注解;每一次的跨越都是如此的伟大,也对应了时代的需求和人类思想的伟大
五、升华
把原本透明化的东西明确了下来,以便于为后面做到明奠定基础