一、背景介绍
结合上回说的开门小例子的学习(开门小例子学习十种用例图),本次将开门小例子实例化的部分通过自定义注解+注解解析器来实现一把
二、思路&方案
原始业务:
- 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配置文件——>注解;每一次的跨越都是如此的伟大,也对应了时代的需求和人类思想的伟大
五、升华
把原本透明化的东西明确了下来,以便于为后面做到明奠定基础