自定义注解和注解解析器

简介: 结合上回说的开门小例子的学习(开门小例子学习十种用例图),本次将开门小例子实例化的部分通过自定义注解+注解解析器来实现一把

一、背景介绍


结合上回说的开门小例子的学习(开门小例子学习十种用例图),本次将开门小例子实例化的部分通过自定义注解+注解解析器来实现一把


二、思路&方案


原始业务:


  • 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配置文件——>注解;每一次的跨越都是如此的伟大,也对应了时代的需求和人类思想的伟大


五、升华


把原本透明化的东西明确了下来,以便于为后面做到明奠定基础

相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
216 0
|
2月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
219 2
|
19天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
59 2
|
19天前
|
前端开发 Java Spring
探索Spring MVC:@Controller注解的全面解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序的基石之一。它不仅简化了控制器的定义,还提供了一种优雅的方式来处理HTTP请求。本文将全面解析`@Controller`注解,包括其定义、用法、以及在Spring MVC中的作用。
40 2
|
19天前
|
前端开发 Java 开发者
Spring MVC中的控制器:@Controller注解全解析
在Spring MVC框架中,`@Controller`注解是构建Web应用程序控制层的核心。它不仅简化了控制器的定义,还提供了灵活的请求映射和处理机制。本文将深入探讨`@Controller`注解的用法、特点以及在实际开发中的应用。
51 0
|
2月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
48 0
|
2月前
|
程序员 开发者 Python
深度解析Python中的元编程:从装饰器到自定义类创建工具
【10月更文挑战第5天】在现代软件开发中,元编程是一种高级技术,它允许程序员编写能够生成或修改其他程序的代码。这使得开发者可以更灵活地控制和扩展他们的应用逻辑。Python作为一种动态类型语言,提供了丰富的元编程特性,如装饰器、元类以及动态函数和类的创建等。本文将深入探讨这些特性,并通过具体的代码示例来展示如何有效地利用它们。
52 0
|
4月前
|
域名解析 网络协议 API
【API管理 APIM】APIM集成内部VNet时,常遇见的关于自定义DNS服务问题。
【API管理 APIM】APIM集成内部VNet时,常遇见的关于自定义DNS服务问题。
|
5月前
|
负载均衡 Java Spring
@EnableFeignClients注解源码解析
@EnableFeignClients注解源码解析
90 14
|
4月前
|
开发者 监控 开发工具
如何将JSF应用送上云端?揭秘在Google Cloud Platform上部署JSF应用的神秘步骤
【8月更文挑战第31天】本文详细介绍如何在Google Cloud Platform (GCP) 上部署JavaServer Faces (JSF) 应用。首先,确保已准备好JSF应用并通过Maven构建WAR包。接着,使用Google Cloud SDK登录并配置GCP环境。然后,创建`app.yaml`文件以配置Google App Engine,并使用`gcloud app deploy`命令完成部署。最后,通过`gcloud app browse`访问应用,并利用GCP的监控和日志服务进行管理和故障排查。整个过程简单高效,帮助开发者轻松部署和管理JSF应用。
63 0

推荐镜像

更多