笔记: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: 注解保留至运行期,意味着我们能够在运行期间结合反射技术获取注解中的所有信息。
    笔记:Java高级特性之注解与反射+代理_第1张图片
    1589821022(1).png

构建一个注解处理器:

1.构建注解处理器类
笔记:Java高级特性之注解与反射+代理_第2张图片
1593145576.png
//要处理的注解
@SupportedAnnotationTypes("com.example.annationtest.Lance")
public class DealProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set 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 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) field.getType());
                        obj = objects;
                    }

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

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

静态代理:

笔记:Java高级特性之注解与反射+代理_第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的一个封装层框架

你可能感兴趣的:(笔记:Java高级特性之注解与反射+代理)