[Day-02] - Annotation Modulize Introduction

Abstract

Annotation的技术风格为Java 5 之後所推畅出来的新模式,并将注解区分为作用在代码上及作用在注解上的注解,亦可使用其他框架套件进行处理这些标签,用於增强或修改程序行为等,另所有开发者所产生的Annotation皆可进行扩展,便於开发者将任何资料或元资料(Metadata)与程序元素(类、方法、成员变数等)进行关联,而Spring框架的三大核心运作思想为IoC(Inversion of Control,控制反转)和DI(Dependency Inject,依赖注入)及面向切面的程序设计(Aspect-oriented programming,AOP),故我们这边先透过原生Java开发一个小程序来达到三大类核心思维,以便读者先了解原生Java元件的运作概念。

Principle Introduction

注解本身就是继承一个@interface的特殊介面接口,处理注解的工具将接收那些实现了这个注解介面的对象,通过java类别反射(Relfection)行为来取得各类别的宣告参数(field)与方法(Method)是否当下有配置注解,并通过getAnnotation方法取得其开发者所配置的注解,其方法最终皆为透过产生一项代理动态物件进行产生调用(invoke)AnnotationInvocationHandler的类别方法,并注入建构子两项参数,分别为开发者所欲取得的Annotation及memberValues集合参数物件,memberValues 为放置注解成员的属性名称和值,该方法会从其中取得memberValues此Map的索引对应值,即可提供给使用者进行各类元数据解析开发。

其片段程序码为

(Annotation)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));

Java 内置了一些常用的注解,其3项在java.lang中,剩下4个在java.lang.annotation中

代码性质注解

Annotation name Descrption
@Override 在某个父类别(class)或实现的接口(interface)中进行配置该方法,并确保当前类别重写了该方法。若没有父类别无该方法,会编译报错。
@Deprecated 可修饰的范围很广,包括类、方法、参数,他表示对应的代码已经过时了,开发者不应该再使用它,编译时会警告。
@SuppressWarnings 表示会忽略编译过程中的警告

作用在注解上的注解

Annotation name Descrption
@Retention 标识这个注解怎麽储存,是只在程序码中,还是编入class档案中,或者是在执行时可以通过反射存取。
@Documented 标记这些注解是否包含在使用者文件中
@Target 标记这个注解应该是哪种 Java 成员[AnnotationType,Constructor,Field,Local_Variable,Method,Module,Package,Parameter,Type,Type_Parameter,Type_use]
@Inherited 标记这个注解是继承於哪个注解类别

Java 7 开始,额外添加了 3 个注解:

Annotation name Descrption
@SafeVarargs Java 7 开始支援,忽略任何使用参数为泛型变数的方法或建构函式呼叫产生的警告。
@FunctionalInterface Java 8 开始支援,标识一个匿名函式或函数式介面。
@Repeatable Java 8 开始支援,标识某注解可以在同一个声明上使用多次。

以下为透过范例程序码仿照Spring框架壳新概念为出发点进行介绍,提供各位开发者进行参考

@Prejump为小编为此套小框架设计的注解模式,运用来配置栏位宣告值与属性,并注入上关联元件的宣告栏位,可以想像成是Spring框架的@Bean注解概念。

@Retention(RetentionPolicy.RUNTIME)
public @interface Prejump {
    String value() default "";
    String name() default "";
}

ObjectCloneHandler.INVOCATION_HANDLER 为一个Map配置池存放开发者所配置注解模式的暂存位置,可以想像成一个Spring IoC BeanFactory配置池概念,Spring采用CGLib进行切面式编程,小编这边采用ProxyInvocation进行切面式编程。

public class ObjectCloneHandler implements InvocationHandler {
    private static volatile ObjectCloneHandler instance = null;
    public static Map<String,Object> INVOCATION_HANDLER = new HashMap<String,Object>();

    public static synchronized ObjectCloneHandler getInstance() {
        if ( instance == null ) {
            instance = new ObjectCloneHandler();
        }
        return instance;
    }


    private Object delegate;

    public Object bind(Object delegate) {
        //透过配置池取得对应元件
        this.delegate = ObjectCloneHandler.getInstance().getInvocationHandler().get(delegate.getClass().getName());;
         return this.delegate;
    }

....

....

}

注册逻辑片段,配置完後存入Prejump配置池中。

// 将取得注册後Prejump的注解模式进行存入配置池中,等待开发者进行宣告注入在进行获取
    private void filterAnnotations(String[] classes) {
        Arrays.stream(classes).forEach( clazz -> {
           try { 
              ......
              ......
              ObjectCloneHandler.getInstance().getInvocationHandler().put(clazz,object);
            } catch (IllegalAccessException illegalAccessException) {
                System.out.println("Core :: error :: IllegalAccessException ! ");
            } catch (ClassNotFoundException classNotFoundException) {
                System.out.println("Core :: error :: classNotFoundException ! ");
            } catch (InstantiationException instantiationException) {
              //  System.out.println("Core :: error :: InstantiationException ! ");
            }
       });

测试片段,进行注入相关元件

public class AnnotationsTest {
    private static final double DELTA = 1e-15;

    MakePizzaService makePizzaService;

    AnimalService animalService;
    @Before
    public void init() {
        ApplicationBoot.getInstance().run(Main.class);
        makePizzaService = (MakePizzaService) ObjectCloneHandler.getInstance().bind(new MakePizzaServiceImpl());
        animalService = (AnimalService) ObjectCloneHandler.getInstance().bind(new AnimalServiceImpl());
    }
    
    
    ...
    ...
    ...
    
    @Test
    public void testShowMyCompany() {
        assertEquals(makePizzaService.showMyCompany(),"YO ! This is WEISTING COMPANY, We have 15.5 pizzas.");
        System.out.println("show my company test pass ! ");
    }
}

测试结果,可发现我们已将@Prejump字串注入对应的元件,提供开发者进行逻辑分析

Testing started at 12:30 上午 ...
> Task :cleanTest
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava UP-TO-DATE
> Task :processTestResources UP-TO-DATE
> Task :testClasses UP-TO-DATE
> Task :test
show my company test pass ! 

透过小编提供的范例仿造一个Spring框架中,并以此运作方式先行提供给开发者了解注解在Spring框架中的强大功用与运作方式。

Structure

Structure Diagram

image

根据每个类别进行取得宣告参数与宣告方法的清单,并呼叫getAnnotation进行取得自身所配置的Annotation类别,其方法将会依照不同的对应代码配置的注解取得方式进行呼叫,各类别的宣告参数类型的注解取得来源为从Field类别中的declaredAnnotations方法,各类别中的方法配置注解取得来源为从Executable类别中declaredAnnotations方法,并统一呼叫AnnotationParser类别中的parseAnnotations、parseAnnotations2及annotationForMap取得其使用者所想获取之注解(Annotation / @interface),其最终呼叫的方法程序码为下面片段。

    public static Annotation annotationForMap(final Class<? extends Annotation> type, final Map<String, Object> memberValues) {
        return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
            public Annotation run() {
                return (Annotation)Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new AnnotationInvocationHandler(type, memberValues));
            }
        });
    }

当每个类别的元数据已充分载入,会将此类别暂存在一个容器中,当开发人员要产生一个实体(Entity)时,我们会将预先产生的实体分配给此使用程序,已达到各类物件依赖性关系载入概念,此时便可依据不同的注解(Annotation)映射出不相同的实体类别。

Sample source

Weisting's Annotation Romp source

Reference Url:

读取标注资讯

Get All Classes Within A Package

注解Annotation实现原理与自定义注解例子

JDK中注解的底层实现

JAVA 注解


<<:  #Day2-- 卖药仔是我!你想要用哪种盒子装?

>>:  【Day02】Verilog 基本简介

Material UI in React [ Day 28 ] Customization Component 自订组件 (part1)

由於组件可以在不同的context中使用,有几种方法可以解决这个问题,官方连结。 1.一次性情况的特...

Day-24 再创 SONY 巅峰盛世、大破大立的 PS4

经过了残酷的 PS3 一役、SONY 内部深刻的意识到:已不是能盲目复制过去的成功经验的时代了、必须...

云端定义 2

本系列文章同步发布於笔者网站 昨天我们介绍了云端的五个必要条件,今天要接续昨天的云端定义,来介绍云端...

30天程序语言研究

目前我最想先学习的程序语言是python,因为我现在大三在许多课程都会优先使用这个语言,如深度学习中...

JavaScript Day 22. Hoisting

在 JavaScript 里还有一个概念称为「Hoisting ( 提升 )」,底下先执行一段范例:...