Java反射机制
反射的概念:程序可以访问、检测和修改它本身状态或行为的一种能力。
Java中的类反射:
Reflection是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查,并能直接操作程序的内部属性。
- 检测类:
- Reflection的工作机制
- Java类反射中的主要方法
四种独立的反射调用:构造函数、字段和方法、java.lang.Class
构造函数
Constructor getConstructor(Class[] params) -- 获取使用特殊的参数类型的公共构造函数
Constructor[] getConstructors() --获取类的所有公共构造函数
Constructor getDeclaredConstructors()--获取类的所有构造函数(接入级别无关)
字段
Field getField(String name) -- 获得命名的公共字段
Field[] getFields() -- 获得类的所有公共字段
Field getDeclaredField(String name) -- 获得类声明的命名的字段
Field[] getDeclaredFields() -- 获得类声明的所有字段
方法函数
Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名
的公共方法
Method[] getMethods() -- 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,
获得类声明的命名的方法
Method[] getDeclaredMethods() -- 获得类声明的所有方法
- Reflection
- 安全性和发射
Java编程语言定义一种多级别方法来处理反射的安全性。基本模式是对反射实施与应用于源代码接入相同的限制。
从任意位置到类公共组件的接入
类自身外部无任何到私有组件的接入
受保护和打包(缺省接入)组件的有限接入
- 反射性能
反射是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。
- 利用反射实现类的动态加载
谈 java 反射机制
java 程序在运行时可以获得任何一个类的字节码信息,包括类的修饰符
(public,static 等),基类(超类,父类),实现的接口,字段和方法等信息.
java 程序在运行时可以根据字节码信息来创建该类的实例对象,改变对象的字段内容和调用对象方法.
Java 的反射机制是通过反射API 来实现的,它允许程序在运行过程中取得任何一个已知名称的类的内部信息.反射API 位于java.lang.reflect 包中.主要包括以下
几类:
1).Constructor 类:用来描述一个类的构造方法
2).Field 类:用来描述一个类的成员变量
3).Method 类:用来描述一个类的方法.
4).Modifer 类:用来描述类内各元素的修饰符
5).Array:用来对数组进行操作.
Constructor, Field,Method 这三个类都是JVM(虚拟机)在程序运行时创建的,用来表示加载类中相应的成员.这三个类都实现了java.lang.reflect.Member 接口,Member 接口定义了获取类成员或构造方法等信息的方法.要使用这些反射API,必须先得到要操作的对象或类的Class 类的实例.通过调用Class 类的newInstance 方法(只能调用类的默认构造方法)可以创建类的实例.
一.获取类的构造方法的Constructor 对象(数组)
- Constructor[] getDeclaredConstructors();返回已加载类声明的所有的构造方法的Constructor 对象数组.
- Constructor getDeclaredConstructor(Class[] paramTypes);返回已加载类声明的指定构造方法的Constructor 对象,paramTypes 指定了参数类型.
- Constructor[] getConstructors();返回已加载类声明的所有的public 类型的构造方法的Constructor 对象数组.
- Constructor getConstructor(Class[] paramTypes); 返回已加载类声明的指定的public 类型的构造方法的Constructor 对象,paramTypes 指定了参数类型.
二.获取类成员变量的 Field 对象(数组)
- Field[] getDeclaredFields():返回已加载类声明的所有成员变量的Field 对象数组,不包括从父类继承的成员变量.
- Field getDeclaredField(String name): 返回已加载类声明的所有成员变量的Field 对象,不包括从父类继承的成员变量,参数name 指定成员变量的名称.
- Field[] getFields():返回已加载类声明的所有public 型的成员变量的Field 对象数组,包括从父类继承的成员变量
- Field getField(String name):返回已加载类声明的所有成员变量的Field 对象,包括从父类继承的成员变量,参数name 指定成员变量的名称.
三.获取类的方法的 Method 对象(数组)
- Method[] getDeclaredMethods():返回已加载类声明的所有方法的Method 对象数组,不包括从父类继承的方法.
- Method getDeclaredMethod(String name,Class[] paramTypes): 返回已加载类声明的所有方法的Method 对象,不包括从父类继承的方法,参数name 指定方法的名称,参数paramTypes 指定方法的参数类型.
- Method[] getMethods():返回已加载类声明的所有方法的Method 对象数组,包括从父类继承的方法.
- Method getMethod(String name,Class[] paramTypes): 返回已加载类声明的所有方法的Method 对象,包括从父类继承的方法,参数name 指定方法的名称,参数paramTypes 指定方法的参数类型.
四.检索类的其他信息
- int getModifiers():返回已加载类的修饰符的整形标识值.
- Package getPackage():返回已加载类的包名
- Class getSuperclass(): 返回已加载类的父类的Class 实例.
- Class [] getInterfaces():返回已加载类实现的接口的Class 对象数组.
- boolean isInterface():返回已加载类是否是接口.
Java 反射机制
反射机制:所谓的反射机制就是Java语言在运行时拥有一项自观的能力。
Java的反射机制的实现要借助于4个类:class,constructor,Field,Method。
Class代表类的对象、Constructor代表类的构造器对象,Field代表类的属性对象,Method代表类的方法对象。
对于以下三类组件中的任何一类来说-- 构造函数、字段和方法-- java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。调用都遵循一种标准格式。以下是用于查找构造函数的一组反射调用:
Constructor getConstructor(Class[] params) -- 获得使用特殊的参数类型的公共构造函数.
Constructor[] getConstructors() -- 获得类的所有公共构造函数.
Constructor getDeclaredConstructor(Class[] params) -- 获得使用特定参数类型的构造函数(与接入级别无关).
Constructor[] getDeclaredConstructors() -- 获得类的所有构造函数(与接入级别无关).
获得字段信息的Class 反射调用不同于那些用于接入构造函数的调用,在参数类型数组中使用了字段名:
Field getField(String name) -- 获得命名的公共字段.
Field[] getFields() -- 获得类的所有公共字段.
Field getDeclaredField(String name) -- 获得类声明的命名的字段.
Field[] getDeclaredFields() -- 获得类声明的所有字段.
用于获得方法信息函数:
Method getMethod(String name, Class[] params) -- 使用特定的参数类型,获得命名的公共方法.
Method[] getMethods() -- 获得类的所有公共方法.
Method getDeclaredMethod(String name, Class[] params) -- 使用特写的参数类型,获得类声明的命名的方法.
Method[] getDeclaredMethods() -- 获得类声明的所有方法.
使用Reflection:
用于reflection 的类,如Method,可以在java.lang.relfect 包中找到。使用这些类的时候必须要遵循三个步骤:第一步是获得你想操作的类的java.lang.Class 对象。在运行中的Java程序中,用java.lang.Class 类来描述类和接口等。
安全性和反射:
1、模仿instanceof 运算符号
- 在类中寻找指定的方法,同时获取该方法的参数列表,例外和返回值
- 获取类的构造函数信息,基本上与获取方法的方式相同
- 获取类中的各个数据成员对象,包括名称。类型和访问修饰符号
- 通过使用方法的名字调用方法
JAVA的反射机制与动态代理
运行时类信息(runtime type Information,RTTI)使得你在程序运行时发现和使用类型信息。
RTTI主要用来运行时获取向上转型之后的对象到底是什么类型。
Class对象:
JAVA使用Class对象来执行RTTI。每个类都有一个Class对象,它用来创建这个类的所有对象,反过来说,每个类的所有对象都会关联同一个Class对象(对于数组来说,维数、类型一致的数组的Class 对象才是相同的),每个对象的创建都依赖于Class对象的是否创建,Class对象的创建发生在类加载(java.lang.ClassLoader)的时候。java.lang.Class类实现了Serializable、GenericDeclaration、Type、AnnotatedElement四个接口,分别实现了可序列化、泛型定义、类型、元数据(注解)的功能。你可以把 Class对象理解为一个类在内存中的接口代理(它代理了这个类的类型信息、方法签名、属性),JVM加载一个类的时候首先创建Class对象,然后创建这个类的每个实例的时候都使用这个Class 对象。
如何获取 Class对象:
1.所有的引用数据类型(类-类型)的类名、基本数据类型都可以通过.class方式获取其Class对象(对于基本数据类型的封装类还可以通过.TYPE 的方式获取其Class 对象,但要注意.TYPE 实际上获取的封装类对应的基本类型的Class 对象的引用,那么你可以判断出int.class==Integer.TYPE 返回true,int.class==Integer.class 返回false!),通过这种方式不会初始化静态域,使用.class、.TYPE 的方式获取Class对象叫做类的字面常量;
2.Class 的forName(String name)传入一个类的完整类路径也可以获得Class 对象,但由于使用的是字符串,必须强制转换才可以获取泛型的Class<T>的Class对象,并且你必须获取这个方法可能抛出的ClassNotFoundException异常。
3.对于引用数据类的引用(必须初始化),可以通过Object 类继承的getClass()方法获取这个引用的Class对象,由于引用已经被初始化,所以这种方式也不会初始化静态域,因为静态域已经被初始化过。另外,前面两种方式如果说是创建Class对象,那么这种方式应该是取得Class对象,因为类的实例已经被创建,那么Class对象也一定早就被创建。
Class的常用方法:
l forName(String name):这是一个静态方法,传入的参数是一个类的完整类路径的字符串,返回这个类的Class 对象,前面说过Class 对象的创建发生在类的加载时,所以这个方法会导致静态成员被调用;
2 forName(String name,boolean initialize,ClassLoader loader):这是上面的方法的重载方法,initialize指定在创建Class对象时是否初始化这个类(即是否执行静态成员,由于在一次JVM的执行中,静态成员的初始化只类加载的时候执行一次,所以如果之前这个类已经被加载,那么即使initialize为true也不会再次执行静态成员的加载),loader指定使用哪个类加载器的实现类(Thread.currentThread().getContextClassLoader()可以获取当前线程使用的类加载器)。forName(***)方法不可以获取基本数据类型的Class对象。
如果要测试initialize是否起作用,请不要在main()方法测试自身类,因为main()是静态方法,执行这个方法会导致静态域被初始化,所以你的initialize无论是true还是false,效果都是一样的。
l asSubClass(Class superClass):这个方法是将父类的class 对象作为参数传入,并将其强制转换成当前的Class对象(子类的Class对象)。
例:Class<List> clazz = List.class;
Class<? extends List> subClazz = ArrayList.class.asSubclass(clazz);
System.out.println(subClazz.getCanonicalName());
注意红色的部分不能写成Class<ArrayList>形式,因为asSubclass()只知道是要转换成子类的Class对象,但不知道是哪个子类。
2 cast(Object o):这个方法是将传入的对象强制转换成Class 对象所代表的类型的对象;
3 getClassLoader():返回创建当前Class 对象的类加载器,默认的,应用程序获得的是sun.misc.Launcher$AppClassLoader ,Applet 程序获得的是
sun.applet.AppletClassLoader;
4 getCanonicalName():返回JAVA 语言中所定义的底层类的规范化名称,如果没有规范化名称就返回null,对于普通的引用数据类型,这个方法和getName()方法都返回完整的类路径,对于数组(以字符串数组为例),getName()返回[Ljava.lang.String];,这个方法返回java.lang.String[];
5 getSimpleName():返回底层规范化名称的简写,也就是去掉包名;
6 getConstructors():返回Class 对象的公有构造器的java.lang.reflect.Contructor 对象数组,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
7 getConstructor(Class<?> …parameterTypes):按照指定的可变参数列表,返回符合参数条件的公有构造方法的Constructor,这里不可能是数组,因为构造方法不可能重复,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
8 getDeclaredConstructors():返回Class 对象的所有构造器的Constructor 对象,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
9 getDeclaredConstructor(Class<?> …parameterTypes):按照指定的可变参数列表,返回符合参数条件的所有构造方法的Constructor,这里不可能是数组,因为构造方法不可能重复,如果找不到匹配的构造方法,返回NoSuchMethodExcetion异常;
10 getFields():获取Class对象的所有公有成员属性的java.lang.reflect.Field数组;
11 getField(String name):按照字段名称获取公有字段的Field对象,注意name是区分大小写的;
12 getDeclaredFields():获取Class对象的所有成员属性的Field数组;
13 getDeclaredField(String name):按照字段名称获取所有字段的Field 对象,注意name是区分大小写的;
14 getMethods():获取Class 对象的公有方法(以及从父类继承的方法,但不包含构造方法)的java.lang.reflect.Method数组;
L5 getMethod(String name,Class<?> …parameterTypes):按照name 指定的方法名称,parameterTypes 指定的可变数组获取公有方法(以及从父类继承的方法,但不包含构造方法)的Method对象,注意name 是区分大小写的;
L6 getDeclaredMethods():获取Class 对象的所有方法(不包含父类继承的方法,构造方法)的Method数组;
17 getDeclaredMethod(String name,Class<?> …parameterTypes):按照name指定的方法名称,parameterTypes 指定的可变数组获取所有方法(不包含父类继承的方法,构造方法)的Method对象,注意name 是区分大小写的;
18 getGenericInterface():以Type 数组的形式返回Class 对象的类直接实现的包含泛型参数的接口,返回顺序是implements后的接口顺序;
19 getInterface():以Class 数组的形式返回Class对象的类直接实现的接口,返回顺序是implements后的接口顺序;
20 getModifiers():以java.lang.reflect.Modifier 的常量值的形式返回Class对象的类的修饰的整型值的和;
21 getGenericSuperclass():以Type数组的形式返回Class对象的类直接实现的包含泛型参数的超类, 返回顺序是extends后的接口顺序;
22 getSuperclass():以Class 数组的形式返回Class对象的类直接实现的超类, 返回顺序是extends后的接口顺序;
23 getName():以String 形式返回Class 对象所表示的实体的完整类名,基本数据类型返回自身,数组类型(以String 数组为例)返回[Ljava.lang.String;,这个方法没有getCanonicalName()返回的完整,但不是所有的类型都有底层的规范化名称;
24 getPackage():以java.lang.reflect.Package形式返回Class对象的类所在的包,基本数据类型、数组抛出异常;
25 getResource(String url):这个方法使用当前Class对象的ClassLoader 实体加载url指定的资源,返回java.net.URL 对象。如果url 以\ 开头,那么就从当前的classPath开始定位资源,否则就从当前Class 对象的类所在的包开始定位资源,Hibernate 要求*.hbm.xml 必须和PO 类在一个包下,就是利用了没有\ 开头的url 传入这个方法实现的;
26 getResourceAsStream(String url):这个方法和上面的方法一样,只不过返回的是java.io.InputStream的对象;
27 isAssignableFrom(Class<?> class):判断当前Class对象的类是否是参数class的类的超类或者接口;
28 isInstance(Object o):判断参数o是否是Class 对象的类的实例,相当于o instanceOf Class。isInstance()、instanceOf关键字检查的某个实例是否是这个类型,具有继承关系的检查能力,即使是其子类的对象,也可以返回true。但是Class 对象是严格的类型,所以super.class==sub.class是一定返回false的。
29 newInstance():这个方法将会使得Class 对象调用类中的公有无参构造方法实例化对象,返回一个Object对象,大多数框架都会使用到这个方法,例如EJB容器、Spring容器都会要求受管组件提供一个默认构造方法。
注意:以上的方法并不是对每种类型都可用,对于返回数组的方法,如果不可用则返回长度为0 的数组,对于返回其他类型的方法,如果不可用则返回null。
JAVA的反射机制
(1.)Field:这个类用于获取类中的字段信息以及访问这些字段的能力。
l getObject(Object o):返回参数o的对象上的Field表示的字段的值;
2 setObject(Object o,Object value):将参数o的对象上的Field表示的字段的值设置为value;
3 getBoolean(Object o):获取参数o的对象的Field表示的布尔类型的字段的值;
4 setBoolean(Object o,boolean value):将参数o的对象上的Field表示的布尔类型的字段的值设置为value;
… …对于基本数据类型,都拥有自己的getXXX()、setXXX()方法。
5 getGenericType():返回Field表示的字段声明的泛型类型的Type实例;
6 getModifiers():以整型数值和的形式返回Field表示的字段的修饰符;
7 getType():返回Field表示的字段的类型的Class 对象;
8 isEnumConstants():如果Field表示的字段是枚举类型,返回true;
9 toGenericString():返回描述此字段的字符串(包含泛型信息),能够包含泛型信息是与toString()方法的唯一区别。
1.Method:这个类用于获取类中的方法的信息以及访问这些方法的能力
l getDefaultValue():返回此方法表示的注视成员的默认值;
getParameterAnnotations():返回一个Annotation的二维数组,表示方法的参数列表的参数的注解;
2 getExceptionTypes():返回这个方法抛出的(底层)异常的Class数组;
3 getGenericExceptionTypes():返回这个方法抛出的异常的Type数组,包含泛型信息;
4 getParameterTypes():返回这个方法的参数列表的Class对象数组;
5 getGenericParameterTypes():返回这个方法的参数列表的包含泛型信息的Type对象数组,例如一个List<String>的参数前面的方法会获得java.util.List,而这个方法会获得java.util.List<java.lang.String>;
6 getReturnType():获取方法返回值的类型的Class对象;
7 getGenericReturnType():获取方法返回值的类型的Type对象,含有泛型信息;
8 isBridge():如果此方法是bridge方法,返回true;
9 isVarArgs():如果此方法的参数列表中含有可变参数,返回true;
10 invoke(Object o,Object… args):使用类的对象和可变参数列表调用这个方法,这个方法返回Object对象,也就是类中的这个方法的返回值;
11 getTypeParameters():以java.lang.reflect.TypeVariable数组返回Method对象的泛型方法的类型参数,数组的顺序是泛型定义的类型的顺序;
12 toGenericString():返回描述此方法的字符串(包含泛型信息),能够包含泛型信息是此方法与toString()方法的唯一区别。
1. Constructor:这个类用于获取类中的构造方法的信息以及访问这些构造方法的能力。构造方法由于比较特殊,所以单独作为一个类,但是它的方法和Method没有任何区别。
2. Member接口:Class、Field、Method、Constructor都实现了Member接口,表明他们是类的成员。
1 getDeclaringClass():返回此成员所属的类的Class对象;
2 getModifiers():以整型值的和的形式返回这个成员的修饰符;
3 getName():返回此成员的名称的字符串;
4 isSynthetic():如果此成员是编译器引入的,返回true。
3. AccessibleObject 类:Class、Field、Method(注意没有Constructor,可见构造方法的访问级别绝对不可以改变!)都扩展了AccessibleObject类,这个类提供了取消JAVA访问权限的限制。
l getAnnotation(Class<T> annotationClass):返回这个成员上指定注解类型的注解;
l getAnnotations():返回这个成员上的所有注解的Annotation数组;
2 getDeclaredAnnotations():返回直接应用在此元素上的注解的Annotation数组;
3 isAccessible():如果该成员在当前位置可以被访问,返回true;
4 isAnnotationPresent(Class<? extends Annotation> annotationClass):返回指定的annotationClass是否存在于当前的成员;
5 setAccessible(boolean bool):设置此元素是否可以在当前位置访问,这个方法在外面访问类中的私有成员时,非常有用处;
6 setAccessible(AccessibleObject[] array,boolean flag):这是一个静态方法,你可以将一组成员包装成AccessibleObject数组,一并设置他们的访问性。
4. Modifier:这个类存放了所有修饰符的常量值,以及通过这些常量值判断是哪种修饰符。你可以将getModifiers()方法返回的int 类型传入这个类的isXXX()静态方法,判断是哪种修饰符。
5. Array:这个类提供了动态创建和访问数组的能力。
这个类中的方法全部是静态方法,它允许在get()、set()方法时进行扩展转型,如果进行收缩
转型,将会抛出非法的参数异常。这个类的方法都是静态方法。
l get(Object o,int index):获取指定数组中的索引位置index上的元素;
2 set(Object o,int index,Object value):设置指定数组中的索引位置index上的元素为value;getXXX()、setXXX()可以直接设置基本数据类型;
3 newInstance(Class componentType,int length) : 创建一个指定类型为componentType的长度为length的一维数组;
4 newInstance(Class componentType,int… args) :创建一个指定类型为componentType的args 指定的维数的多维数组;
动态代理
无论是那种代理方式,都存在代理对象和目标对象两个模型,所谓目标对象就是我们要生成的代理对象所代理的那个对象。
- 包装的模式进行静态代理:
备注:这种方式的静态代理,缺点就是当Animal 接口中增加了新的方法,那么包装类中也必须增加这些新的方法。
- 继承的模式进行静态代理
备注:这种方式的缺点更明显,那就是不能实现对Animal 所有子类的代理,与包装的模式相比,大大缩小了代理范围。
- 基于Proxy的动态代理
JAVA自带的动态代理是基于java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler两个类来完成的,使用JAVA反射机制。
Proxy类中的几个方法都是静态的,通常,你可以使用如下两种模式创建代理对象:
① Object proxy = Proxy.newProxyInstance(定义代理对象的类加载器,
要代理的目标对象的归属接口数组,回调接口InvocationHandler);
②Class proxyClass=Proxy.getProxyClass(定义代理对象的类加载器,
要代理的目标对象的归属接口数组);
Object proxy = proxyClass.getConstructor(
new Class[] { InvocationHandler.class }).newInstance(
回调接口InvocationHandler);
第一种方式更加直接简便,并且隐藏了代理$Proxy0 对象的结构。
- 基于CGLIB的动态代理
CGLIB是一个开源的动态代理框架,它的出现补充了JDK自带的Proxy不能对类实现动态代理的问题(ASM)。
关于Enhancer
(4-1.)关于Enhancer:
这个类的静态方法create()可以用于创建代理对象,CGLIB使用动态生成子类的方式完成动态代理,那么默认情况下,子类会继承父类的无参构造进行实例化,如果你想调用父类的其他构造器,可以使用create(Class[] argumentsType,Object[] arguments)这个重载方法分别指定构造方法的参数类型、传递参数。
但是一般情况下,我们会创建Enhancer 的实例来完成动态代理,而不是使用静态方法create(),因为使用Enhancer 的实例,你可以获取更多的功能。使用Enhancer 实例的代码如下所示:
通过Enhancer 的实例你可以设置是否使用缓存、生成策略等。
(4-2.)关于Callback:
除了MethodInterceptor 以外,CGLIB还提供了一些内置的回调处理。
l net.sf.cglib.proxy.FixedValue:为提高性能,FixedValue回调对强制某一特别方法返回固定值是有用的。
2 net.sf.cglib.proxy.NoOp:NoOp回调把对方法调用直接委派到这个方法在父类中的实现,相当于不进行代理。
3 net.sf.cglib.proxy.LazyLoader:当实际的对象需要延迟装载时,可以使用LazyLoader 回调。一旦实际对象被装载,它将被每一个调用代理对象的方法使用。
(4-3.)关于CallbackFilter:
Enhancer 的setCallbacks(Callback[] callbacks)方法可以为代理对象设置一组回调器,你可以配合CallbackFilter 为不同的方法使用不同的回调器。
- Spring 的AOP编程:
Spring 的AOP 底层通过动态代理(接口代理使用Proxy、类代理使用CGLIB)来做支持,AOP涉及到如下几个概念:
l 切面 Aspect:切面就是一个关注点的模块化,譬如:事务管理、日志记录、权限管理都是所谓的切面。
2 连接点 Joinpoint:程序执行时的某个特定的点,在Spring中就是一个方法的执行。
3 通知Advice:通知就是在切面的某个连接点上执行的操作,也就是事务管理、日志记录等的代码。
4 切入点 Pointcut:切入点是指描述某一类特定的连接点,也就是说指定某一类要织入通知的方法。
5 目标对象Target:就是被AOP动态代理的目标对象。
(5-1.)启用注解风格的AOP:
要启用注解风格的AOP,需要配置以下的内容:
<aop:aspectj-autoproxy/>
这个元素的proxy-target-class属性默认为false,也就是使用Proxy接口动态代理,当然你可以指定为true,强制使用CGLIB类动态代理。
注意版本兼容
public class User {
private String name;
private String age;
public User(String name,String age){
this.name = name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
} public interface IMyService {
User m1(User u,String id);
User m2(User u,String id);
}
package cn.jiankang51.spring.ch01;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 使用@Aspect 注解的类, Spring 将会把它当作一个特殊的Bean(一个切面),也就是不对这个类本身进行动态代理。
@Component("aspectDemo")
@Aspect
public class AspectDemo {
// 定义切入点表达式
private final static String EXP = "execution (* *.m1(..)) || execution(* *.m2(..))";
@Before(EXP)
public void before(JoinPoint point) {
System.out.println("Before " + point.getSignature().getName());
}
@After(EXP)
public void after(JoinPoint point) {
System.out.println("After " + point.getSignature().getName());
}
// 后置方法返回值通知
// @AfterReturning(EXP)
// 默认情况下,@AfterReturning与@After没什么不同,但你可以使用下面的方式,定义argNames参数,表示被注解的方法的一个参数,
// 然后returning指定返回值使用这个参数,实际上就是Spring会把方法的返回值传递给你指定的参数obj。
@AfterReturning(pointcut = EXP, returning = "obj", argNames = "obj")
public void afterReturning(JoinPoint point, Object obj) {
System.out.println("After Returning " + obj);
}
// 异常通知
@AfterThrowing(pointcut = EXP, throwing = "e", argNames = "e")
public void afterThrowing(JoinPoint point, Exception e) {
System.out.println("After Throwing " + e);
}
// 环绕方法通知,环绕方法通知要注意必须给出调用之后的返回值,否则被代理的方法会\ 停止调用并返回null,除非你真的打算这么做。
// 只有环绕通知才可以使用JoinPoint的子类ProceedingJoinPoint,这个连接点类型可以调用代理的方法,并获取、改变返回值。
@Around(EXP)
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("Around Before " +
point.getSignature().getName());
// 调用目标对象的方法并获取返回值
Object o = point.proceed(point.getArgs());
System.out.println("Around After " +
point.getSignature().getName());
return o;
}
} @Service("myService")
public class MyServiceImpl implements IMyService {
@Override
public User m1(User u, String id) {
System.out.println("m1 executed!");
if (id == null || "".equals(id))
return u;
else
return new User("1", "Hello World!");
}
@Override
public User m2(User u, String id) {
System.out.println("m2 executed!");
if (id == null || "".equals(id))
return u;
else
return new User("2", "Hello World!");
}
}
(5-2.)启用XML风格的AOP:
使用 XML风格的AOP如下所示:
<aop:config>
<aop:pointcut id="pointcut" expression="execution (* *.m1(..)) || execution(* *.m2(..))"/>
<aop:aspect ref="aspectDemo1">
<aop:before method="before" pointcut-ref="pointcut"></aop:before>
</aop:aspect>
</aop:config>// 使用@Aspect 注解的类, Spring 将会把它当作一个特殊的Bean(一个切面),也就是不对这个类本身进行动态代理。
@Component("aspectDemo1")
public class AspectDemo {
public void before(JoinPoint point) {
System.out.println("aspectDemo1:Before " + point.getSignature().getName());
}
public void after(JoinPoint point) {
System.out.println("aspectDemo1:After " + point.getSignature().getName());
}
// 后置方法返回值通知
// @AfterReturning(EXP)
// 默认情况下,@AfterReturning与@After没什么不同,但你可以使用下面的方式,定义argNames参数,表示被注解的方法的一个参数,
// 然后returning指定返回值使用这个参数,实际上就是Spring会把方法的返回值传递给你指定的参数obj。
public void afterReturning(JoinPoint point, Object obj) {
System.out.println("aspectDemo1:After Returning " + obj);
}
// 异常通知
public void afterThrowing(JoinPoint point, Exception e) {
System.out.println("aspectDemo1:After Throwing " + e);
}
// 环绕方法通知,环绕方法通知要注意必须给出调用之后的返回值,否则被代理的方法会\ 停止调用并返回null,除非你真的打算这么做。
// 只有环绕通知才可以使用JoinPoint的子类ProceedingJoinPoint,这个连接点类型可以调用代理的方法,并获取、改变返回值。
public Object around(ProceedingJoinPoint point) throws Throwable {
System.out.println("aspectDemo1:Around Before " +
point.getSignature().getName());
// 调用目标对象的方法并获取返回值
Object o = point.proceed(point.getArgs());
System.out.println("aspectDemo1:Around After " +
point.getSignature().getName());
return o;
}
}
(5-3.)切入点表达式:
这是 Spring 中使用最多的切入点表达式。?表示可以不配置,也就是说只有方法的名字name-pattern、方法的参数param-pattern是必须的。对于param-pattern之外的其余部分,可以使用*作为通配符,表示任意,例如:execution (* *.m1(..))就是执行任意返回值、任意类中的m1方法时进行拦截。
参数的统配稍微复杂一些,其中(..)表示参数为0 个或者多个,且类型任意;(*)表示任意类型的一个参数;(*,String)表示第一个参数为任意类型,第二个参数为String类型;什么都不写表示无参数。
多个表达式可以使用&&、||、!进行运算,因为表达式返回的是布尔值。