Java 注解和反射

1. 注解

1.1 注解的定义

注解就是源代码的元数据,通熟的讲就是代码中的标签。注解就有如下的特点:

  1. 注解是一个附属品,依赖于其他元素(包、类、方法、属性等等)存在。
  2. 注解本身没有作用,在恰当的时候由外部程序进行解析才会发生作用。

1.2 注解的分类

  1. 按来源分
    • JDK 自带注解,例如:@Override, @Deprecated, @SuppressWornings 。
    • 第三方注解。
    • 自定义注解。
  2. 按生命周期划分
    • SOURCE:只存在于源代码中,编译成 class 文件就不存在了。
    • Class:存在于源代码中和 class 文件中。
    • RUNTIME:注解保留到运行时。

1.3 元注解

元注解指的是用于修饰注解的注解,包括如下几个:

  1. @Retention:指明 Annotation 的生命周期,传入的值是一个枚举类型,可选值为:
    • RetentionPolicy.SOURCE
    • RetentionPolicy.CLASS
    • RetentionPolicy.RUNTIME
  2. @Target:指明 Annotation 可以修饰程序哪些元素,传入的值为ElemetType[] 类型,值可为:
    • ElementType.CONSTRUCTOR :构造器
    • ElementType.FIELD:属性
    • ElementType.LOCAL_VARIABLE:局部变量
    • ElementType.METHOD:方法
    • ElementType.PACKAGE:包
    • ElementType.PARAMETER:参数
    • ElementType.TYPE:类、接口(包括注解类型和 enum 声明)
  3. @Documented:使用此修饰的注解将会被 javadoc 工具提取成文档,使用此注解,其 @Retention 必须被设置为 RetentionPolicy.RUNTIME
  4. @Inherited:具有继承性。

1.4 自定义注解

自定义注解需要注意的问题:

  1. 使用 @interface 关键字定义。

  2. 自动继承 java.lang.annotation.Annotation 接口。

  3. 配置参数的类型只能是八大基本类型、String、Class、enum、Annotation 和对应的数组类型。

  4. 配置参数声明的语法格式如下([] 表示可省略):

    类型 变量名() [default 默认值];
    
  5. 如果只有一个配置参数,其参数名必须为 value。

  6. 如果定义的注解含有配置参数,那在使用注解时,必须指定参数值,指定形式为:“参数名=参数值”。如果只有一个参数,直接写参数值即可,定义中指定了默认值的参数可以不指定值,但没有的一定要指定值

  7. 没有成员的注解为标记,包含成员的称为元数据

1.5 注解的解析

Info.java:

package com.hkl;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Info {
    String info();
    String birthday();
    int age() default 0;
}

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@interface Desc{
    String value();
}

App.java:

package com.hkl;

import java.lang.reflect.Method;

/**
 * Hello world!
 *
 */
@Info(info = "hkl", birthday = "2019/7/20")
@Desc("这是一个类")
public class App 
{
    @Info(info = "hkl", birthday = "2019/7/20", age = 22)
    @Desc("这是一个方法")
    public static void main( String[] args )
    {
        // 解析注解
        try {
            Class clazz = Class.forName("com.hkl.App");

            // 获取类修饰的注解
            System.out.println("---------类中的注解---------");
            if(clazz.isAnnotationPresent(Info.class)){
                Info classInfo = (Info) clazz.getAnnotation(Info.class);
                System.out.println(classInfo.info());
                System.out.println(classInfo.birthday());
                System.out.println(classInfo.age());
            }

            if(clazz.isAnnotationPresent(Desc.class)){
                Desc classDesc = (Desc)clazz.getAnnotation(Desc.class);
                System.out.println(classDesc.value());
            }

            // 获取方法修饰的注解
            Method[] methods = clazz.getMethods();

            System.out.println("---------方法中的注解解析---------");
            for(Method method : methods){
                if(method.isAnnotationPresent(Desc.class)){
                    Desc methodDesc = (Desc)method.getAnnotation(Desc.class);
                    System.out.println(methodDesc.value());
                }

                if(method.isAnnotationPresent(Info.class)){
                    Info methodInfo = (Info)method.getAnnotation(Info.class);
                    System.out.println(methodInfo.info());
                    System.out.println(methodInfo.birthday());
                    System.out.println(methodInfo.age());
                }

            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

2. 反射

2.1 什么反射

反射指的是程序在运行期间借助反射 API 取得任何类的内部信息,并通过这些内部信息去操作对应对象的内部属性和方法。

任何一个类,在第一次使用时,就会被 JVM 加载到堆内存的方法区中。JVM 加载类成功后,就会在方法区中产生一个对应的 Class 对象(一个类只要一个 Class 对象),这个 Class 对象包含被加载类的全部结构信息。

2.2 获取 Class 对象的常用方式

(1)类的 class 属性

每一个类,都有一个 class 静态属性,这个静态属性就是类对应的 Class 对象。

Class<Person> cl1 = Person.class;

(2)Object 对象 的 getClass() 方法

Person p1 = new Person();
Class<Person> cl2 = (Class<Person>) p1.getClass();

(3)通过 Class 类的 forName() 方法(最常用)

try {
    Class cl3 = Class.forName("com.llm.hkl.Person");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

(4)通过 ClassLoader 类(不常用)

ClassLoader cl = Person.class.getClassLoader();
try {
    Class cl4 = cl.loadClass("com.llm.hkl.Person");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

2.3 反射的基本使用

反射的基本使用包括创建对象,设置属性和调用方法。Class 对象中大多数 get 方法有 Declared 和无 Declared,他们的区别是:

  1. 无 Declared:只能获取到 public 修饰的,包括当前类和所有父类。
  2. 有 Declared:获取到当前类所有的(含有 private),但不包括其父类。

Person 类:

public class Person {
    private String name;
    private int age;
    public String habbit;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    private Person(String name, int age, String habbit) {
        this.name = name;
        this.age = age;
        this.habbit = habbit;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getHabbit() {
        return habbit;
    }

    public void setHabbit(String habbit) {
        this.habbit = habbit;
    }

    private String say(String str){
        String str1 = name+"说:"+str;
        System.out.println(str1);
        return str1;
    }

    public void eat(String food){
        System.out.println(name+"吃"+food);
    }

    @Override
    public String toString() {
        return "["+name+","+age+","+habbit+"]";
    }
}

测试方法:

public class PersonTest {
    @Test
    public void ReflexTest() throws Exception{
        System.out.println("---------- new 方式创建对象 ----------");
        // 普通方式创建 Person 对象
        Person p1 = new Person("hkl", 22);

        // 直接设置属性
        p1.habbit = "编程";
        // 调用方法
        System.out.println(p1);

        //无法直接设置私有属性和调用私有方法
        //p1.name = "hkl";
        //p1.say(""Hello);

        //反射方式创建对象
        System.out.println("---------- 反射方式创建对象 ----------");
        Class<Person> clazz = Person.class;

        // 调用无参构造器参加对象
        Constructor<Person> constructor1 = clazz.getConstructor();
        Person p2 = constructor1.newInstance();
        System.out.println(p2);

        // 通过有参构造器
        Constructor<Person> constructor2 = clazz.getConstructor(String.class, int.class);
        Person p3 = constructor2.newInstance("hkl", 22);
        System.out.println(p3);

        // 通过私有的构造器
        Constructor<Person> constructor3 = clazz.getDeclaredConstructor(String.class, int.class, String.class);
        constructor3.setAccessible(true);
        Person p4 = constructor3.newInstance("hkl", 22, "编程");
        System.out.println(p4);

        //通过反射设置公有属性
        Field personFeildHabbit = clazz.getDeclaredField("habbit");
        personFeildHabbit.set(p2, "编程");

        //通过反射设置私有属性
        Field personFeildAge = clazz.getDeclaredField("age");
        personFeildAge.setAccessible(true);
        personFeildAge.set(p2, 18);

        //通过反射调用方法
        Method personMethodToString = clazz.getDeclaredMethod("toString");
        // 方法的返回值为调用方法的返回值
        String str = (String)personMethodToString.invoke(p2);
        System.out.println(str);

        // 通过反射调用私有方法
        Method personMethodSay = clazz.getDeclaredMethod("say", String.class);
        personMethodSay.setAccessible(true);
        String str2 = (String)personMethodSay.invoke(p2, "Hello");
        System.out.println(str2);
    }
}

2.5 设计模式:代理模式

使用一个代理对象将原始对象包装起来, 然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理对象,代理对象决定是否以及何时将方法调用转到原始对象上。

2.5.1 静态代理

代理类和原始对象在编译期间就确定下来了,不利于程序的扩展,且每一个代理只能为一个接口服务,这就会在开发的过程中产生多大的代理类。

静态代理的实现:

  1. 代理类和原始对象实现相同的接口。
  2. 代理类保持原始对象的引用。
  3. 代理类里调用原始对象的方法。

ClothFactory 接口:

package com.hkl.proxy;


public interface ClothFactory {
    String producer();
}

ClothFactoryProxy 类:

package com.hkl.proxy;

public class ClothFactoryProxy implements ClothFactory{
    private ClothFactory clothFactory;

    public ClothFactoryProxy(ClothFactory clothFactory) {
        this.clothFactory = clothFactory;
    }

    @Override
    public String producer() {
        System.out.println("代理对象做一些准备");
        clothFactory.producer();
        System.out.println("代理对象做一些收尾工作");

        return null;
    }
}

NikeFactory 类:

package com.hkl.proxy;

public class NikeFactory implements ClothFactory{

    public NikeFactory() {
    }

    @Override
    public String producer() {
        System.out.println("Nike 正在生产衣服");
        return null;
    }
}

测试方法:

@Test
public void staticProxyTest(){
    // 创建被代理对象
    NikeFactory nikeFactory = new NikeFactory();

    // 创建代理对象
    ClothFactoryProxy clothFactoryProxy = new ClothFactoryProxy(nikeFactory);

    // 通过代理对象调用被代理对象的方法
    clothFactoryProxy.producer();
}

2.5.2 动态代理模式

动态代理是通过 Java 的反射机制实现的。通过动态代理,只需一个代理对象就可以代理所有的对象。

Humen :接口

package com.llm.proxy;
public interface Human {
    String belief();
    void eat(String food);
}

SuperMan:类

package com.hkl.proxy;

public class SuperMan implements Human {
    @Override
    public String belief() {
        return "我相信,我能行!";
    }

    @Override
    public void eat(String food) {
        System.out.println("正在吃"+food);
    }
}

动态代理类:

package com.hkl.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;


public class DynamicProxy {
    // 获取代理对象
    public static Object getInstance(Object obj){
        MyInvocation h = new MyInvocation();
        h.bind(obj);
        // 动态的创建对象
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), h);
    }
}

class MyInvocation implements InvocationHandler{
    private Object obj;

    /**
     * 绑定被代理对象
     * @param obj
     */
    public void bind(Object obj){
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理对象调用被代理对象的方法
        Object ret = method.invoke(obj, args);
        return ret;
    }
}

测试方法:

@Test
public void dynamicProxyTest(){
    SuperMan sm = new SuperMan();
    Human h = (Human)DynamicProxy.getInstance(sm);
    System.out.println(h.belief());;
    h.eat("麻辣烫");

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