美文网首页
笔记:Java高级特性之注解与反射+代理

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

作者: 盐海里的鱼 | 来源:发表于2020-05-19 01:18 被阅读0次

    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的一个封装层框架

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/gtgmohtx.html