Java的代理和注解

简介: 都已经记不起到底何时开始使用Java语言进行编程的了,印象中起码也有三五年了吧。想象中自己应该要做后台,却阴差阳错成了Android狗,还好Java仍然陪伴着我(Java:你明明去看了Kotlin!)。

都已经记不起到底何时开始使用Java语言进行编程的了,印象中起码也有三五年了吧。想象中自己应该要做后台,却阴差阳错成了Android狗,还好Java仍然陪伴着我(Java:你明明去看了Kotlin!)。


img_12ee22d4bb865e27c11edee583321fb0.png

今天打算写写关于代理模式和注解这方面的内容。这两个简直是风马牛不相及,放在一起好像有那么一丢丢别扭。不过呢,这主要是为了后面的文章做准备。作为一名伪源码爱好者,还是读过不少源码。其中Android的网络请求框架Retrofit就将动态代理和注解完美了结合在了一起,后面详细讲解吧(PS:这些内容一年前就已经看懂了,现在只为了记录)!

1. 代理模式

代理可以分为两类:静态代理和动态代理,说实话,Android在使用代理类这方面用的并不是很多,关于AOP(面向切面编程)这些东西使用频繁的还是在后台开发。

代理的三个条件:

  • 共同接口:代理类和被代理类实现相同的接口
  • 真实对象:实现接口,并真正完成某些操作
  • 代理对象:实现接口,对真实对象进行代理,可以在其操作前后执行其他操作

关于代理我的理解就是xxx(被代理类)有中间商(代理类),还可能赚差价(进行操作)。光说不练好像一点用处都没有,还是用show you my code吧!

共同接口:

public interface Action {
    void doSomething(float value);
}

真实对象:

public class RealDoAction implements Action {
    @Override
    public void doSomething(float value) {
        System.out.println("我是车主我要卖车,价钱" + value + "元");
    }
}

静态代理:

public class ActionProxy implements Action {

    private Action realDoAction;

    public ActionProxy(RealDoAction realDoAction) {
        this.realDoAction = realDoAction;
    }

    @Override
    public void doSomething(float value) {
        // 被代理类操作执行前执行的操作
        System.out.println("我是平台,在我这里可以进行交易。");
        realDoAction.doSomething(value);
        // 被代理类操作完成后执行的操作
        System.out.println("车卖出去了,价钱" + addValue(value) + "元,没有中间商赚差价。");
    }

    private float addValue(float value) {
        return value + 10;
    }
}
// 测试
public static void main(String[] args) {
    Action action = new ActionProxy(new RealDoAction());
    action.doSomething(100);
}

输出:

我是平台,在我这里可以进行交易。
我是车主我要卖车,价钱100.0元
车卖出去了,价钱110.0元,没有中间商赚差价。

可以看到使用静态代理模式可以很方便的扩展原有的功能并且不去修改原代码,但是对于需要代理的多个类此时实现却有些麻烦。为了简单有效的解决这个问题,我们可以使用动态代理来更灵活的方式去实现代理功能。
使用动态代理的方式也比较简单,Java提供给我们Proxy.newProxyInstance可以快速方便实现动态代理:

// 还是原来的代码
Action realDo = new RealDoAction();
Action action = (Action) Proxy.newProxyInstance(Action.class.getClassLoader(), // 类加载器
        new Class[]{Action.class}, // 被代理类实现的所有接口
        new InvocationHandler() { // InvocationHandler具体逻辑写在这里,返回值是该方法的返回值。接口中有多个方法可以根据方法名称判断
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("我是平台,在我这里可以进行交易。");
                method.invoke(realDo, args);
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Float) {
                        System.out.println("车卖出去了,价钱" + ((float) args[i] + 10) + "元,没有中间商赚差价。");
                        break;
                    }
                }
                return null;
            }
        });
action.doSomething(1000);

看下输出,和静态代理一样:

我是平台,在我这里可以进行交易。
我是车主我要卖车,价钱1000.0元
车卖出去了,价钱1010.0元,没有中间商赚差价。

代理到这里基本上就结束了,东西不多。


2. 注解

谈起注解,可写的内容可就很多了。这里还是简单来写吧,省的又是长篇大论。
首先说下可以用在注解上的注解——元注解

@Retention保留期,可取值:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,编译后的class文件不存在该注解。
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,编译后的class文件该注解仍然存在,但是不会被加载到JVM中。
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

关于RetentionPolicy.SOURCE和RetentionPolicy.CLASS这两者都可以使用AbstractProcessor来在编译时生成代码,其主要不同点在于编译后的class文件中是否存在该注解。也可以理解为是方便给IDE看还是给程序员看。

@Target注解的作用域,可取值:

  • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  • ElementType.CONSTRUCTOR 可以给构造方法进行注解
  • ElementType.FIELD 可以给属性进行注解
  • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  • ElementType.METHOD 可以给方法进行注解
  • ElementType.PACKAGE 可以给一个包进行注解
  • ElementType.PARAMETER 可以给一个方法内的参数进行注解
  • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举
  • ElementType.TYPE_PARAMETER和ElementType.TYPE_USE(1.8新增)它们都可以限制哪个类型可以进行注解。能在局部变量、泛型类、父类和接口的实现处使用,甚至能在方法上声明异常的地方使用。(具体用途不详。。)

@Documented有关注释的注解。
@Inherited指可继承注解,表示某个类的父类拥有该注解,子类如果没有其他注解的话,那么该子类会继承父类的注解。
@Repeatable(1.8新增)我的理解是为了简化一个属性拥有多个相同注解而新增的注解,这里面可以放多个注解。(解释不太清楚,用的也不多)

上面讲了5个元注解,其实常用的也就@Retention和@Target,这里面可以根据@Retention可以分为编译时注解和运行时注解,下面分别写个例子。

2.1 编译时注解

关于编译时注解昨天基本上花费了一整天,原本Eclipse中的可以执行但是换成了IDEA就不能使用了。花费了一天时间,查找原因(主要还是不想用老大哥Eclipse进行展示)。下面看下例子:

  1. 创建注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS) // 这里也可以改为RetentionPolicy.SOURCE,只不过在class文件中看不到这个注解
public @interface TestAnnotation {
    String value() default "";
}
  1. 继承AbstractProcessor实现编译时生成代码
@SupportedAnnotationTypes("com.nick.demo.annotation.TestAnnotation")// 要支持的注解
@SupportedSourceVersion(SourceVersion.RELEASE_8) // 版本
public class Processor extends AbstractProcessor {
    // 文件创建器
    private Filer filer;
    // 消息输出者
    private Messager messager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 初始化获取文件创建器和消息输出
        filer = processingEnv.getFiler();
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 根据注解获取所有的Element
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(TestAnnotation.class);
        // 遍历
        for (Element element : elements) {
            // 如果类型是Class,也就是注解的作用域是ElementType.TYPE
            if (element.getKind() == ElementKind.CLASS) {
                // 转换成TypeElement
                TypeElement typeElement = (TypeElement) element;
                Name simpleName = typeElement.getSimpleName();
                // 输出名称,只为测试而用
                messager.printMessage(Diagnostic.Kind.NOTE, simpleName);
                // 输出全部名称
                messager.printMessage(Diagnostic.Kind.NOTE, typeElement.getQualifiedName());
                // 创建文件
                createFile(typeElement.getQualifiedName().toString(), element.getAnnotation(TestAnnotation.class).value());
            }
        }
        return true;
    }

    private void createFile(String className, String output) {
        StringBuilder cls = new StringBuilder();
        // 获得com.xxx.A的包名,com.xxx
        String[] strings = className.split("\\.");
        StringBuilder packageName = new StringBuilder();
        for (int i = 0; i < strings.length - 1; i++) {
            packageName.append(strings[i]);
            if (i != strings.length - 2) {
                packageName.append(".");
            }
        }
        // 代码的拼接
        cls.append("package " + packageName + ";\n\npublic class ")
                .append(strings[strings.length - 1] + output)
                .append(" {\n  public static void main(String[] args) {\n")
                .append("    System.out.println(\"")
                .append(output)
                .append("\");\n  }\n}");
        // 输出这段代码
        messager.printMessage(Diagnostic.Kind.NOTE, cls.toString());
        try {
            // 通过文件创建器创建SourceFile
            JavaFileObject sourceFile = filer.createSourceFile(className + output);
            Writer writer = sourceFile.openWriter();
            writer.write(cls.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  1. 在与java平级目录下创建resources文件夹,并在其目录下创建META-INF文件,接着在META-INF创建services,最后在services文件目录下创建文件javax.annotation.processing.Processor,内容为自定义的Processer的类全名。如下图所示:


    img_2a09dcc1133c6f5b9ae92a8fda544e52.png
    内容.png
img_6ab88d8eeb5cf01c50e36726c4a29662.png
添加Processer.png
  1. 将java文件和resources文件打包成jar,idea可以通过Project Structure-> Artifacts 添加JAR-> From module,选择module并且记得将resources也添加:
img_dbfb691dcd3e0d5520bb35800c077120.png
生成jar.png
  1. 这一步很关键,我们创建了编译时注解的代码的jar,我们需要告诉ide在编译的时候执行我这个jar,所以需要将其添加到Compiler的Annotation Processors中:
img_6c5011e7deb4c9e97183c01761ec1e34.png
添加.png
  1. 将jar导入项目中,并且使用我们刚才创建的注解,这里一定要注意把原项目定义的注解删掉,使用jar中的注解,不然不会生成代码:

    img_0b44ab569569192b6a84088ffc039be1.png
    添加注解.png

  2. 编译,可以看到Messages中有我们打印的日志。编译成功可以在build/classes/main/目录下找到自定义注解生成的class文件,同时在generated目录下还有java文件的生成:

img_54a05e9a67f6b693cc877a491184f9f5.png
打印了message.png
img_4a8c66041a61261cdc7b800e49d5abca.png
生成的文件.png

OK,编译时注解这里基本上就讲完了。Android好多优秀的框架都使用的编译时注解框架,例如Android的Databinding框架,butterknife等等。

2.2 运行时注解

运行时注解就比较简单了,下面还是看下例子:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String value() default "";
}

@TestAnnotation("123456789")
public class Main {
    public static void main(String[] args) {
        // 获得类的TestAnnotation注解
        TestAnnotation annotation = Main.class.getAnnotation(TestAnnotation.class);
        if (annotation != null) {
            // 将值输出
            System.out.println(annotation.value());
        }
    }
}

运行输出:

123456789

好像没什么可以说的,基于运行时注解的框架也不少,例如EventBus、Retrofit等等。

The end

写完收工,关于代理和注解就写这么多吧。希望能在年前将Retrofit的分析写完,加油了。


img_da58c1ad7172c277838c972dbb7866ad.png
目录
相关文章
|
17天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
53 7
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
98 43
Java学习十六—掌握注解:让编程更简单
|
22天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
60 5
|
1月前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
57 14
|
1月前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
37 12
|
1月前
|
Java
JAVA 静态代理 & 动态代理
【11月更文挑战第14天】静态代理是一种简单的代理模式实现,其中代理类和被代理类的关系在编译时已确定。代理类实现与被代理类相同的接口,并持有被代理类的实例,通过调用其方法实现功能增强。优点包括代码结构清晰,易于理解和实现;缺点是对于多个被代理类,需为每个类编写相应的代理类,导致代码量大增,维护成本高。动态代理则在运行时动态生成代理类,更加灵活,减少了代码冗余,但可能引入性能损耗和兼容性问题。
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
36 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
Java
Java访问外网图片地址时,如何添加代理?
【10月更文挑战第14天】Java访问外网图片地址时,如何添加代理?
46 2
|
1月前
|
Java 编译器
Java进阶之标准注解
Java进阶之标准注解
34 0
|
2月前
|
JSON Java 数据库
java 常用注解大全、注解笔记
关于Java常用注解的大全和笔记,涵盖了实体类、JSON处理、HTTP请求映射等多个方面的注解使用。
45 0
java 常用注解大全、注解笔记