实战:基于自定义注解实现自定义框架Spring

本文介绍了如何通过自定义注解实现Spring框架,详细阐述了自定义注解的原理,元注解的作用,以及Spring中常用注解的定义。在实践中,创建了类似Spring的BeanFactory,通过反射读取XML配置,扫描指定包下带有自定义注解的类,并进行实例化、依赖注入和事务管理等操作。

实战:基于自定义注解实现自定义框架Spring


一、自定义注解介绍

1.1 通过反射API,可以判断一个类、接口、字段或者方法上是否有注解

Class类(java.lang包下)中提供了一些方法用于反射注解

  • //返回指定的注解
    getAnnotation
  • //判断当前元素是否被指定注解修饰
    isAnnotationPresent
  • //返回所有的注解
    getAnnotations
例如:判断一个类上是否有注解

在类TransferServiceImpl 上添加自定义注解@MyService

@MyService("myTransferServiceImpl")
public class TransferServiceImpl implements TransferService {

}

通过反射,判断TransferServiceImpl类上是否有注解,如果有就获取注解和注解的value

//1.获取某个类的字节码对象
Class<?> aClass = TransferServiceImpl.this.getClass();
//2.判断当前类上是否有注解
if(aClass.isAnnotationPresent(MyService.class)){
	//返回true,表示TransferServiceImpl类上有指定的MyService注解
	//3. 获取注解对象,进而获取注解对象的value属性 “myTransferServiceImpl”
	String value = aClass.getAnnotation(MyService.class).value()
}
结论:通过反射可以操作自定义注解

1.2 自定义注解(注解本质就是一个接口)

1.2.1 语法格式:

@元注解   //用于修饰自定义注解
public    @interface   注解名称 {
 
 	//自定义注解属性

}

1.2.2 元注解介绍(用于修饰自定义注解)

1. @Target元注解:表明该注解可以应用的java元素类型

在这里插入图片描述

2. @Retention元注解:表明该注解的生命周期

在这里插入图片描述

3. @Document:表明该注解标记的元素可以被Javadoc 或类似的工具文档化
4. @Inherited:表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解

1.2.3 Spring框架中定义的注解介绍

Service 注解
@Target({ElementType.TYPE})    //应用于类、接口(包括注解类型)、枚举
@Retention(RetentionPolicy.RUNTIME)  //由JVM 加载,包含在类文件中,在运行时可以被获取到
@Documented	//表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Component   //添加了此注解 被ioc容器管理
public @interface Service {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";   //默认的value值

}
Repository 注解
@Target({ElementType.TYPE})   //应用于类、接口(包括注解类型)、枚举
@Retention(RetentionPolicy.RUNTIME)   //由JVM 加载,包含在类文件中,在运行时可以被获取到
@Documented 	//表明该注解标记的元素可以被Javadoc 或类似的工具文档化
@Component   //添加了此注解 被ioc容器管理
public @interface Repository {

	/**
	 * The value may indicate a suggestion for a logical component name,
	 * to be turned into a Spring bean in case of an autodetected component.
	 * @return the suggested component name, if any (or empty String otherwise)
	 */
	@AliasFor(annotation = Component.class)
	String value() default "";   // 默认的value值

}
Autowired 注解
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})  // 应用于构造函数、应用于方法、应用于方法的形参、应用于属性(包括枚举中的常量)、应用于注解类型
@Retention(RetentionPolicy.RUNTIME)   //由JVM 加载,包含在类文件中,在运行时可以被获取到
@Documented 	//表明该注解标记的元素可以被Javadoc 或类似的工具文档化
public @interface Autowired {
    boolean required() default true;  
}

1.2.4 自定义Spring框架的注解

如果我们在自定义注解的时候可以参考spring框架中定义的注解的方式

二、自定义Spring框架 整体实现思路梳理

  1. 自定义工厂类BeanFactory,作为spring ioc核心类
/**
 * 工厂类,生产对象(使用反射技术)
 */
public class BeanFactory {

    /**
     * 任务一:读取解析xml,获取包扫描路径(dao/service类所在的包)
     * 任务二:通过反射技术实例化对象并且存储待用(map集合)
     * 		 获取class/判断自定义注解@ ,决定是否实例化操作
     * 任务三:自定义对象
     * 任务四:判断自定义注解@MyAutoWired,决定是否进行依赖注入操作
     * 任务五:判断自定义注解@MyTransactional,决定是否进行增强操作
     */
	static{
        //任务一:读取解析xml,获取包扫描路径
        getPackage();
        
        //任务二:通过反射技术实例化对象并且存储待用(map集合)
        loadClass(packagePath);
        
        //任务三:实例化
        doInstance();
        
        //任务四:依赖注入
        doAutoWired();
        
        //任务五:增强
        doTransactional();
	}
  1. 自定义注解@MyService
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
    String value() default "";
}
  1. 判断类上 是否有 自定义@MyService,决定是否将当前处理类交给Spring 容器管理

//1 获取aClass类字节码对象

//2 判断类上 是否有 自定义@MyService注解
if (aClass.isAnnotationPresent(MyService.class)) {
		
	  //3 获取注解的value
     if (aClass.isAnnotationPresent(MyService.class)) {
			 annoBeanValue = aClass.getAnnotation(MyService.class).value();
	  }
		
     //4 实例化bean
	  Object obj = aClass.newInstance();
	  
     //5 将实例化之后的对象交给容器管理,key就是注解中指定的value(如果没有指定value,可以指定类名首字母小写作为key)
	  ioc容器.put(annoBeanValue, obj);
}

三、具体编码介绍

public class BeanFactory {

    /**
     * 任务一:读取解析xml,获取包扫描路径
     * 任务二:通过反射技术实例化对象并且存储待用(map集合)
     * 任务三:依赖注入
     * 任务四:增强
     */

    private static Map<String, Object> classMap = new HashMap<>();  // 存储对象

    private static Set<String> classSet = new HashSet<>();  // 存储对象的路径信息
    private static String packagePath;

    static {


        //* 任务一:读取解析xml,获取包扫描路径
        getPackage();

        //* 任务二:通过反射技术实例化对象并且存储待用(map集合)
        loadClass(packagePath);
        
        // 任务三:实例化
        doInstance();
        
        //* 任务四:依赖注入
        doAutoWired();

        //* 任务五:增强
        doTransactional();
    }
}    

任务一:读取解析xml,获取包扫描路径

    private static void getPackage() {
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();

        Document document = null;
        try {
            document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> beanList = rootElement.selectNodes("//component-scan");
            for (int i = 0; i < beanList.size(); i++) {
                Element element = beanList.get(i);
                // 处理每个base-package元素
                packagePath = element.attributeValue("base-package");        // accountDao

                String parentPath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
                System.out.println("parentPath:" + parentPath);

                packagePath = parentPath + packagePath.replace(".", "/");


                System.out.println("packagePath:" + packagePath);
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }

    }

任务二:通过反射技术实例化对象并且存储待用(map集合)

    private static void loadClass(String packagePath) {

        File scanPackage = new File(packagePath);
        //递归扫描路径
        File[] files = scanPackage.listFiles();

        for (File file : files) {
            if (file.isDirectory()) { // 子package
                // 递归
                loadClass(file.getPath());  // com.lagou.demo.controller
            } else if (file.getName().endsWith(".class")) {
                String className = scanPackage + "\\" + file.getName().replaceAll(".class", "");
                className = className.split("classes")[1].replace("\\", ".").substring(1);
                classSet.add(className);
            }

        }
        System.out.println("-------------------------------------------");
        classSet.forEach(path -> System.out.println("path -> " + path));


    }

任务三:实例化

    /**
     * 通过反射实例化对象,此时暂不维护依赖注入关系
     */
    private static void doInstance() {
        classSet.forEach(path -> {
            try {
                //反射
                Class<?> aClass = Class.forName(path);

                //设置存储的key
                String annoBeanValue = null;

                //只处理有如下的注解
                if (aClass.isAnnotationPresent(MyService.class) ||
                        aClass.isAnnotationPresent(MyComponent.class) ||
                        aClass.isAnnotationPresent(MyRepository.class)) {

                    //获取注解的value
                    if (aClass.isAnnotationPresent(MyService.class)) {
                        annoBeanValue = aClass.getAnnotation(MyService.class).value();
                    } else if (aClass.isAnnotationPresent(MyRepository.class)) {
                        annoBeanValue = aClass.getAnnotation(MyRepository.class).value();
                    } else if (aClass.isAnnotationPresent(MyComponent.class)) {
                        annoBeanValue = aClass.getAnnotation(MyComponent.class).value();
                    }


                    //如果没有指定value,则以类型首字母小写为key进行存储
                    if ("".equals(annoBeanValue)) {
                        annoBeanValue = lowerFirst(aClass.getSimpleName());
                    }

                    //实例化bean
                    Object obj = aClass.newInstance();
                    classMap.put(annoBeanValue, obj);


                    //service层往往是有接口的,面向接口开发,此时再以接口名为id,放入一份对象到容器中,便于后期根据接口类型注入

                    Class<?>[] interfaces = aClass.getInterfaces();
                    if(interfaces != null && interfaces.length > 0) {
                        for (int j = 0; j < interfaces.length; j++) {
                            Class<?> anInterface = interfaces[j];
                            // 以接口的全限定类名作为id放入
                            classMap.put(
                                    lowerFirst(anInterface.getName().substring(anInterface.getName().lastIndexOf(".")+1)),
                                    aClass.newInstance());
                        }
                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        });
    }

任务四:依赖注入

    /**
     * 这边可以实现一个简单的循环依赖的处理
     *  A 可能依赖于 B ,B 可能依赖于 C ,C 可能又依赖于D,可以维护一下嵌套依赖
     *  static List<String> fieldsAlreayProcessed = new ArrayList<>(); // 缓存已经进行过依赖注入的信息
     *
     */
    private static void doAutoWired() {
        classMap.forEach((keys, obj) -> {
            System.out.println(keys + " / " + obj);
            Field[] fields = obj.getClass().getFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                if (!field.isAnnotationPresent(MyAutowired.class)) {
                    continue;
                }

                //具有此注解
                try {
                    field.setAccessible(true);
                    String autoWiredFieldName = lowerFirst(field.getType().getName().
                            substring(field.getType().getName().lastIndexOf(".")+1));
                    System.out.println("autoWiredFieldName:"+autoWiredFieldName);
                    field.set(obj,classMap.get(autoWiredFieldName));

                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }


            }



        });

        System.out.println("doAutoWired over");

    }

任务五:增强

    /*
     * 实现事务管理,为添加了@MyTransactional注解的对象创建代理对象,并覆盖原IOC容器中的对象
     */
    private static void doTransactional() {
        //目前只处理在类上添加此注解
        classMap.forEach((keys, obj) -> {
            Class<?> aClass = obj.getClass();

            if(aClass.isAnnotationPresent(MyTransactional.class)) {
                //进行增强
                Class<?>[] interfaces = aClass.getInterfaces();
                if(interfaces != null && interfaces.length > 0) {
                    // 使用jdk动态代理
                    classMap.put(keys,((ProxyFactory)classMap.get("proxyFactory")).getJdkProxy(obj));
                }else{
                    // 使用cglib动态代理
                    classMap.put(keys,((ProxyFactory)classMap.get("proxyFactory")).getCglibProxy(obj));
                }
            }
        });
    }

源码仓库地址:

下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿城大饼

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值