注解与反射

注解(Annotation)

1、注解的理解:

  •  Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取(与注释的区别), 并执行相应的处理。通过使用 Annotation,程序员可以在不改变原逻辑的情况下, 在源文件中嵌入一些补充信息。

  •  Annotation可以像修饰符一样使用,可以用来修饰包、类、构造器、方法、成员变量、参数、局部变量的声明,这些信息被保存在Annotation的“name = value”对中。

  • 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/Android 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗 代码和XML配置等。

2、内置注解:

@Override:定义在java.lang.Override中,此注解只设用于修饰方法,表示这个方法打算重写父类中的另一个方法,并且可以检查是否符合重写的规范

@Deprecated:定义在java.lang.Deprecated中,此注解可以修饰方法、属性、类,表示不鼓励程序员使用这种元素,通常是因为它很危险或者存在更好的选择

@SuppressWarnings:定义在java.lang.SuppressWarnings中,用来抑制编译时的警告信息。

3、注解的使用:

使用 Annotation时要在其前面增加@,并把该 Annotation当成个修饰符使用。用于修饰它支持的程序元素

示例一:生成文档相关的注解

  • @author标明开发该类模块的作者,多个作者之间使用,分割
  • @version标明该类模块的版本
  • @see参考转向,也就是相关主题
  • @since从哪个版本开始增加的
  • @param对方法中某参数的说明,如果没有参数就不能写
  • @return对方法返回值的说明,如果方法的返回值类型是void就不能写
  • @exception对方法可能抛出的异常进行说明,如果方法没有用 throws显式抛出的异常就不能写
  • 其中 @param @return和@exception这三个标记都是只用于方法的。
/**
 * @author bruce
 * @project_name JavaSenior
 * @package_name com.bruce.java
 * @create 2020-08-30 13:58
 */
public class AnnotationTest {
    /**
     *程序的主方法
     * @param args 传入命令行参数
     */
    public static void main(String[] args) {

    }
    /**
     * 求圆形面积
     * @param radius 所求面积的半径
     * @return 面积值
     */
    public static double getArea(double radius){
        return Math.PI * radius * radius;
    }
}

示例二:在编译时进行格式检查(JDK内置的个基本注解)

public class AnnotationTest{
    public static void mian (String [] args){
        @SuppressWarning("unused")
        int a = 0;
    }
    @Deprecated
    public void print(){
        System.out.print("过时的方法");
    }
    @Override
    public String toString(){
        return "重写的toString方法";
    }
}

示例三:跟踪代码依赖性,实现替代配置文件功能

在使用Spring框架时会大量用到注解驱动开发。

3、元注解

对现有的注解进行解释说明的注解。

  • Retention:指定所修饰的 Annotation 的生命周期:SOURCE\CLASS(默认行为)\RUNTIME只有声明为RUNTIME生命周期的注解,才能通过反射获取。
  • Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素

image-20200426111841391

  • Documented:表示所修饰的注解在被javadoc解析时,保留下来。
  • Inherited:被它修饰的 Annotation 将具继承性。

4、自定义注解

  • 注解声明为:@interface
  • 内部定义成员,通常使用value表示
  • 可以指定成员的默认值,使用default定义
  • 如果自定义注解没成员,表明是一个标识作用。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {

    String value() default "hello";
}
  • 如果注解有成员,在使用注解时,需要指明成员的值。
  • 自定义注解必须配上注解的信息处理流程(使用反射)才意义。
  • 自定义注解通常都会指明两个元注解:Retention、Target

反射(Reflection)

1、反射的概括

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

通常的方式:引入需要的“包类”名称---->通过“new”实例化---->获得实例化对象

反射的方式:实例化对象---->getClass()方法---->得到完整的“包类”名称

反射机制提供的功能

  • 在运行时判断任意一个对象所属的类

  • 在运行时构造任意一个类的对象

  • 在运行时判断任意一个类所具有的成员变量和方法

  • 在运行时获取泛型信息

  • 在运行时调用任意一个对象的成员变量和方法

  • 在运行时处理注解

  • 生成动态代理

2、Class类

1 、Class类简述

在Java中,每个class都有一个相应的Class对象。也就是说,当我们编写一个类,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。

  • Class实例对应着加载到内存中的运行时类(不是对象)

  • 在Object类中定义了public final Class getClass(),此方法将被所有子类继承,方法返回值的类型是一个Class类,此类是Java反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即可以通过对象反射求出类的名称。

  • 对象使用反射后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留一个不变的Class类型的对象。一个Class对象包含了特定某个结构( class/interface/enum/annotation/primitive type/void/[])的有关信息。

  • Class本身也是一个类

  • Class对象只能由系统建立对象

  • 一个加载的类在JVM中只会有一个Class实例

  • 一个Class对象对应的是一个加载到JVM中的一个.class文件

  • 每个类的实例都会记得自己是由哪个Class实例所生成

  • 通过Class可以完整地得到一个类中的所有被加载的结构

  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

2、类的加载过程:

  • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。

  • 换句话说,Class的实例就对应着一个运行时类。

  • 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。

3、Class类的常用方法

image-20200505221744128

4、获取Class实例的几种方式

  • 已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高 比如:Class clazz = String.class;这种用的比较少,因为不能动态获取。还不如直接new

  • 已知某个类的实例,调用该实例的getclass()方法获取Class对象 比如:Class clazz=person.getclass();

  • 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出 ClassNotFoundException(比较常用) 比如:Class clazz = Class.forName(String classPath) //体现动态性

  • 用类加载器 ClassLoader cl = this.getclass().getClassLoader(); Class clazz = cl.loadClass("类的全类名");(少用)

5、Class实例可以代表的结构

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

  • interface:接口

  • []:数组

  • enum:枚举

  • annotation:注解@interface

  • primitive type:基本数据类型

  • void

万事万物皆对象

3、反射的应用

1、创建运行时类的对象

@Test
public void test1() throws Exception {
    //方式一
    Class<Person> clazz1 = Person.class;
    //方式二
    Class<Person> clazz2 = (Class<Person>) Class.forName("cn.bruce.java.Person");

    Person person1 = clazz1.newInstance();
    Person person2 = clazz2.newInstance();
    System.out.println(person1);
    System.out.println(person2);

}

newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器

要想此方法正常的创建运行时类的对象,要求:

  • 运行时类必须提供空参的构造器

  • 空参的构造器的访问权限得够。通常,设置为public。

javabean中要求提供一个public的空参构造器。原因:

  • 便于通过反射,创建运行时类的对象

  • 便于子类继承此运行时类时,默认调用super()时,保证父类此构造器

2、获取运行时类的完整结构(了解)

我们可以通过反射,然后创建运行时类对象,获取对应的运行时类中所有的属性、方法、构造器、父类、接口、父类的泛型、包、注解、异常等

获取属性Fieled:

  • public Field[] getDeclaredFields(),返回此Class对象所表示的类或接口的全部Field

  • public Field[] getFields(),返回此Class对象所表示的类及其所有父类或接口的public的Field。

  • Field方法中

    • public int getModifiers()以整数形式返回此Field的修饰符(比如0代表缺省,1代表public,2代表private等)

    • public Class getType()得到Field的属性类型

    • public String getName()返回Field的名称。

获取方法Method

  • public Method[] getDeclaredMethods(),返回此Class对象所表示的类或接口的全部方法

  • public Method[] getMethods(),返回此Class对象所表示的类及其所有父类或接口的public的方法

  •  Method类中:

    • public Class getReturnType()取得全部的返回值

    • public Class [] getParameterTypes()取得全部的参数

    • public int getModifiers()取得修饰符

    • public Class [] getEXceptionTypes()取得异常信息

实现的全部接口

public Class [] getInterfaces() 确定此对象所表示的类或接口实现的接口。

所继承的父类

 public Class getSuperclass() 返回表示此Class所表示的实体(类、接口、基本类型)的父类的Class。

全部的构造器:

  • public Constructor [] getConstructors(),返回此Class对象所表示的类的所有public构造方法。

  • public Constructor [] getDeclaredConstructors(),返回此Class对象表示的类声明的所有构造方法。

  • 在Constructor类中:

    • 取得修饰符:public int getModifiers();

    • 取得方法名称: public String getName();

    • 取得参数的类型: public Class getParameterTypes();

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        //加载类
        Class<Book> bClass = (Class<Book>) Class.forName("com.company.Book");
        //获取构造器
        Constructor<Book> constructor = bClass.getConstructor(String.class, String.class);
        //创造实例
        Book book = constructor.newInstance("西游记", "师徒四人西天取经");
        System.out.println(book);//Book{name='西游记', info='师徒四人西天取经'}
    }
}

Annotation相关

get  Annotation(Class annotationClass)

get  DeclaredAnnotations()

3、调用运行时类的指定结构

调用指定的属性

在反射机制中,可以直接通过Field类操作类中的属性,通过Field类提供的set()和get()方法就可以完成设置和取得属性内容。

  • public Field getField(String name)返回此Class对象表示的类或接口的指定的public的Field。(不常用)

  • public Field getDeclaredField(String name)返回此Class对象表示的类或接口的指定的Field。

在Field中:

  • public Object get(object obj)取得指定对象obj上此 Field的属性内容

  • public void set(Object obj,Object value)设置指定对象obj上此Field的属性内容

//Person类中有一个私有的name属性

@Test
public void testField() throws Exception {
    Class clazz = Person.class;

    //创建运行时类的对象
    Person p = (Person) clazz.newInstance();

    //1. getDeclaredField(String fieldName):获取运行时类中指定变量名的属性
    Field name = clazz.getDeclaredField("name");

    //2.保证当前属性是可访问的(这样私有的才能访问)
    name.setAccessible(true);
    //3.获取、设置指定对象的此属性值
    name.set(p,"Tom");

    System.out.println(name.get(p));
}

调用指定的方法(常用)

通过反射,调用类中的方法,通过Method类完成。步骤:

  • 通过Class类的getMethod(String name,Class… parameterTypes)方法取得一个Method对象,后面的可变形参是调用方法参数的类型。

  • 之后使用 Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的ob对象的参数信息

注意:

  • Object对应原方法的返回值,若原方法无返回值,此时返回null

  • 若原方法若为静态方法,此时形参Object obj可为null,因为静态的方法不需要知道具体的哪个对象。

  • 若原方法形参列表为空,则Object[] args为null

  • 若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

//Person类中有两个方法:
//private String  show(String s);
//private static void showDesc();


@Test
public void testMethod() throws Exception {
    Class<Person> clazz = Person.class;

    //创建运行时类的对象
    Person person = clazz.newInstance();
    /*
    1.获取指定的某个方法
    getDeclaredMethod():参数1 :指明获取的方法的名称  参数2:指明获取的方法的形参列表
    */
    Method show = clazz.getDeclaredMethod("show", String.class);
    //2.保证当前方法是可访问的
    show.setAccessible(true);
    /*
    3. 调用方法的invoke():参数1:方法的调用者  参数2:给方法形参赋值的实参
    invoke()的返回值即为对应类中调用的方法的返回值。
    */
    Object returnValue = show.invoke(person, "CHN");
    System.out.println(returnValue);

    *************如何调用静态方法*****************

    Method showDesc = clazz.getDeclaredMethod("showDesc");
    showDesc.setAccessible(true);
    
    Object returnVal = showDesc.invoke(Person.class);//括号里面也可填写null 

    System.out.println(returnVal);//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
}

4、获取注解的信息


@Retention(RetentionPolicy.RUNTIME)//这样才能被反射获取
@Target(ElementType.METHOD)//只能用于方法
public @interface MyAnnotation {
    String username();
    String password();
}
public class Main {
    @MyAnnotation(username = "admin",password = "123")
    public void f(){
    }

    //获取上面这个方法的注解信息
    public static void main(String[] args) throws Exception {
        //想要拿到这个注解需要得到方法,得到方法的前提是需要得到类
        Class c = Class.forName("Test.Main");//写全类名
        //获取f方法
        Method f = c.getDeclaredMethod("f");
        //判断这个方法是否有注解
        if(f.isAnnotationPresent(MyAnnotation.class)){
            //得到注解
            MyAnnotation annotation = f.getAnnotation(MyAnnotation.class);
            System.out.println(annotation.username());
            System.out.println(annotation.password());
        }
    }
}

5、反射来读取配置文件信息,动态创建对象

配置文件

className=zz.Student//全类名
methodName=sleep

要创建的类

public class Person {
	public void eat() {
		System.out.println("开饭啦");
	}
}
public class Student {
	public void sleep() {
		System.out.println("学生睡觉");
	}
}

反射代码

package zz;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;


public class Main {

	public static void main(String[] args) throws Exception {
		//创建properties对象
		Properties pro = new Properties();
		//获取class目录下的配置文件
		//先找到类加载器,再通过类加载器来寻找
		ClassLoader classLoader = Main.class.getClassLoader();
		//获取这个文件对应的字节流
		InputStream inStream = classLoader.getResourceAsStream("pro.properties");
		pro.load(inStream);
		
		//获取配置文件中定义的数据
		String className = pro.getProperty("className");
		String methodName = pro.getProperty("methodName");
		
		//加载该类进内存
		Class cls = Class.forName(className);
		//创建对象
		Object obj = cls.newInstance();
		//获取对象的方法
		Method method = cls.getMethod(methodName);
		//调用方法
		method.invoke(obj);
	}

}

可以在不改变代码的情况下,只通过改变配置文件就可以动态的创建对象

6.动态代理,面向切面编程

这里我们想要定义一个实现四则运算的类,并且每次执行方法都要给出输入的参数和运行的结果:

传统的写法:

public interface Cal {
    public int add(int num1,int num2);
    public int sub(int num1,int num2);
    public int mul(int num1,int num2);
    public int div(int num1,int num2);
}
public class CalImpl implements Cal {
	
	 public int add(int num1, int num2) {
		 System.out.println("add⽅法的参数是["+num1+","+num2+"]");
		 int result = num1+num2;
		 System.out.println("add⽅法的结果是"+result);
		 return result;
	 }
	 
	 public int sub(int num1, int num2) {
		 System.out.println("sub⽅法的参数是["+num1+","+num2+"]");
		 int result = num1-num2;
		 System.out.println("sub⽅法的结果是"+result);
		 return result;
	 }
	 
	 public int mul(int num1, int num2) {
		 System.out.println("mul⽅法的参数是["+num1+","+num2+"]");
		 int result = num1*num2;
		 System.out.println("mul⽅法的结果是"+result);
		 return result;
	 }
	 
	 public int div(int num1, int num2) {
		System.out.println("div⽅法的参数是["+num1+","+num2+"]");
	 	int result = num1/num2;
	 	System.out.println("div⽅法的结果是"+result);
	 	return result;
	 }
}
上述代码中,⽇志信息和业务逻辑的耦合性很⾼,不利于系统的维护。可以使用面向切面来进行优化,用动态代理实现。
给业务代码找⼀个代理,打印⽇志信息的⼯作交个代理来做,这样的话业务代码就只需要关注⾃身的业务即可。
public class CalImpl implements Cal {
	
	 public int add(int num1, int num2) {
		 int result = num1+num2;
		 return result;
	 }
	 
	 public int sub(int num1, int num2) {
		 int result = num1-num2;
		 return result;
	 }
	 
	 public int mul(int num1, int num2) {
		 int result = num1*num2;
		 return result;
	 }
	 
	 public int div(int num1, int num2) {
		 int result = num1/num2;
		 return result;
	 }
}
//帮忙创建动态代理类
public class MyInvocationHandler implements InvocationHandler{
	//委托对象
	private Object object = null;
	
	//返回代理对象实例
	public Object bind(Object object) {
		this.object = object;
		return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
		//object.getClass().getInterfaces(),拿到委托对象的所有接口(代理对象必须完成委托对象的全部功能)
	}
	//执行的方法
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println(method.getName()+"方法的参数是:"+Arrays.toString(args));
		Object result = method.invoke(this.object, args);//还是用委托对象来执行业务方法
		System.out.println(method.getName()+"的结果是"+result);
		return result;
	}
}
public class Test {
	public static void main(String[] args) {
		Cal cal = new CalImpl();//委托对象
		MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
		Cal cal1 = (Cal)myInvocationHandler.bind(cal);//动态创建代理对象
		cal1.add(1, 2);
		cal1.sub(3, 2);
		cal1.mul(2, 3);
		cal1.div(6, 2);
	}
}

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值