笔记:Java高级特性之注解与反射+代理

1.注解

注解:Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关
于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

元注解:在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)。一般的,我们在定义自定义注解时,需要指定的元注解有两个 :

        @Target 指定注解的标记类型
             ElementType.ANNOTATION_TYPE 可以应用于注解类型。
             ElementType.CONSTRUCTOR 可以应用于构造函数。
             ElementType.FIELD 可以应用于字段或属性。
             ElementType.LOCAL_VARIABLE 可以应用于局部变量。
             ElementType.METHOD 可以应用于方法级注解。
             ElementType.PACKAGE 可以应用于包声明。
             ElementType.PARAMETER 可以应用于方法的参数。
             ElementType.TYPE 可以应用于类的任何元素。

      @Retention 指定注解的作用域
            RetentionPolicy.SOURCE - 标记的注解仅保留在源级别中,并被编译器忽略。
            RetentionPolicy.CLASS - 标记的注解在编译时由编译器保留,但 Java 虚拟机(JVM)会忽略。
            RetentionPolicy.RUNTIME - 标记的注解由 JVM 保留,因此运行时环境可以使用它。

另外还有@Documented 与 @Inherited 元注解,前者用于被javadoc工具提取成文档,后者表示允许子类
继承父类中定义的注解。

声明一个注解:

@Target(ElementType.TYPE)//注解可以标记的类型
@Retention(RetentionPolicy.SOURCE)//注解的作用域
public @interface Chen {
    String value();
}

注解作用域应用场景:

  • SOURCE: 常用于APT(Annotation Process Tools),作用于源码级别的注解,可提供给IDE语法检查、APT等场景使用。
  • CLASS: 此种注解的应用场景为字节码操作。如:AspectJ、热修复Roubust中应用此场景。
  • RUNTIME: 注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
    1589821022(1).png

构建一个注解处理器:

1.构建注解处理器类
1593145576.png
//要处理的注解
@SupportedAnnotationTypes("com.example.annationtest.Lance")
public class DealProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //输出注解日志
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE,"=======================================这是一个注解处理器======================");
        return false;
    }
}

2.往文件中注册注解处理器:

1593145576(1).png

3.使用,引入注解处理器:

dependencies {
    //引入注解处理器
    annotationProcessor project(':compiler')
}

作用于源码级代码作用域限定:

public class Test {
    private static WeekEmumDay mCuurentDay;

    private static int mCurrentIntDay;

    enum WeekEmumDay{
        SUNNDAY,MONDAY
    }
    private static final int SUNDAY =0;
    private static final int MONDAY =1;

    @IntDef({SUNDAY,MONDAY})
    @Target({ElementType.FIELD,ElementType.PARAMETER})
    @Retention(RetentionPolicy.SOURCE)
    @interface WeekDay {

    }
    //利用自带的注解检查
    public static void setDrawable(@DrawableRes int id){

    }

    public static void main(String[] args) {
        //设置int类型会有提示
        //setDrawable(1);
        //设置引用资源
        setDrawable(R.drawable.ic_launcher_background);

        setCurrentDay(10001);//此时传入类型不受限制
        //限定输入类型一种是使用枚举 但是枚举开销较大
        setCurrentDay1(WeekEmumDay.SUNNDAY);

        //限定输入值范围
        //setCurrentDay1(1);
        setCurrentDay1(SUNDAY);

    }

    /**
     * 使用逐渐限定输入的取值域
     * @param day
     */
    private static void setCurrentDay1(@WeekDay int day) {
    }

    /**
     * 限定枚举类型
     * @param day
     */
    private static void setCurrentDay1( WeekEmumDay day) {
    }

    //未检查时
    public static void setCurrentDay(int currentDay){

    }

}

2.反射

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们
使用 JDK 提供的反射 API 进行反射调用。反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和
方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。是Java被视为动态语言的关键。
Java反射机制主要提供了以下功能:
在运行时构造任意一个类的对象
在运行时获取或者修改任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法(属性)

获取Class:

获取Class对象的三种方式
1. 通过类名获取 类名.class
2. 通过对象获取 对象名.getClass()
3. 通过全类名获取 Class.forName(全类名) classLoader.loadClass(全类名)

判断是否为某个类的实例。
public native boolean isInstance(Object obj);
获取构造器信息:

Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的public构造函数(包括父类)
Constructor[] getConstructors() -- 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(包括私有)
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关)

获取类的成员变量(字段)信息:

Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段

调用方法:

Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法
Method[]getMethods() -- 获得类的所有公共方法 包括父类的非private方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法 类本市身

反射获取泛型真实类型:

当我们对一个泛型类进行反射时,需要的到泛型中的真实数据类型,来完成如json反序列化的操作。此时需要通
过 Type 体系来完成。 Type 接口包含了一个实现类(Class)和四个实现接口,他们分别是:

  • TypeVariable 泛型类型变量。可以泛型上下限等信息;

  • ParameterizedType具体的泛型类型,可以获得元数据中泛型签名类型(泛型真实类型)

  • GenericArrayType当需要描述的类型是泛型类的数组时,比如List[],Map[],此接口会作为Type的实现。

  • WildcardType通配符泛型,获得上下限信息;

实例:通过自定义注解与反射实现页面跳转的参数注入

public static void injectAutowired(Activity activity) {
        Class<? extends Activity> cls = activity.getClass();
        //获得数据
        Intent intent = activity.getIntent();
        Bundle extras = intent.getExtras();
        if (extras == null) {
            return;
        }

        //获得此类所有的成员
        Field[] declaredFields = cls.getDeclaredFields();
        for (Field field : declaredFields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                Autowired autowired = field.getAnnotation(Autowired.class);
                //获得key
                String key = TextUtils.isEmpty(autowired.value()) ? field.getName() : autowired.value();

                if (extras.containsKey(key)) {
                    Object obj = extras.get(key);
                    // todo Parcelable数组类型不能直接设置,其他的都可以.
                    //获得数组单个元素类型
                    Class<?> componentType = field.getType().getComponentType();
                    //当前属性是数组并且是 Parcelable(子类)数组
                    if (field.getType().isArray() &&
                            Parcelable.class.isAssignableFrom(componentType)) {
                        Object[] objs = (Object[]) obj;
                        //创建对应类型的数组并由objs拷贝

                        Object[] objects = Arrays.copyOf(objs, objs.length, (Class<? extends Object[]>) field.getType());
                        obj = objects;
                    }

                    field.setAccessible(true);
                    try {
                        field.set(activity, obj);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

3.代理(静态代理与动态代理)

静态代理:

1593248156(1).png
public interface Message {
    void message();
}

public class Lucy implements Message {
    @Override
    public void message() {
        System.out.println("lucy  message实际服务对象实现");
    }
}

//静态代理Message的代理对象 中间隔离层
public class Agent implements Message {
    private final Message message;
    public Agent(Message message){
        this.message = message;
    }
    public void before(){
        System.out.println("前置处理");
    }

    @Override
    public void message() {
        before();
        message.message();//实际提供服务者
        after();
    }

    public void after(){
        System.out.println("后置处理");
    }

}

静态代理的缺点在于每增加一个服务功能 都需要创建对应的代理类 一个代理类代理一个接口

动态代理:

通过java提供的动态代理接口Proxy.newProxyInstance 获取代理对象 代理只能代理接口
方式如下:

 final Avlin avlin   = new Avlin();
        final Lucy lucy   = new Lucy();
      Object o =   Proxy.newProxyInstance(Test2.class.getClassLoader(),
                new Class[]{Message.class, Wash.class}, new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("动态代理调用==============>"+method.getName());
                        return method.invoke(avlin,args);
                    }
                });
      Message message = (Message) o;
      message.message();//调用代理类的方法 会回调到InvocationHandler(Object proxy(代理对象), Method method(调用的方法), Object[] args(方法的参数)) invoke函数 
        Wash wash = (Wash) o;
        wash.wash();
    }

注解 反射 动态代理实例 获取OnClicklistener事件的注入:

@OnClick({R.id.btn_1,R.id.btn_2})
    private void click(View view){
        switch (view.getId()){
            case R.id.btn_1:
                Toast.makeText(this,"触发点击事件Btn1",Toast.LENGTH_LONG).show();
                break;
            case R.id.btn_2:
                Toast.makeText(this,"触发点击事件Btn2",Toast.LENGTH_LONG).show();
                break;
        }
    }

//TODO:定义Onclick注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)

public @interface OnClick {
    @IdRes int[] value();
}
//TODO:扫描注解与实例化对象:
 final Method[] methods = clas.getDeclaredMethods();
        for (final Method m : methods) {
            System.out.println(m.getName());
            if(m.isAnnotationPresent(OnClick.class)){
                OnClick onClick = m.getAnnotation(OnClick.class);
                int[] ids = onClick.value();
                for (int id : ids) {
                    final View view = activity.findViewById(id);
                    System.out.println("id is:"+id);
                    //通过动态代理获取onclicklistener代理对象
                    Object o =  Proxy.newProxyInstance(view.getClass().getClassLoader(),new Class[]{View.OnClickListener.class}, new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println(method.getName());
                            m.setAccessible(true);
                            //通过反射对方法注入事件
                            return m.invoke(activity,args);
                        }
                    });
                    View.OnClickListener listener = (View.OnClickListener) o;
                    view.setOnClickListener(listener);
                }
            }
        }

补充:Retrofit 的核心就是用到了动态代理+注解与反射 通过动态代理在使用者调用方法时在 invoke方法里进行处理最终通过okhttp来发起网络请求和响应,retrofit本质上是对okhttp的一个封装层框架

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342