JUnit单元测试、反射、注解、动态代理
直接上传的黑马的官方笔记。
反射、注解、动态代理在许多框架都有使用。
最近看Mybatis代码时,有点忘了,传上来好经常复习。
学习目标
第一章 Junit单元测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| Junit是什么 * Junit是Java语言编写的第三方单元测试框架(工具类) * 类库 ==> 类 junit.jar
单元测试概念 * 单元:在Java中,一个类就是一个单元 * 单元测试:程序猿编写的一小段代码,用来对某个类中的某个方法进行功能测试或业务逻辑测试。
Junit单元测试框架的作用 * 用来对类中的方法功能进行有目的的测试,以保证程序的正确性和稳定性。 * 能够让方法独立运行起来。
Junit单元测试框架的使用步骤 * 编写业务类,在业务类中编写业务方法。比如增删改查的方法 * 编写测试类,在测试类中编写测试方法,在测试方法中编写测试代码来测试。 * 测试类的命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法 * 每一个单词首字母大写,称为大驼峰命名法,比如类名,接口名... * 从第二单词开始首字母大写,称为小驼峰命名法,比如方法命名 * 比如业务类类名:ProductDao,那么测试类类名就应该叫:TestProductDao * 测试方法的命名规则:以test开头,以业务方法名结尾 * 比如业务方法名为:save,那么测试方法名就应该叫:testSave
测试方法注意事项 * 必须是public修饰的,没有返回值,没有参数 * 必须使注解@Test修饰
如何运行测试方法 * 选中方法名 --> 右键 --> Run '测试方法名' 运行选中的测试方法 * 选中测试类类名 --> 右键 --> Run '测试类类名' 运行测试类中所有测试方法 * 选中模块名 --> 右键 --> Run 'All Tests' 运行模块中的所有测试类的所有测试方法
如何查看测试结果 * 绿色:表示测试通过 * 红色:表示测试失败,有问题
Junit常用注解(Junit4.xxxx版本) * @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。 * @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。 * @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次。 * @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次。
Junit常用注解(Junit5.xxxx版本) * @BeforeEach:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。 * @AfterEach:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。 * @BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前执行一次。 * @AfterAll:用来静态修饰方法,该方法会在所有测试方法之后执行一次。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
public class Cacluate {
public int sum(int a,int b){ return a + b + 10; }
public int sub(int a,int b){ return a - b; } }
public class TestCacluate {
static Cacluate c = null;
@BeforeClass public static void init(){ System.out.println("初始化操作"); c = new Cacluate(); }
@AfterClass public static void close(){ System.out.println("释放资源"); c = null; }
@Test public void testSum(){ int result = c.sum(1,1);
Assert.assertEquals("期望值和实际值不一致",12,result); System.out.println(result); }
@Test public void testSub(){
int result = c.sub(1,1); Assert.assertEquals("期望值和实际值不一致",0,result); System.out.println(result); } }
|
第二章 反射
2.1 反射的概述
2.1.1 反射的引入
- 问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?
1
| 通过反射技术对象类进行了解剖得到了类的所有成员。
|
2.1.2 反射的概念
1
| 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)
|
2.1.3 使用反射操作类成员的前提
2.1.4 反射在实际开发中的应用
1 2
| * 开发IDE(集成开发环境),比如IDEA,Eclipse * 各种框架的设计和学习 比如Spring,Hibernate,Struct,Mybaits....
|
2.2 Class对象的获取方式
2.2.1 三种获取方法
1 2 3 4
| * 方式1: 通过类名.class获得 * 方式2:通过对象名.getClass()方法获得 * 方式3:通过Class类的静态方法获得: static Class forName("类全名") * 每一个类的Class对象都只有一个。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class ReflectDemo01 { public static void main(String[] args) throws ClassNotFoundException { Class c1 = Student.class;
Student stu = new Student(); Class c2 = stu.getClass(); System.out.println(c1 == c2);
Class c3 = Class.forName("com.itheima._03反射.Student"); System.out.println(c1 == c3); System.out.println(c2 == c3); } }
|
2.2.2 Class类常用方法
1 2 3
| String getSimpleName(); 获得类名字符串:类名 String getName(); 获得类全名:包名+类名 T newInstance() ; 创建Class对象关联类的对象
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class ReflectDemo02 { public static void main(String[] args) throws Exception { Class c = Student.class; System.out.println(c.getSimpleName()); System.out.println(c.getName()); Student stu = (Student) c.newInstance(); System.out.println(stu); } }
|
2.3 反射之操作构造方法
2.3.1 Constructor类概述
1 2 3 4 5
| 反射之操作构造方法的目的 * 获得Constructor对象来创建类的对象。
Constructor类概述 * 类中的每一个构造方法都是一个Constructor类的对象
|
2.3.2 Class类中与Constructor相关的方法
1 2 3 4 5 6 7 8 9
| 1. Constructor getConstructor(Class... parameterTypes) * 根据参数类型获得对应的Constructor对象。 * 只能获得public修饰的构造方法 2. Constructor getDeclaredConstructor(Class... parameterTypes) * 根据参数类型获得对应的Constructor对象,包括private 3. Constructor[] getConstructors() 获得类中的所有构造方法对象,只能获得public的 4. Constructor[] getDeclaredConstructors() 获得类中的所有构造方法对象,包括private修饰的
|
2.3.3 Constructor对象常用方法
1 2 3 4
| 1. T newInstance(Object... initargs) 根据指定的参数创建对象 2. void setAccessible(true) 设置是否取消权限检查,true取消权限检查,false表示不取消(暴力反射)
|
2.3.4 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class ReflectDemo03 {
@Test public void test03() throws Exception { Class c = Student.class; Constructor[] cons = c.getDeclaredConstructors(); for (Constructor con:cons) { System.out.println(con); } }
@Test public void test02() throws Exception { Class c = Student.class; Constructor con = c.getDeclaredConstructor(String.class,String.class); con.setAccessible(true); Object obj = con.newInstance("rose","女"); System.out.println(obj); }
@Test public void test01() throws Exception { Class c = Student.class; Constructor con = c.getConstructor(); Object obj = con.newInstance(); System.out.println(obj);
Constructor con2 = c.getConstructor(String.class, String.class,int.class); Object obj2 = con2.newInstance("jack", "男",18); System.out.println(obj2); } }
|
2.4 反射之操作成员方法
2.4.1 Method类概述
1 2 3 4
| 反射之操作成员方法的目的 * 操作Method对象来调用成员方法 Method类概述 * 每一个成员方法都是一个Method类的对象。
|
2.4.2 Class类中与Method相关的方法
1 2 3 4 5 6 7 8 9 10 11
| * Method getMethod(String name,Class...args); * 根据方法名和参数类型获得对应的构造方法对象,只能获得public的
* Method getDeclaredMethod(String name,Class...args); * 根据方法名和参数类型获得对应的构造方法对象,包括private的
* Method[] getMethods(); * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
* Method[] getDeclaredMethods(); * 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
|
2.4.3 Method对象常用方法
1 2 3 4 5
| * Object invoke(Object obj, Object... args) * 调用指定对象obj的该方法 * args:调用方法时传递的参数 * void setAccessible(true) 设置是否取消权限检查,true取消权限检查,false表示不取消(暴力反射)
|
2.4.4 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| public class ReflectDemo04 {
@Test public void test04() throws Exception { Class c = Student.class; Method method = c.getDeclaredMethod("eat",String.class); method.invoke(null,"蛋炒饭"); }
@Test public void test03() throws Exception { Class c = Student.class; Method[] methods = c.getDeclaredMethods(); for (Method m: methods) { System.out.println(m); }
}
@Test public void test02() throws Exception { Class c = Student.class;
Student stu = (Student) c.newInstance(); Method m = c.getDeclaredMethod("sleep"); m.setAccessible(true);
m.invoke(stu); }
@Test public void test01() throws Exception { Class c = Student.class; Student stu = (Student) c.newInstance(); Method m = c.getMethod("study"); m.invoke(stu);
Method m2 = c.getMethod("study", int.class); m2.invoke(stu,8); } }
|
2.5 反射之操作成员变量
2.5.1 Field类概述
1 2 3 4 5
| 反射之操作成员变量的目的 * 通过Field对象给对应的成员变量赋值和取值
Field类概述 * 每一个成员变量都是一个Field类的对象。
|
2.5.2 Class类中与Field相关的方法
1 2 3 4 5 6 7 8
| * Field getField(String name); * 根据成员变量名获得对应Field对象,只能获得public修饰 * Field getDeclaredField(String name); * 根据成员变量名获得对应Field对象,包含private修饰的 * Field[] getFields(); * 获得所有的成员变量对应的Field对象,只能获得public的 * Field[] getDeclaredFields(); * 获得所有的成员变量对应的Field对象,包含private的
|
2.5.3 Field对象常用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void set(Object obj, Object value) void setInt(Object obj, int i) void setLong(Object obj, long l) void setBoolean(Object obj, boolean z) void setDouble(Object obj, double d)
Object get(Object obj) int getInt(Object obj) long getLong(Object obj) boolean getBoolean(Object ob) double getDouble(Object obj)
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。 Class getType(); 获取属性的类型,返回Class对象。
|
setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。
getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。
2.5.4 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| public class ReflectDemo05 {
@Test public void test02() throws Exception { Class c = Student.class; Field[] fields = c.getDeclaredFields(); for (Field f: fields) { System.out.println(f); } }
@Test public void test01() throws Exception { Class c = Student.class; Object obj = c.newInstance(); Field f = c.getField("name"); f.set(obj,"jack");
System.out.println(f.get(obj)); System.out.println(f.getName());
Field f1 = c.getDeclaredField("gender"); f1.setAccessible(true); f1.set(obj,"男");
System.out.println(obj);
} }
|
第三章 注解
3.1 注解的概述
3.1.1 注解的概念
注解是JDK1.5的新特性。
注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种 标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。
3.1.2 注解的作用
注解的作用就是给程序带入参数。
以下几个常用操作中都使用到了注解:
生成帮助文档:**@author和@version**
编译检查:**@Override**
@Override:用来修饰方法声明。
- 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图

框架的配置(框架=代码+配置)
3.1.3 常见注解
- @author:用来标识作者名,eclipse开发工具默认的是系统用户名。
- @version:用于标识对象的版本号,适用范围:文件、类、方法。
- @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
3.2 自定义注解
3.2.1 定义格式
1 2 3 4 5 6 7
| public @interface 注解名{
} 如:定义一个名为Student的注解 public @interface Student {
}
|
3.2.2 注解的属性
属性的格式
- 格式1:数据类型 属性名();
- 格式2:数据类型 属性名() default 默认值;
属性定义示例
1 2 3 4 5 6
| String name();
int age() default 18;
String[] hobby();
|
属性适用的数据类型
1 2 3
| * 八种数据数据类型(int,short,long,double,byte,char,boolean,float) * String,Class,注解类型,枚举类 * 以上类型的数组形式
|
3.3 使用自定义注解
3.3.1 定义和注解
- 定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 代码实现
1 2 3 4 5 6 7
| @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { String value(); double price() default 100; String[] authros(); }
|
3.3.2 使用注解
- 定义类在成员方法上使用Book注解
- 使用注意事项
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
3.3.3 特殊属性value
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
@interface TestA{ String[] value(); int age() default 100; String name(); }
@interface TestB{ String name(); }
@TestB(name = "zzz") @TestA(name = "yyy",value = {"xxx","xxx"}) public class AnnotationDemo02 {
}
|
3.4 注解之元注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 元注解概述 * Java官方提供的注解 * 用来定义注解的注解 * 任何官方提供的非元注解的定义都使用到了元注解。
常用的元注解 * @Target * 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。 * 可使用的值定义在ElementType枚举类中,常用值如下 TYPE,类,接口 FIELD, 成员变量 METHOD, 成员方法 PARAMETER, 方法参数 CONSTRUCTOR, 构造方法 LOCAL_VARIABLE, 局部变量
* @Retention * 作用:用来标识注解的生命周期(有效范围) * 可使用的值定义在RetentionPolicy枚举类中,常用值如下 * SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在 * CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值 * RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段
|
3.5 注解解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 什么是注解解析 * 使用Java技术获得注解上数据的过程则称为注解解析。 与注解解析相关的接口 * Annotation: 注解类,该类是所有注解的父类。 * AnnotatedElement:该接口定义了与注解解析相关的方法 T getAnnotation(Class<T> annotationClass) 根据注解类型获得对应注解对象 Annotation[] getAnnotations() * 获得当前对象上使用的所有注解,返回注解数组,包含父类继承的 Annotation[] getDeclaredAnnotations() * 获得当前对象上使用的所有注解,返回注解数组,只包含本类的 boolean isAnnotationPresent(Class<Annotation> annotationClass) * 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
获取注解数据的原理 * 注解作用在哪个成员上就会得该成员对应的对象来获得注解 * 比如注解作用成员方法,则要获得该成员方法对应的Method对象 * 比如注解作用在类上,则要该类的Class对象 * 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。 * Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口
|
3.5.4.1 需求说明
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义TestAnnotation测试类获取Book注解上的数据
3.5.4.2 代码实现
- 注解Book
1 2 3 4 5 6 7
| @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { String value(); double price() default 100; String[] authros(); }
|
- BookShelf类
1 2 3 4 5 6 7 8
| @Book(value = "红楼梦",authros = {"曹雪芹"}) public class BookShelf {
@Book(value = "西游记",authros = {"吴承恩","白求恩"},price = 200) public void showBook(){
} }
|
- TestAnnotation类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
public class AnnotationDemo04 {
@Test public void test02() throws Exception { Class c = BookShelf.class; if(c.isAnnotationPresent(Book.class)){ Book annotation = (Book) c.getAnnotation(Book.class); System.out.println(annotation.value()); System.out.println(Arrays.toString(annotation.authros())); System.out.println(annotation.price()); } Annotation[] annotations = c.getDeclaredAnnotations(); System.out.println(Arrays.toString(annotations)); }
@Test public void test01() throws Exception { Class c = BookShelf.class; Method m = c.getMethod("showBook"); Book annotation = m.getAnnotation(Book.class); System.out.println(annotation.value()); System.out.println(Arrays.toString(annotation.authros())); System.out.println(annotation.price()); } }
|
3.6 注解案例
3.6.1 案例说明
3.6.2 案例分析
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。
3.6.3 案例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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.METHOD) public @interface MyTest {
}
public class TestMyTest {
@MyTest public void tests01(){ System.out.println("test01"); }
public void tests02(){ System.out.println("test02"); }
@MyTest public void tests03(){ System.out.println("test03"); } }
public class AnnotationDemo05 { public static void main(String[] args) throws Exception { Class c = TestMyTest.class; Object obj = c.newInstance(); Method[] methods = c.getMethods(); for(Method m:methods){ if(m.isAnnotationPresent(MyTest.class)){ m.invoke(obj); } } } }
|
第四章 动态代理
4.1 代理模式【Proxy Pattern】
为什么要有“代理”?生活中就有很多例子,例如委托业务等等,代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,这才是“代理”存在的原因。例如,我现在需要出国,但是我不愿意自己去办签证、预定机票和酒店(觉得麻烦 ,那么就可以找旅行社去帮我办,这时候旅行社就是代理,而我自己就是被代理了。
java中代理可以分为:
详细的区别主要是见这篇文章,这里只着重介绍动态代理
4.2 动态代理概述
动态代理简单来说是:拦截对真实对象方法的直接访问,增强真实对象方法的功能
动态代理详细来说是:代理类在程序运行时创建的代理对象被称为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。也就是说你想获取哪个对象的代理,动态代理就会动态的为你生成这个对象的代理对象。动态代理可以对被代理对象的方法进行增强,可以在不修改方法源码的情况下,增强被代理对象方法的功能,在方法执行前后做任何你想做的事情。动态代理技术都是在框架中使用居多,例如:Struts1、Struts2、Spring和Hibernate等后期学的一些主流框架技术中都使用了动态代理技术。
4.3 案例引出
现在,假设我们要实现这样的需求:在企业的大型系统中,每个业务层方法的执行,都需要有对应的日志记录,比如这个方法什么时候调用完成的,耗时多久等信息,根据这些日志信息,我们可以看到系统执行的情况,尤其是在系统出现错误的时候,这些日志信息就显得尤为重要了。现在有一种实现思路是这样的,业务层实现类代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public interface SchoolService{ String login(String loginName, String passWord); String getAllClazzs(); }
public class SchoolServiceImpl implements SchoolService {
@Override public String login(String loginName, String passWord) { long startTimer = System.currentTimeMillis(); try { Thread.sleep(500); if("admin".equals(loginName) && "123456".equals(passWord)){ return "success"; } } catch (Exception e) { throw new RuntimeException("登录异常"); } long endTimer = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("login方法执行->"+sdf.format(endTimer)+",耗时:"+(endTimer - startTimer)); return "登录名称或者密码不正确"; }
@Override public String getAllClazzs() { long startTimer = System.currentTimeMillis(); try { Thread.sleep(1000); return "返回了所有的班级(1班,2班,3班)"; } catch (Exception e) { throw new RuntimeException("查询班级异常"); }finally{ long endTimer = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("getAllClazzs方法执行->"+sdf.format(endTimer)+",耗时:"+(endTimer - startTimer)); } } }
|
.
我们在业务层的方法每次执行的时候都去记录了开始时间和结束时间,这样虽然可以记录每个方法运行的截止时间和耗时情况,但是代码显得很臃肿,这些日记记录代码本身也是与业务功能无关的代码。
4.4 使用动态代理优化代码
那么有没有一种方式可以解决这个问题呢?
动态代理就是解决此类问题非常好的实现手段,通过动态代理我们可以为该业务层实现类对象提供一个动态的代理对象。该代理对象,可以为实现类的所有方法进行代理,并对代理的方法功能进行增强。也就是说只要调用了被代理实现类对象的方法,该方法的执行会先进入到代理对象中去,代理对象可以在该方法执行前记录开始时间,然后去触发该方法的执行,在方法执行完成以后再由代理对象去记录结束时间然后计算时间差作为日志记录,因为方法的日记记录由代理完成了,所以被代理对象的方法就无需自己单独记录日志操作了。这样就产生了一种非常好的设计模型。
现在我们来使用动态代理写一个日志记录的代理类:
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class LogProxy { public static Object getProxy(Object obj) { return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTimer = System.currentTimeMillis(); try { return method.invoke(obj, args); } catch (Exception e) { throw new RuntimeException(e); } finally { long endTimer = System.currentTimeMillis(); SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); System.out.println(method.getName() + "方法执行->" + sdf.format(endTimer) + ",耗时:" + (endTimer - startTimer)); } } }); }
}
|
4.5 重点类和方法
在上述代码中 getProxy 方法即是用于获取某个实现类对象的一个代理对象。在该代码中,如果要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:
java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一个静态方法来为一组接口的实现类动态地生成代理类及其对象。
newProxyInstance方法的三个参数的详解:
该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
1 2 3 4 5 6 7
| public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
1、obj.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader() 方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要!
2、obj.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。
3、InvocationHandler 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类方法的处理以及访问.
|
invoke方法的参数
1 2 3 4
| public Object invoke(Object proxy, Method method, Object[] args) 1、Object proxy生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。 2、Method method:被代理的对象中被代理的方法的一个抽象。 3、Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。
|
代理类定义完成以后业务层实现类的方法就无需再自己申明日记记录的代码了,因为代理对象会帮助做日志记录,修改后实现类代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public class SchoolServiceImpl implements SchoolService {
@Override public String login(String loginName, String passWord) { try { Thread.sleep(500); if("admin".equals(loginName) && "123456".equals(passWord)){ return "success"; } } catch (Exception e) { throw new RuntimeException("登录异常"); } return "登录名称或者密码不正确"; }
@Override public String getAllClazzs() { try { Thread.sleep(1000); return "返回了所有的班级(1班,2班,3班)"; } catch (Exception e) { throw new RuntimeException("查询班级异常"); } } }
|
开始使用动态代理去访问方法:
1 2 3 4 5 6 7 8 9 10 11
| public class TestMain { public static void main(String[] args) { SchoolService schoolService = (SchoolService) LogProxy.getProxy(new SchoolServiceImpl()); System.out.println(schoolService.login("admin", "1234256")); System.out.println(schoolService.getAllClazzs()); } }
|
此代码中业务层对象已经是被代理的了,那么以后调用业务层对象的方法时,方法的调用会先被代理对象处理,代理会先记录方法执行的开始时间,然后通过method.invoke(obj, args)去真正触发该方法的执行,接下来代理对象进行方法结束时间的记录和日志的输出即可。这样整个过程就通过代理完美的实现了。
4.6 总结
动态代理非常的灵活,可以为任意的接口实现类对象做代理
动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
动态代理同时也提高了开发效率。
缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。