4.注解,反射,动态代理

阳光穿透心脏的1/2处 2024-04-08 08:48 133阅读 0赞

学习目标

  • 学习反射
  • 学习动态代理
  • 学习注解

这三块内容在一定程度上是密切相关的,因此就集中一起整理


反射

反射的执行过程就是先创建java,lang.Class对象,然后再对Class对象进行实例化,如果需要调用方法或者一些属性就可以从类对象中获取,然后挂到堆中的对象上执行,这个过程就和JVM通过类加载器子系统加载对象,然后再在具体的方法栈中实例化对象,调用方法的实际过程是相近的。是一种更接近底层的使用方式。

  • 类所以的属性方法注解都在创建的class对象上获取(这和jvm的组织和类加载方式有关)
  • 要调用方法或者获取一些属性的话就需要具体的对象挂在来执行
  1. import java.lang.reflect.Field;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. import java.util.concurrent.Callable;
  5. class Person{
  6. public final int a = 1;
  7. public void printa(){
  8. System.out.println("hello");
  9. }
  10. public static void print(){
  11. System.out.println("静态方法");
  12. }
  13. }
  14. public class fanshe {
  15. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchFieldException {
  16. // 使用反射创建class对象,也就是对类进行初始化
  17. Class<?> aClass = Class.forName(Person.class.getName().toString());
  18. //打印类对象的相关信息
  19. Method[] methods = aClass.getMethods();
  20. for(int i=0;i<methods.length;i++){
  21. System.out.println(methods[i].toString());
  22. }
  23. //获得对象的字段信息
  24. Field[] fields = aClass.getFields();
  25. for(int i=0;i<fields.length;i++){
  26. System.out.println(fields[i].toString());
  27. }
  28. //获得类对象的一个方法
  29. Method print = aClass.getDeclaredMethod("printa");
  30. Method print1 = aClass.getDeclaredMethod("print");
  31. Field a = aClass.getField("a");
  32. System.out.println(a);
  33. //实例化class的一个对象
  34. Object o = aClass.newInstance();
  35. //获得对象的字段信息
  36. Object o1 = a.get(o);
  37. System.out.println(o1);
  38. //对象调用方法
  39. print.invoke(o);
  40. print1.invoke(o);
  41. }
  42. }

动态代理

动态代理是一种设计模式,他的思想就是把这个类继承过来在保存原有类的功能的同时扩展一下类中的方法的功能
java中有两种动态代理的实现方式CGlib和java代理参考地址

  • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • CGLIB动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
  1. package src.TProxy;
  2. import net.sf.cglib.proxy.Enhancer;
  3. import net.sf.cglib.proxy.MethodInterceptor;
  4. import net.sf.cglib.proxy.MethodProxy;
  5. import java.lang.reflect.InvocationHandler;
  6. import java.lang.reflect.Method;
  7. import java.lang.reflect.Proxy;
  8. public class Test {
  9. public static void main(String[] args) {
  10. // 不实现接口的类进行CGlib动态代理,可以直接转为被代理类的类型
  11. UserManagerImpl1 userManager = (UserManagerImpl1)new CGLibProxy().createProxyObject(new UserManagerImpl1());
  12. System.out.println("CGLibProxy:");
  13. userManager.addUser("tom", "root");
  14. // 实现接口的类型进行CGlib动态代理,可以转为接口类型或者被代理的类型
  15. UserManager userManager1 = (UserManager)new CGLibProxy().createProxyObject(new UserManagerImpl());
  16. System.out.println("CGLibProxy:");
  17. userManager1.addUser("tom", "root");
  18. System.out.println("JDKProxy:");
  19. // 实现接口的类,可以被jdk动态代理,但是只能转为接口类型
  20. JDKProxy jdkProxy = new JDKProxy();
  21. UserManagerImpl userManagerJDK = (UserManagerImpl)jdkProxy.newProxy(new UserManagerImpl());
  22. userManagerJDK.addUser("tom", "root");
  23. // 没实现接口的类,直接无法代理
  24. UserManager userManagerJDK1 = (UserManager)jdkProxy.newProxy(new UserManagerImpl1());
  25. userManagerJDK1.addUser("tom", "root");
  26. }
  27. }
  28. // 定义一个接口
  29. interface UserManager {
  30. public void addUser(String id, String password);
  31. public void delUser(String id);
  32. }
  33. // 实现这个接口
  34. class UserManagerImpl implements UserManager {
  35. @Override
  36. public void addUser(String id, String password) {
  37. System.out.println("调用了UserManagerImpl.addUser()方法!");
  38. }
  39. @Override
  40. public void delUser(String id) {
  41. System.out.println("调用了UserManagerImpl.delUser()方法!");
  42. }
  43. }
  44. // 不实现这个接口
  45. class UserManagerImpl{
  46. public void addUser(String id, String password) {
  47. System.out.println("调用了UserManagerImpl.addUser()方法!");
  48. }
  49. public void delUser(String id) {
  50. System.out.println("调用了UserManagerImpl.delUser()方法!");
  51. }
  52. }
  53. // 使用JDK动态代理直接对接口进行扩展和重写
  54. class JDKProxy implements InvocationHandler {
  55. // 需要代理的目标对象
  56. private Object targetObject;
  57. // 传入需要被代理对象的,
  58. public Object newProxy(Object targetObject) {
  59. // 将目标对象传入进行代理
  60. this.targetObject = targetObject;
  61. // 返回代理对象
  62. return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
  63. }
  64. // invoke方法,和反射里面的方法挂在一样,这里是对invoke扩展了,但是里面method.invoke就是在反射调用对象
  65. @Override
  66. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  67. // 进行逻辑处理的函数
  68. checkPopedom();
  69. Object ret = null;
  70. // 调用invoke方法
  71. ret = method.invoke(targetObject, args);
  72. return ret;
  73. }
  74. private void checkPopedom() {
  75. // 模拟检查权限
  76. System.out.println("检查权限:checkPopedom()!");
  77. }
  78. }
  79. class CGLibProxy implements MethodInterceptor {
  80. // CGlib需要代理的目标对象
  81. private Object targetObject;
  82. public Object createProxyObject(Object obj) {
  83. this.targetObject = obj;
  84. Enhancer enhancer = new Enhancer();
  85. enhancer.setSuperclass(obj.getClass());
  86. enhancer.setCallback(this);
  87. Object proxyObj = enhancer.create();
  88. return proxyObj;
  89. }
  90. @Override
  91. public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  92. Object obj = null;
  93. // 过滤方法
  94. if ("addUser".equals(method.getName())) {
  95. // 检查权限
  96. checkPopedom();
  97. }
  98. obj = method.invoke(targetObject, args);
  99. return obj;
  100. }
  101. private void checkPopedom() {
  102. System.out.println("检查权限:checkPopedom()!");
  103. }
  104. }

从上面的代码可以看出,代理都需要传入一个对象,都需要实现一个类似invoke的方法来扩展方法和调用方法,代理之后生成一个新的代理类

  • JDK的动态代理只能对实现了接口的类生成代理,而且代理的对象类型必须转为接口的类型而不是被代理对象的类型,这种方式创建块,运行慢,因为用的反射
  • CGLib可以对任何类生成代理对象,因为他在字节码水平上(用字节码处理框架asm,通过修改字节码生成子类)重新创建了一个类的子类(具体说被代理类和代理类是继承关系),子类作为代理对象,因此可以对任何类进行代理,而且可以类型转换为被代理的类型,这种方法创建慢,运行起来块,比境创建了一个独立的对象。

jdk动态代理函数参数说明:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  • ClassLoader:ClassLoader会定义动态代理类的加载器,一般从被代理的类中获得即可
    -Class<?>[] interfaces:动态代理类需要实现的接口,也就是被代理类实现的接口
    -InvocationHandler:传递一个实现了InvokationHandler接口的类的实例,当一个方法被一个代理实例调用时, 将对该方法调用进行编码, 并将其调度到invocation handler的 {@code invoke}方法

public Object invoke(Object proxy, Method method, Object[] args)

  • Object:实现方法的代理对象,也就是当前创建的代理对象
  • Method:代理实例激发的方法
  • Object[]:传递给方法的一系列参数

注解

注解在java中就是配置在类方法字段接口等位置上的一些属性,在访问这些内容前可以先看一下相关属性,在做操作,说白了就是一些配置信息,这些信息直接没有约束关系(不像类之间的继承关系那么严格,更像是注释,随便写),有的只是jvm读取的时候自定义的一些规则需要根据这些信息来完成,因此可以通过设置注解来调整jvm的行为,注解就是配置文件,这些配置文件通过反射被获得读取出来。掌握了注解就是注释一样的配置文件这个思路就可以灵活的使用注解了

注解的根: Annotation 接口

在 Java 中,Annotation 接口是所有元注解接口扩展的通用接口。所有的注解都隐式的扩展自该接口。但是需要注意的,继承和实现该接口并不能实现定义注解接口。并且该接口并没有定义成一个注解接口。
也就是说java规定Annotation为所有注解的基类,可以把所有的注解当成一个一个的接口,接口可以扩展接口,所以定义注解就是在定义一些接口,在注解上加注解就是在extends接口,就是换了种形式,这种形式的接口可以放置的位置更多
此外注解不存在什么循环依赖,就是一个属性表,你可以把他放在任何地方(合法的地方)甚至它本身。就相当一种注释,提供额外信息的

元注解

@Target @Retention @Documented @Inherited 元注解是系统定义的几个注解,在自定义注解的时候必须加上,以规范注解的定义,就相当于规范信息格式一样。你也可以自己写个规范,不过jvm默认是不承认的哈。

@Target

Target注解规定注解可以放置的位置,他的取值如下:

  1. public enum ElementType {
  2. TYPE, /* 类、接口(包括注释类型)或枚举声明 */
  3. FIELD, /* 字段声明(包括枚举常量) */
  4. METHOD, /* 方法声明 */
  5. PARAMETER, /* 参数声明 */
  6. CONSTRUCTOR, /* 构造方法声明 */
  7. LOCAL_VARIABLE, /* 局部变量声明 */
  8. ANNOTATION_TYPE, /* 注释类型声明 */
  9. PACKAGE /* 包声明 */
  10. }

一个注解可以取上面的多个值

@Retention

Retention规定这个注解的存活时间,可以取如下几个值

  1. package java.lang.annotation;
  2. public enum RetentionPolicy {
  3. SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
  4. CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
  5. RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
  6. }

一个注解只能有一个Retion值

@Inherited

规定这个注解是否可以被注解的类的子类继承使用,本身没有取值,再次说明注解就是注释,下面调用注解的反射过程可以更清晰的说明这一切

@Documented

用来说明是否生成javadoc

其他内置注解

作用在代码的注解是

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

作用在其他注解的注解(或者说 元注解)是:

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
  • @Documented - 标记这些注解是否包含在用户文档中。
  • @Target - 标记这个注解应该是哪种 Java 成员。
  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

从 Java 7 开始,额外添加了 3 个注解:

  • @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
  • @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
  • @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

自定义注解和使用

自定义注解使用 @interface
使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
定义 Annotation 时,@interface 是必须的。
注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。

  1. /**
  2. * Annotation在反射函数中的使用示例
  3. */
  4. @Retention(RetentionPolicy.RUNTIME)
  5. @interface MyAnnotation {
  6. String[] value() default "unknown";
  7. }
  8. /**
  9. * Person类。它会使用MyAnnotation注解。
  10. */
  11. class PersonMy {
  12. /**
  13. * empty()方法同时被 "@Deprecated" 和 "@MyAnnotation(value={"a","b"})"所标注
  14. * (01) @Deprecated,意味着empty()方法,不再被建议使用
  15. * (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
  16. */
  17. @MyAnnotation
  18. @Deprecated
  19. public void empty(){
  20. System.out.println("\nempty");
  21. }
  22. /**
  23. * sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
  24. * @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
  25. */
  26. @MyAnnotation(value={
  27. "girl","boy"})
  28. public void somebody(String name, int age){
  29. System.out.println("\nsomebody: "+name+", "+age);
  30. }
  31. }
  32. public class AnnotationTest {
  33. public static void main(String[] args) throws Exception {
  34. // 新建Person
  35. PersonMy person = new PersonMy();
  36. // 获取Person的Class实例
  37. Class<PersonMy> c = PersonMy.class;
  38. // 获取 somebody() 方法的Method实例
  39. Method mSomebody = c.getMethod("somebody", new Class[]{
  40. String.class, int.class});
  41. // 执行该方法
  42. mSomebody.invoke(person, new Object[]{
  43. "lily", 18});
  44. iteratorAnnotations(mSomebody);
  45. // 获取 somebody() 方法的Method实例
  46. Method mEmpty = c.getMethod("empty", new Class[]{
  47. });
  48. // 执行该方法
  49. mEmpty.invoke(person, new Object[]{
  50. });
  51. iteratorAnnotations(mEmpty);
  52. }
  53. public static void iteratorAnnotations(Method method) {
  54. // 判断 somebody() 方法是否包含MyAnnotation注解
  55. if(method.isAnnotationPresent(MyAnnotation.class)){
  56. // 获取该方法的MyAnnotation注解实例
  57. MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
  58. // 获取 myAnnotation的值,并打印出来
  59. String[] values = myAnnotation.value();
  60. for (String str:values)
  61. System.out.printf(str+", ");
  62. System.out.println();
  63. }
  64. // 获取方法上的所有注解,并打印出来
  65. Annotation[] annotations = method.getAnnotations();
  66. for(Annotation annotation : annotations){
  67. System.out.println(annotation);
  68. }
  69. }
  70. }

发表评论

表情:
评论列表 (有 0 条评论,133人围观)

还没有评论,来说两句吧...

相关阅读

    相关 反射运用----动态代理

    本人对动态代理的理解不深,下面实现的动态代理,大致是通过代理类得到的参数来决定调用的哪个实现类的实例,而这个参数可以动态的在配置文件中,也可以在数据库中保存,这样在不修改代码的

    相关 反射动态代理

    实例说明   代理是Java SE 1.3版新增的特性。使用代理可以在程序运行时创建一个实现指定接口的新类。通常只有在编译时无法确定需要使用哪个接口时才需要使用代理,这