Java反射和动态代理及AOP原理

反射机制的定义: 

是在运行状态中,对于任意的一个类,都能够知道这个类的所有属性和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法,这种动态获取类信息及动态调用类对象方法的功能称为java的反射机制。

反射的作用:

1、动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型。

2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类

Java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。

一、Classes

Java中的类型,要么是引用类型,要么是原始数据类型。原始数据类型是一个固定的集合,包括:boolean, byte, short, int, long, char, float, and double。其它都是引用类型,例如:arrays, string, enum 等。

类型信息在运行时是如何表示的?这是由Class对象来完成的,它包含了与类有关的信息,Java使用Class对象来执行RTTI。

对于任意一类的对象,在JVM中都会为其实例化一个不变的 Class 对象,该对象提供了可以检查该对象运行时属性、方法和类型信息的方法。同时,Class 对象也具备可以创建一个新的 class 或 对象的方法。更重要的是,Class 对象是反射API发挥作用的入口点。

所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。Class对象仅在需要的时候才会加载,static初始化是在类加载时进行的。

public class TestMain {
    public static void main(String[] args) {
        System.out.println(XYZ.name);
    }
}

class XYZ {
    public static String name = "luoxn28";

    static {
        System.out.println("xyz静态块");
    }

    public XYZ() {
        System.out.println("xyz构造了");
    }
}

输出结果为:

  类加载器首先会检查这个类的Class对象是否已被加载过,如果尚未加载,默认的类加载器就会根据类名查找对应的.class文件。

  想在运行时使用类型信息,必须获取对象(比如类Base对象)的Class对象的引用,使用功能Class.forName(“Base”)可以实现该目的,或者使用 类名.class。注意,有一点很有趣,使用功能”.class”来创建Class对象的引用时,不会自动初始化该Class对象,使用forName()会自动初始化该Class对象。为了使用类而做的准备工作一般有以下3个步骤:

  • 加载:由类加载器完成,找到对应的字节码,创建一个Class对象
  • 链接:验证类中的字节码,为静态域分配空间
  • 初始化:如果该类有超类,则对其初始化,执行静态初始化器和静态初始化块 

1,Retrieving Class Objects

第一种,使用 Object.getClass()

当一个类是继承自 Object 类的时候,那么该类的实例则可以通过调用 .getClass() 方法来获取到这个类的 Class 对象。例如:

Class c = "foo".getClass();  //返回字符串的 Class 对象

enum E {
        A, B
}
Class c = E.A.getClass(); //返回枚举类 E 的 Class 对象

byte[] bytes = new byte[1024];
Class c = bytes.getClass(); //返回 byte 类型的 Class 对象

Set s = new HashSet();
Class c = s.getClass();  //返回 HashSet 类型的 Class 对象

第二种,使用 .class

当没有类的实例时,可以使用 .class 语法来获取类的 Class 对象,同时,使用 .class 也可以获取原始数据类型的 Class 对象。例如:

 

boolean b;
Class c = b.getClass();   // compile-time error
Class c = boolean.class;  // 正确返回 boolean 类型的 Class 对象

Class c = java.io.PrintStream.class; //返回 PrintStream 类的 Class 对象

Class c = int[][][].class; //返回 int 三维数组的 Class 对象

第三种,使用 Class.forName()

如果有类的完全限定名称,则可以使用 Class.forName() 来获取对应类的 Class 对象。这种方式不能应用到原始数据类型上。例如:

 

Class c = Class.forName("com.duke.MyLocaleServiceProvider"); //返回 MyLocaleServiceProvider 类的 Class 对象

//返回一个表示 double 类型一维数组的 Class 对象
//等价于 double[].class 或者 double[] darr = new  double[9]; darr.getClass()
Class cDoubleArray = Class.forName("[D");

//返回一个表示 String 类型的二维数组的 Class 对象
Class cStringArray = Class.forName("[[Ljava.lang.String;");

2,原始包装类型的 TYPE 字段

对于原始数据类型 .class 这种方式是一种非常合适的方式来获取它的 Class 对象。但是这里还有另外一种方式,对于每一种原始数据类型和 void 类型,在 java.lang 包中都有其一个对应的包装类型。每个包装类型都有一个 TYPE 字段可以用来获取对应原始数据类型的 Class 对象。例如:

 

//以下都表示获取 double 类型的 Class 对象
Class c = Double.TYPE;
Class c1 = double.class;

3,返回 Class 对象的方法

下面举例的这些反射API都可以返回一个 Class 对象,但是前提是,你必须首先得直接或间接的已经得到一个 Class 对象。

Class.getSuperclass() 获取指定类的父类的 Class 对象

 

Class c = javax.swing.JButton.class.getSuperclass();

获取定义成员类的 Class 对象
Class.getDeclaringClass()
java.lang.reflect.Field.getDeclaringClass()
java.lang.reflect.Method.getDeclaringClass()
java.lang.reflect.Constructor.getDeclaringClass()

 

class T {
    public String name; //这里必须是 public 类型
}
Class c = T.class.getField("name").getDeclaringClass(); //返回 T 类的 Class 对象

4,Examining Class Modifiers and Types

一个类可以声明一个或多个修饰符(Modifiers),这些修饰符将影响它运行时的行为。

  • Access modifiers: public, protected, and private
  • Modifier requiring override: abstract
  • Modifier restricting to one instance: static
  • Modifier prohibiting value modification: final
  • Annotations

java.lang.reflect.Modifier 类包含了所有可用的修饰符,同时它包含了一些方法用来解析由 Class.getModifiers() 类返回的修饰符集合。

以下代码示例展示了如何获取一个类的相关声明的组件信息,包括类修饰符,泛型类型参数,实现的接口和继承路径。同时因为 Class 类实现了 java.lang.reflect.AnnotatedElement 接口,因此也可以去获取类在运行时的注解定义信息。

 

public interface ITest {
}

public class ATest {
}

@Deprecated
public class Test extends ATest implements ITest {
}

public class ClassDeclarationSpy {
    public static void main(String[] args) {
        Class c = Test.class;

        //类的全限定名称
        System.out.println(c.getCanonicalName());

        //获取类的修饰符
        System.out.println(Modifier.toString(c.getModifiers()));

        //获取泛型类型参数
        TypeVariable[] typeVariables = c.getTypeParameters();
        for (int i = 0; i < typeVariables.length; i++) {
            System.out.println(typeVariables[i].getName());
        }

        //获取实现的接口
        Type[] intfs = c.getGenericInterfaces();
        for (int i = 0; i < intfs.length; i++) {
            System.out.println(intfs[i].toString());
        }

        //获取继承的类
        List l = new ArrayList();
        printAncestor(c, l); //递归的获取
        l.forEach(clazz -> System.out.println(clazz.getCanonicalName()));

        //获取运行时定义的注解
        Annotation[] ann = c.getAnnotations();
        for (Annotation a : ann) {
            System.out.println(a.toString());
        }
    }

    private static void printAncestor(Class c, List l) {
        Class ancestor = c.getSuperclass();
        if (ancestor != null) {
            l.add(ancestor);
            printAncestor(ancestor, l);
        }
    }
}

5,Discovering Class Members

在 Class 类中包括两种类型的方法,用来访问类的字段、方法和构造器。一种是枚举这些成员信息,一种是获取指定的成员,如下图:

Java反射和动态代理及AOP原理_第1张图片

1559122617186.jpg

二,Members

反射API定义了一个 java.lang.reflect.Member 接口,这个接口由 java.lang.reflect.Field, java.lang.reflect.Method, 和 java.lang.reflect.Constructor 三个类实现。

如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个前提:这个类型在编译时必须已知,这样才能使用RTTI来识别它。Class类与java.lang.reflect类库一起对反射进行了支持,该类库包含Field、Method和Constructor类,这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样的话就可以使用Contructor创建新的对象,用get()和set()方法获取和修改类中与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。

RTTI和反射之间的真正区别只在于:

  • RTTI,编译器在编译时打开和检查.class文件
  • 反射,运行时打开和检查.class文件 

1,Fields

Field 由类型和值组成。java.lang.reflect.Field 类提供了可以操作某个对象中指定 Field 的类型信息、值信息的方法。

Obtaining Field Types

一个 Field 可能是原始数据类型也可能是引用类型。下面的代码向你展示了打印 Filed 的类型信息、泛型类型以及 Field 名称。

 

public class TestField {
    public static void main(String[] args) {
        Class c = FieldSpy.class;

        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println("name: "+field.getName()+", type: "+field.getType()+", GenericType: "+field.getGenericType());
        }
    }

    static class FieldSpy {
        public boolean[][] b = {{ false, false }, { true, true } };
        public String name  = "Alice";
        public List list;
        public T val;
    }
}

Retrieving and Parsing Field Modifiers

Field 修饰符可以包含如下几类:

  • Access modifiers: public, protected, and private
  • Field-specific modifiers governing runtime behavior: transient and volatile
  • Modifier restricting to one instance: static
  • Modifier prohibiting value modification: final
  • Annotations

通过 Field.getModifiers() 方法可以获取到一个使用整数表示的该 Field 的修饰符集合。这个整数中的字节表示的修饰符定义在 java.lang.reflect.Modifier 类中,我们可以使用该类提供的方法来判断 Field 的修饰符类型。

下面这个类向你展示了如何通过给定的修饰符检索一个 Field,同时也展示了如何判断一个 Field 是不是合成的,或者是一个枚举的常量。

 

public class FieldModifierSpy {

    public static void main(String[] args) {
        Class c = FieldModifierDemo.class;

        Field[] flds = c.getDeclaredFields();
        for (Field field : flds) {
            int modifiers = field.getModifiers();
            if (Modifier.isPublic(modifiers)) {
                System.out.println("Public FieldName: "+field.getName());
            } else if (Modifier.isProtected(modifiers)) {
                System.out.println("Protected FieldName: "+field.getName());
            }

            if (field.isSynthetic()) {
                System.out.println("Field: "+field.getName()+" is Synthetic");
            }

            if (field.isEnumConstant()) {
                System.out.println("Field: "+field.getName()+" is EnumConstant");
            }
        }

        System.out.println("----------------------");

        c = FieldModifierDemo.Spy.class;

        flds = c.getDeclaredFields();
        for (Field field : flds) {
            int modifiers = field.getModifiers();
            if (Modifier.isPublic(modifiers)) {
                System.out.println("Public FieldName: "+field.getName());
            } else if (Modifier.isProtected(modifiers)) {
                System.out.println("Protected FieldName: "+field.getName());
            } else if (Modifier.isPrivate(modifiers)) {
                System.out.println("Private FieldName: "+field.getName());
            }

            if (field.isSynthetic()) {
                System.out.println("Field: "+field.getName()+" is Synthetic");
            }

            if (field.isEnumConstant()) {
                System.out.println("Field: "+field.getName()+" is EnumConstant");
            }
        }

        System.out.println("----------------------");

        c = FieldModifierDemo.Inner.class;

        flds = c.getDeclaredFields();
        // flds = c.getFields(); //这里将不包含合成字段
        for (Field field : flds) {
            int modifiers = field.getModifiers();
            if (Modifier.isPublic(modifiers)) {
                System.out.println("Public FieldName: "+field.getName());
            } else if (Modifier.isProtected(modifiers)) {
                System.out.println("Protected FieldName: "+field.getName());
            } else if (Modifier.isPrivate(modifiers)) {
                System.out.println("Private FieldName: "+field.getName());
            } else if (Modifier.isFinal(modifiers)) {
                System.out.println("Final FieldName: "+field.getName());
            }

            if (field.isSynthetic()) {
                System.out.println("Field: "+field.getName()+" is Synthetic");
            }

            if (field.isEnumConstant()) {
                System.out.println("Field: "+field.getName()+" is EnumConstant");
            }
        }
    }

    static class FieldModifierDemo {
        protected volatile int share;
        public int instance;

        enum Spy { BLACK , WHITE }

        class Inner {}
    }
}

需要注意的是,合成字段在 getFields() 方法的返回值中并未包含,这是该方法和 getDeclaredFields() 方法的一点区别。同时,由于 Field 类实现了 java.lang.reflect.AnnotatedElement 接口,因此我们也可以方便的获取到该字段上的所有运行时注解。

Getting and Setting Field Values

下面的代码展示了如何设置 Field 的值。

 

public class SetFieldValTest {
    enum Tweedle { DEE, DUM }

    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException {
        Book book = new Book();
        String fmt = "%6S:  %-12s = %s%n";

        Class c = book.getClass();

        //对于要设置的字段类型是原始数据类型的包装类型时,不能使用 setLong, setInt 等方法
        //因为在反射中不会自动装箱和拆箱,因此只能用 set 方法实现
        Field chap = c.getDeclaredField("chapters");
        chap.set(book, Long.valueOf(12));
        out.format(fmt, "after", "chapters", chap.get(book));

        Field age = c.getDeclaredField("age");
        age.setInt(book, 10);
        out.format(fmt, "after", "age", age.get(book));

        Field chars = c.getDeclaredField("characters");
        String[] newChars = { "Queen", "King" };
        chars.set(book, newChars);
        out.format(fmt, "after", "characters", Arrays.asList(book.characters));

        Field t = c.getDeclaredField("twin");
        t.set(book, Tweedle.DUM);
        out.format(fmt, "after", "twin", t.get(book));

        //对于用 private、final 修饰的字段,在设置值之前必须要设置其访问属性
        //setAccessible 方法只有在 security context 中被允许时才能够成功
        Field flag = c.getDeclaredField("flag");
        flag.setAccessible(true);
        flag.set(book, true);
        out.format(fmt, "after", "flag", flag.getBoolean(book));
        flag.setAccessible(false);
    }

    static class Book {
        public String[] characters = { "Alice", "White Rabbit" };
        public Tweedle twin = Tweedle.DEE;
        public int age = 5;
        public Long chapters = 0L;
        private boolean flag = false;
    }
}

设置字段的值有可能会产生两个异常:一个是修改使用 private、final 修饰的字段时访问权限被拒绝,一个是使用 setLong、setInt 等方法时抛出设置失败异常。关于解决方案和有可能的原因已经在上述代码中注释描述,直接看代码。

2,Methods

方法由返回值,参数以及可能抛出的异常构成。java.lang.reflect.Method 类提供了方法可以获取方法参数的类型信息、方法的返回值信息,以及可以执行指定对象的方法的方法。

Obtaining Method Type Information

以下代码向你展示了如何获取指定类中指定方法的参数信息、返回值信息、异常信息以及判断该方法的参数是否是可变参数,如下:

 

public class MethodSpy {
    private static final String  fmt = "%24s: %s%n";

    public static void main(String[] args) {
        try {
            String className = "java.lang.Class";
            String methodName = "cast";

            Class c = Class.forName(className);
            Method[] allMethods = c.getDeclaredMethods();
            for (Method m : allMethods) {
                if (!m.getName().equals(methodName)) {
                    continue;
                }
                out.format("%s%n", m.toGenericString()); //获取方法的泛型形式定义

                out.format(fmt, "ReturnType", m.getReturnType());
                out.format(fmt, "GenericReturnType", m.getGenericReturnType());

                Class[] pType  = m.getParameterTypes();
                Type[] gpType = m.getGenericParameterTypes();
                for (int i = 0; i < pType.length; i++) {
                    out.format(fmt,"ParameterType", pType[i]);
                    out.format(fmt,"GenericParameterType", gpType[i]);
                }

                Class[] xType  = m.getExceptionTypes();
                Type[] gxType = m.getGenericExceptionTypes();
                for (int i = 0; i < xType.length; i++) {
                    out.format(fmt,"ExceptionType", xType[i]);
                    out.format(fmt,"GenericExceptionType", gxType[i]);
                }

                out.println("是否是可变参数:"+m.isVarArgs());
                out.println("------------------------------");
            }

            // production code should handle these exceptions more gracefully
        } catch (ClassNotFoundException x) {
            x.printStackTrace();
        }
    }
}

上面的代码中,如果用下面的代码简单修改一下:

 

String className = "java.io.PrintStream";
String methodName = "format";

运行之后你会发现,对于一个类中的多个重载方法,getDeclaredMethods 方法都会把它们返回回来。

Obtaining Names of Method Parameters

Method 类提供了 getParameters() 方法,用来获取方法的参数名称、类型等信息。但是在 .class 文件中并没有存储参数的名称信息,这是因为许多使用和生成 .class 文件的工具,不太期望 .class 文件占用更大的静态或动态空间,因为它们处理大的 .class 文件时,则会要求 jvm 占用更多的内存,同时,方法参数名称可能会暴露方法的一些安全信息,如果 passwrod 名称等。

为了让 .class 文件中存储方法参数的名称信息,则需要在编译 java 源码时给 javac 命令指定 -parameters 参数。

Retrieving and Parsing Method Modifiers

使用 getModifiers 方法可以获取方法的修饰符信息,判断方法具有哪些修饰符和 Field 中的操作是一样的。

Invoking Methods

Method 类提供了一个 invoke() 方法用来通过反射的方式来执行一个类中的某个方法,invoke() 方法需要两个参数,第一个参数是类的实例对象,如果要执行的方法是 static 修饰的,那么第一个参数固定为 null,第二个参数是一个可变参数,用来向要执行的方法传递参数。

示例代码如下:

 

public class MethodSpy {
    private static final String  fmt = "%24s: %s%n";

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        Class c = Deet.class;
        Method[] allMethods = c.getDeclaredMethods();
        Object t = c.newInstance();
        for (Method m : allMethods) {
            m.setAccessible(true);
            Object o;
            if (m.getParameters().length > 0) {
                o = m.invoke(t, new Locale("zh-CN"));
            } else {
                o = m.invoke(t);
            }
            if (Modifier.isStatic(m.getModifiers())) {
                out.println("invoke static method: "+m.getName());
                o = m.invoke(null);
            }
            out.println(o);
        }
    }

    static class Deet {
        private boolean testBar() {
            return true;
        }

        private int testFoo(Locale l) {
            return 0;
        }
        private static int testStatic() {
            return 1;
        }
    }
}

3,Constructors

反射API提供了 java.lang.reflect.Constructor 类用来操作构造方法,和 Method 类类似。

Finding Constructors

通过 Class.getDeclaredConstructors() 可以获取到指定类的全部构造方法(包括 private 修饰的构造方法),但是 Class.getConstructors() 方法则只返回 public 修饰的构造方法。

Retrieving and Parsing Constructor Modifiers

直接参考普通方法的获取方式即可。

Creating New Class Instances

有两种用来创建一个类的实例的方式:

 

java.lang.reflect.Constructor.newInstance() //第一种,通过构造函数

Class.newInstance() //第二种,直接使用 Class 类

一般,推荐使用第一种,因为如下原因:

  • Class.newInstance() 它不理会类的构造方法的参数,只会执行无参数的构造方法
  • Class.newInstance() 它不理会构造函数的异常是 unchecked 或者是 checked,它直接将异常包装成 InvocationTargetException 异常,然后抛出
  • Class.newInstance() 不能执行 private 修饰的构造方法

下面的代码示例,使用第一种方式来创建一个类的实例,并且该代码也展示了,如何获取到指定的构造方法,如下:

 

public class RestoreAliases {
    private static Map defaultAliases = new HashMap();
    private static Set defaultAliasesSet = new HashSet();
    static {
        defaultAliases.put("Duke", "duke@i-love-java");
        defaultAliases.put("Fang", "fang@evil-jealous-twin");

        defaultAliasesSet.add("zh");
        defaultAliasesSet.add("cn");
    }

    public static void main(String[] args) {
        try {
            //获取具有指定参数个数和类型的构造方法
//            Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashMap.class);
            Constructor ctor = EmailAliases.class.getDeclaredConstructor(HashSet.class);
            ctor.setAccessible(true);
//            EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliases);
            EmailAliases email = (EmailAliases)ctor.newInstance(defaultAliasesSet);
            email.printKeys();

            // production code should handle these exceptions more gracefully
        } catch (InstantiationException x) {
            x.printStackTrace();
        } catch (IllegalAccessException x) {
            x.printStackTrace();
        } catch (InvocationTargetException x) {
            x.printStackTrace();
        } catch (NoSuchMethodException x) {
            x.printStackTrace();
        }
    }

    static class EmailAliases {
        private Set aliases;
        private EmailAliases(HashMap h) {
            out.println("EmailAliases HashMap run");
            aliases = h.keySet();
        }

        private EmailAliases(HashSet h) {
            out.println("EmailAliases HashSet run");
            aliases = h;
        }

        public void printKeys() {
            out.format("Mail keys:%n");
            for (String k : aliases)
                out.format("  %s%n", k);
        }
    }
}

三 AOP概述

       AOP(Aspect Oriented Programing),即面向切面编程,它主要用于日志记录、性能统计、安全控制、事务处理、异常处理等方面。它的主要意图就要将日志记录,性能统计,安全控制、事务处理、异常处理等等代码从业务逻辑代码中清楚地划分出来。通过对这些行为的分离,我们希望可以将它们独立地配置到业务逻辑方法中,而要改变这些行为的时候也不需要影响到业务逻辑方法代码。

       下面让我们来看一个利用AOP来实现日志记录的例子,在没有使用AOP之前,我们的代码如下面所讲述。

       下面这段代码为业务的接口类代码:

package org.amigo.proxy;

/** *//**
 *业务逻辑类接口.    
 */
publicinterface BusinessObj {

    /** *//**
     *执行业务.
     */
    publicvoid process();
}

      BusinessObj接口的某个实现类代码如下:

package org.amigo.proxy;

/** *//**
 * 业务逻辑对象实现类.
 */
public class BusinessObjImpl implements BusinessObj {

       /** *//**
        * 执行业务.
        */
       public void process() {
              try {
                     System.out.println("before process");
                     System.out.println("执行业务逻辑");
                     System.out.println("after process");
              } catch (Exception e) {
                     System.err.println("发生异常:" + e.toString());
              }
       }
}

      在上例中我们可以看到,在执行业务方法前、执行业务方法后以及异常发生时的日志记录,我们都是通过在对应的类中写入记录日志的代码来实现的,当有这种日志记录需求的业务逻辑类不断增多时,将会给我们的维护带来很大困难,而且,在上面的例子中,日志代码和业务逻辑代码混合在一起,为日后的维护工作又抹上了一层“恐怖”色彩。

       按照AOP的思想,我们首先需要寻找一个切面,在这里我们已经找到,即在业务逻辑执行前后以及异常发生时,进行相应的日志记录。我们需要将这部分日志代码放入一个单独的类中,以便为以后的修改提供方便。

       我们在截获某个业务逻辑方法时,可以采用Java的动态代理机制来实现。在下节中我们将重点讲述Java的动态代理机制。

四,实现动态代理

首先应该理解代理模式,代理模式分为静态代理和动态代理。

代理模式是为了提供额外或不同的操作,而插入的用来替代”实际”对象的对象,这些操作涉及到与”实际”对象的通信,因此代理通常充当中间人角色。Java的动态代理比代理的思想更前进了一步,它可以动态地创建并代理并动态地处理对所代理方法的调用。在动态代理上所做的所有调用都会被重定向到单一的调用处理器上,它的工作是揭示调用的类型并确定相应的策略。 

学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,它利用的是反射机制,依赖注入就不用多说了,而对于Spring的核心AOP来说,使用了动态代理,其实底层也是反射。我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制,所以本篇随笔就是对java的动态机制进行一个回顾。

JDK中提供了 Proxy 类来实现动态代理

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。我们来看看InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

我们看到这个方法一共接受三个参数,那么这三个参数分别代表什么呢?

proxy:  指代我们所代理的那个真实对象
method:  指代的是我们所要调用真实对象的某个方法的Method对象
args:  指代的是调用真实对象某个方法时接受的参数

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h) throws IllegalArgumentException

这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:  

loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

示例代码如下:

public interface MyDemoInterface {
    int run(int time);
}

public class MyDemoClass implements MyDemoInterface {
    @Override
    public int run(int time) {
        return time*10;
    }
}

public class MyProxyHandler implements InvocationHandler {
    // 这个就是我们要代理的真实对象
    private Object object;

    //    构造方法,给我们要代理的真实对象赋初值
    public MyProxyHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before invoke");

        //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        return method.invoke(object, args);

        //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after invoke");
    }
}

public class TestJdkProxy {
    public static void main(String[] args) {
        //    我们要代理的真实对象
        MyDemoInterface myDemoClass = new MyDemoClass();

        //    我们要代理哪个真实对象,就将该对象传进去,最后是通过该真实对象来调用其方法的
        MyProxyHandler handler = new MyProxyHandler(myDemoClass);

        /*
         * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
         * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
         * 第二个参数realSubject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
         * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
         */
        MyDemoInterface myDemoClassProxy = (MyDemoInterface) Proxy.newProxyInstance(
                MyDemoInterface.class.getClassLoader(),
                new Class[] {MyDemoInterface.class},
                handler
        );

        System.out.println(subject.getClass().getName());
        System.out.println(myDemoClassProxy.run(10));
    }
}

控制台的输出: 

$Proxy0

before invoke
100
after invoke

我们首先来看看 $Proxy0 这东西,我们看到,这个东西是由 System.out.println(subject.getClass().getName()); 这条语句打印出来的,那么为什么我们返回的这个代理对象的类名是这样的呢?

Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
                .getClass().getInterfaces(), handler);

可能我以为返回的这个代理对象会是MyDemoInterface 类型的对象,或者是InvocationHandler的对象,结果却不是,首先我们解释一下为什么我们这里可以将其转化为MyDemoInterface 类型的对象?原因就是在newProxyInstance这个方法的第二个参数上,我们给这个代理对象提供了一组什么接口,那么我这个代理对象就会实现了这组接口,这个时候我们当然可以将这个代理对象强制类型转化为这组接口中的任意一个,因为这里的接口是MyDemoInterface 类型,所以就可以将其转化为MyDemoInterface 类型了

同时我们一定要记住,通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号

接着我们来看看这句 myDemoClassProxy.run(10)

这里是通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 myDemoClass类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行: 

public Object invoke(Object object, Method method, Object[] args)
            throws Throwable
    {
        //  在代理真实对象前我们可以添加一些自己的操作
        System.out.println("before invoke");
        
        System.out.println("Method:" + method);
        
        //    当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        method.invoke(subject, args);
        
        //  在代理真实对象后我们也可以添加一些自己的操作
        System.out.println("after invoke");
        
        return null;
    }

在真正通过代理对象来调用真实对象的方法的时候,可以在该方法前后添加自己的一些操作

cglib 来实现动态代理

JDK的动态代理要求要代理的类必须是实现了某个接口,不然无法代理。因此我们这个时候就需要使用 cglib 来实现动态代理,示例代码如下:

public class DemoClass {
    public int run(int time) {
        return time*10;
    }
}

public class MyCglibProxy implements MethodInterceptor {
    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(o, args);
    }
}

public class TestCglibProxy {
    public static void main(String[] args) {
        DemoClass demoClass = new DemoClass();

        MyCglibProxy cglibProxy = new MyCglibProxy();

        DemoClass demoClassProxy = (DemoClass) cglibProxy.getInstance(demoClass);

        System.out.println(demoClassProxy.run(10));
    }
}

五,Spring中AOP原理

在揭示SpringAOP的原理之前,我们需要先分析JDK的Proxy类的执行流程,而后根据这个流程来反推Spring中关于AOP的两种实现方式的大体原理,之后再通过查看Spring的源码来进行证明。

首先我们把上面使用JDK实现动态代理的代码中修改如下几个地方:

public class MyProxyHandler implements InvocationHandler {
    private Object object;

    public MyProxyHandler(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.print(method.getName()+"==>");
        for (Parameter parameter :method.getParameters()) {
            System.out.println(parameter.getType().toGenericString());
        }
        System.out.println();

        return method.invoke(object, args);
    }
}

public class TestJdkProxy {
    public static void main(String[] args) {
        MyDemoInterface myDemoClass = new MyDemoClass();

        MyProxyHandler handler = new MyProxyHandler(myDemoClass);

        MyDemoInterface myDemoClassProxy = (MyDemoInterface) Proxy.newProxyInstance(
                MyDemoInterface.class.getClassLoader(),
                new Class[] {MyDemoInterface.class},
                handler
        );

        //打印代理类的类名
        System.out.println(myDemoClassProxy.getClass().toGenericString());
        System.out.println();

        //打印代理类实现的接口信息
        for (Type type : myDemoClassProxy.getClass().getGenericInterfaces()) {
            System.out.println(type.getTypeName());
        }
        System.out.println();

        //打印代理类中的方法名称
        for (Method method : myDemoClassProxy.getClass().getDeclaredMethods()) {
            System.out.print(method.getName()+"==>");
            for (Parameter parameter :method.getParameters()) {
                System.out.println(parameter.getType().toGenericString());
            }
            System.out.println();
        }

        System.out.println("------------------");
        System.out.println(myDemoClassProxy.run(10));
    }
}

实际就是加了一些打印信息,利用这些打印信息,再结合 Proxy 类中的如下源码(源码解释看注释):

private static final Class[] constructorParams =
        { InvocationHandler.class };

//获取到的代理类构造函数
protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
}

public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        //创建出代理类,实际就是 Proxy 类本身
        Class cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //获取到代理中参数类型为 constructorParams 类型的构造函数,实际就是获取到了上面摘抄
            //下来的构造函数,
            final Constructor cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 创建代理类的实例,实际就相当于调用了
            // new Proxy(InvocationHandler h)
            // 注意,这里的 InvocationHandler 就是我们外面传进来的,即我们上面代码中的 MyProxyHandler 实例
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

根据对 JDK Proxy 类的源码分析,以及 TestJdkProxy 类中的打印信息,最终分析推理且证明得到如下结论:

  • Proxy.newProxyInstance 方法实际做的事情就是在内部帮我们动态的生成了一个 Proxy 类,然后这个生成的类实现了我们准备被代理类 MyDemoClass 类的接口,即实现了 MyDemoInterface 接口,这也就是为什么 Proxy.newProxyInstance 返回的实例可以强制转换成 MyDemoInterface 类型,根本原因是因为这个实例对应的类也实现了 MyDemoInterface 接口
  • Proxy类中生成的代理类同时也实现了 MyDemoInterface 中的全部方法,同时参数信息也是保持一致的,这也就解释了为什么 Proxy.newProxyInstance 返回的实例在后续阶段可以直接调用 MyDemoInterface 中的方法
  • Proxy类中生成的代理类中实现的 MyDemoInterface 中的方法的方法执行流程为:直接调用 InvocationHandler 中的 invoke 方法同时传递进去必要的参数然后返回 invoke 方法的返回值,但是这里的返回值数据类型必须要和接口中的方法的返回值数据类型一致,不然会抛异常。
  • 我们定义的 InvocationHandler 中的 invoke 方法成为了我们实际要执行的被代理类的方法的入口,在该入口中,我们可以自主的选择执行被代理类方法的时机,因此该 invoke 方法可以被认为就是 AOP 中的JointPoint

清楚了 Proxy 类的流程和动态代理的本质之后,现在来看 Spring AOP 的实现原理。在开始之前,需要先澄清一些关于 AOP 的概念。首先 AOP 是一种编程模式,称为面向切面编程。它与传统面向对象编程的区别在于,使用 AOP 可以在系统中的某些位置动态的插入逻辑而不影响且不用修改原来的执行逻辑和代码。通常的应用包括日志打印,权限控制,事物控制等。

AOP 中包括如下几个术语:

  • JointPoint 连接点,即上面我们提到的 invoke 方法
  • Advice 通知,定义在连接点的哪个位置执行插入的逻辑,通常包括:前置,后置,环绕等
  • Pointcut 切入点,在 AOP 中使用 AOP 表达式寻找到的一组连接点
  • Aspect 切面,上述三个概念的组合称为一个切面

清楚了概念之后,再来看原理。AOP 是一种规范,没有固定的实现,它的底层技术原理就是动态代理。在 Spring AOP 中提供了两种方式的动态代理实现:JDK 动态代理 和 cglib 动态代理。分别对应的源码类为:JdkDynamicAopProxy 和 CglibAopProxy,它们的公共父类接口为:AopProxy。

我们通过阅读 JdkDynamicAopProxy 的如下部分源码可知,Spring 的 JdkDynamicAopProxy 实现实际就是 JDK Proxy 的一个简单包装,整体实现流程和上面我们分析的过程基本一致。关键源码和注释说明如下:

final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable {

@Override
public Object getProxy(ClassLoader classLoader) {
        if (logger.isDebugEnabled()) {
            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
        }
                // 得到代理类的接口信息,实际得到的就是 AopProxy 类,说明 Spring 中所有 Bean 的代理类实际
                // 都实现了  AopProxy 类,虽然该类是空的
        Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
                // 利用 JDK Proxy 创建代理类实例,同时指定 InvocationHandler 为它自己
        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

}

理清了 JdkDynamicAopProxy 的流程和原理,那么如果你对 cglib 较为熟悉的话,相信 CglibAopProxy 也一样可以看明白,因为这个类实际上就是 cglib 实现动态代理的一个简单包装。

掌握了原理之后再来学习 Spring AOP,会发现,这个实际非常简单。首先 JointPoint 的动态织入代理 Spring 通过底层的动态代理框架已经帮我们实现了,我们要做的就是学习在 Spring 中可以使用什么样的表达式来寻找这些连接点,即首先要学习如何定义一个 Pointcut,然后有了切入点之后再根据实际情况说明我们要执行的通知,当然这也需要符合 Spring 声明通知的规范,最后则是学习在 Spring 中如何把 Pointcut 和 Advice 组织到一起形成一个切面,这些都是固定的规范语法,查看文档即可,而根据的底层原理我们已经掌握了。

关于 Spring AOP 的语法规范可以查看这里

六.Spring中AOP的模拟实现

       在学习了Java的动态代理机制后,我们在本节中将学习Java的动态代理机制在Spring中的应用。首先我们创建一个名为AopHandler的类,该类可生成代理对象,同时可以根据设置的前置或后置处理对象分别在方法执行前后执行一些另外的操作,该类的内容如下所示:

package org.amigo.proxy;

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

/** *//**
 * AOP处理器.   
 */
public class AopHandler implements InvocationHandler {   
       //需要代理的目标对象 
       private Object target;   
       //方法前置顾问 
       Advisor beforeAdvisor;
       //方法后置顾问
       Advisor afterAdvisor;

       /** *//**
        * 设置代理目标对象,并生成动态代理对象. 
        * @param target 代理目标对象
        * @return 返回动态代理对象
        */
       public Object setObject(Object target) { 
              //设置代理目标对象
              this.target = target;   
              //根据代理目标对象生成动态代理对象
              Object obj = Proxy.newProxyInstance(
                            target.getClass().getClassLoader(),   
                            target.getClass().getInterfaces(), this);   
              return obj;   
       }   

       /** *//**
        * 若定义了前置处理,则在方法执行前执行前置处理
        * 若定义了后置处理,则在方法调用后调用后置处理.
        * @param proxy 代理对象
        * @param method 调用的业务方法
        * @param args 方法的参数
        * @return 返回结果信息
        * @throws Throwable
        */ 
       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              //进行业务方法的前置处理
              if (beforeAdvisor != null) {
                     beforeAdvisor.doInAdvisor(proxy, method, args);
              }

              //执行业务方法
              Object result = method.invoke(target, args);   
              //进行业务方法的后置处理
              if (afterAdvisor != null) { 
                     afterAdvisor.doInAdvisor(proxy, method, args);
              }
              //返回结果对象
              return result;   
       }

       /** *//**
        * 设置方法的前置顾问.
        * @param advisor 方法的前置顾问
        */
       public void setBeforeAdvisor(Advisor advisor) {   
              this.beforeAdvisor = advisor;   
       }   

       /** *//**
        * 设置方法的后置顾问.
        * @param advisor 方法的后置顾问
        */
       public void setAfterAdvisor(Advisor advisor) {   
              this.afterAdvisor = advisor;   
       }   
}

    在上类中,前置和后置顾问对象都继承Advisor接口,接下来让我们来看看顾问接口类的内容,该类定义了doInAdvisor(Object proxy, Method method, Object[] args)方法,如下所示:

package org.amigo.proxy;

import java.lang.reflect.Method;

/** *//**
 *顾问接口类.
 */
publicinterface Advisor {   

    /** *//**
     *所做的操作.
     */
    publicvoid doInAdvisor(Object proxy, Method method, Object[] args);   
}

BeforeMethodAdvisor和AfterMethodAdvisor都实现了Advisor接口,分别为方法的前置顾问和后置顾问类。

BeforeMethodAdvisor.java文件(前置顾问类)的内容如下所示:

package org.amigo.proxy;

import java.lang.reflect.Method;

/** *//**
 *
 *方法前置顾问,它完成方法的前置操作.
 */
publicclass BeforeMethodAdvisor implements Advisor {
    /** *//**
     *在方法执行前所进行的操作.
     */
    publicvoid doInAdvisor(Object proxy, Method method, Object[] args) {   
       System.out.println("before process: " + method);   
    }   
} 

 

 AfterMethodAdvisor.java文件(后置顾问类)的内容如下所示:

package org.amigo.proxy;

import java.lang.reflect.Method;

/** *//**
 *方法的后置顾问,它完成方法的后置操作.   
 */
publicclass AfterMethodAdvisor implements Advisor {

    /** *//**
     *在方法执行后所进行的操作.
     */
    publicvoid doInAdvisor(Object proxy, Method method, Object[] args) {   
       System.out.println("after process: " + method);   
    }   
}

这两个类分别在方法执行前和方法执行后做一些额外的操作。

       对于在配置文件中对某个bean配置前置或后置处理器,我们可以在bean中增加两个属性aop和aopType,aop的值为对应的前置顾问类或后置顾问类的名称,aopType用于指明该顾问类为前置还是后置顾问,为before时表示为前置处理器,为after时表示为后置处理器,这时候我们需要修改上一篇文章中的BeanFactory.java这个文件,添加其对aop和aopType的处理,修改后的该文件内容如下:

package org.amigo.proxy;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/** *//**
 *bean工厂类.      
 */
publicclass BeanFactory {
    private Map beanMap = new HashMap();

    /** *//**
     *bean工厂的初始化.
     *@paramxmlxml配置文件
     */
    publicvoid init(String xml) {
       try {
           //读取指定的配置文件
           SAXReader reader = new SAXReader();
           ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
           InputStream ins = classLoader.getResourceAsStream(xml);
           Document doc = reader.read(ins);
           Element root = doc.getRootElement();   
           Element foo;

           //创建AOP处理器
           AopHandler aopHandler = new AopHandler();

           //遍历bean
           for (Iterator i = root.elementIterator("bean"); i.hasNext();) {   
              foo = (Element) i.next();
              //获取bean的属性id、class、aop以及aopType
              Attribute id = foo.attribute("id");   
              Attribute cls = foo.attribute("class");
              Attribute aop = foo.attribute("aop");
              Attribute aopType = foo.attribute("aopType");

              //配置了aop和aopType属性时,需进行拦截操作
              if (aop != null && aopType != null) {
                  //根据aop字符串获取对应的类
                  Class advisorCls = Class.forName(aop.getText());
                  //创建该类的对象
                  Advisor advisor = (Advisor) advisorCls.newInstance();

                  //根据aopType的类型来设置前置或后置顾问
                  if ("before".equals(aopType.getText())) {
aopHandler.setBeforeAdvisor(advisor);
                  } elseif ("after".equals(aopType.getText())) {
                     aopHandler.setAfterAdvisor(advisor);
                  }
              }

              //利用Java反射机制,通过class的名称获取Class对象
              Class bean = Class.forName(cls.getText());


              //这里也可以通过反射进行操作
              //获取对应class的信息
              java.beans.BeanInfo info = java.beans.Introspector.getBeanInfo(bean);
              //获取其属性描述
              java.beans.PropertyDescriptor pd[] = info.getPropertyDescriptors();
              //设置值的方法
              Method mSet = null;
              //创建一个对象
              Object obj = bean.newInstance();
              //遍历该bean的property属性
              for (Iterator ite = foo.elementIterator("property"); ite.hasNext();) {   
                  Element foo2 = (Element) ite.next();
                  //获取该property的name属性
                  Attribute name = foo2.attribute("name");
                  String value = null;

                  //获取该property的子元素value的值
                  for(Iterator ite1 = foo2.elementIterator("value"); ite1.hasNext();) {
                     Element node = (Element) ite1.next();
                     value = node.getText();
                     break;
                  }

                  for (int k = 0; k < pd.length; k++) {
                     if (pd[k].getName().equalsIgnoreCase(name.getText())) {
                         mSet = pd[k].getWriteMethod();
                         //利用Java的反射机制调用对象的某个set方法,并将值设置进去
                         mSet.invoke(obj, value);
                     }
                  }
              }

              //为对象增加前置或后置顾问
              obj = (Object) aopHandler.setObject(obj);

              //将对象放入beanMap中,其中key为id值,value为对象
              beanMap.put(id.getText(), obj);
           }
       } catch (Exception e) {
           System.out.println(e.toString());
       }
    }

    /** *//**
     *通过bean的id获取bean的对象.
     *@parambeanNamebean的id
     *@return返回对应对象
     */
    public Object getBean(String beanName) {
       Object obj = beanMap.get(beanName);
       return obj;
    }

    /** *//**
     *测试方法.
     *@paramargs
     */
    publicstaticvoid main(String[] args) {
       BeanFactory factory = new BeanFactory();
       factory.init("config.xml");
       BusinessObj obj = (BusinessObj) factory.getBean("businessObj");
       obj.process();
    }
}

/**

*业务处理类

*/

public class BusinessServiceImpl implements BusinessService{

 @Override
 public void process() {
  System.out.println("process is running......");
  
 }

}

     观察此类我们可以发现,该类添加了对bean元素的aop和aopType属性的处理。编写完该文件后,我们还需要修改src目录下的配置文件:config.xml文件,在该文件中添加名为businessObj的bean,我们为其配置了aop和aopType属性,增加了方法的前置顾问。增加的部分为:

       此时运行BeanFactory.java这个类文件,运行结果如下:

before process:public abstract void service.BusinessService.process()
process is running......

       由运行结果可以看出,前置处理已经生效。

       本节中的例子只是实现了Spring的AOP一小部分功能,即为某个bean添加前置或后置处理,在Spring中,考虑的比这多很多,例如,为多个bean配置动态代理等等。但是究其根源,Spring中AOP的实现,是基于Java中无比强大的反射和动态代理机制。

 

你可能感兴趣的:(java)