JavaSE-08-反射注解代理(框架灵魂)

注解、反射是JavaEE框架的灵魂。掌握注解、反射可以更好地理解框架,甚至自己定制化自己的框架。而代理,特别是动态代理,更是ORM框架、AOP横切思想的实现本质,增强目标类的能力,动态生成接口的实现类,称得上“虽然看不见接口的实现类,但能在运行中感受得到它的存在”。
脑图

一、反射

问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?
答案:通过反射技术对象类进行了解剖得到了类的所有成员。

什么是反射?

反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)。使用反射的前提是要获得该类字节码文件对象,就是Class对象。反射一般使用在框架,比较底层的工具类库上,作为辅助,配合文件配置,可以轻松实现框架的解耦、扩展特性。

  • 开发IDE(集成开发环境),比如IDEA,Eclipse
  • 各种框架的设计和学习 比如Spring,Mybaits....

1.1 Class对象

每一个类的Class对象在同一个JVM中都只有一个!由JVM虚拟机生成。

java文件:就是我们自己编写的java代码。

字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)

字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象。这个对象里面至少包含了:构造方法,成员变量,成员方法。而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。

三种获取方法

  • 方式1: 通过类名.class获得
  • 方式2:通过对象名.getClass()方法获得
  • 方式3:通过Class类的静态方法获得: static Class forName("类全名")

示例:

// 获取类对象,是获取类的各个成分对象的前提 public static void test1_class () throws ClassNotFoundException { Class<Cat> c1 = Cat.class; Class<?> c2 = Class.forName( "com.water.javase.reflect.Cat" ); System.out.println(c1 == c2); // true Cat cat = new Cat ( "Jack" , 5 ); Class<? extends Cat > c3 = cat.getClass(); System.out.println(c3 == c2); // true } // 输出:true,true 结论:三种方式都能获取同一个类的Class对象,证明在JVm里面是唯一的

Class类常用方法

  • String getSimpleName(); 获得类名字符串:类名
  • String getName(); 获得类全名:包名+类名
  • String getPackageName() ; 获得包名

示例:

public static void main (String[] args) throws Exception { Class<Cat> catClass = Cat.class; System.out.println(catClass.getSimpleName()); // 类名 System.out.println(catClass.getName()); // 包名+类名 System.out.println(catClass.getPackageName()); // 包名 }

输出:

Cat com.water.base.reflect.Cat com.water.base.reflect

这三个是获取包名、类名的信息。class对象当然不止这几个信息获取方法,更多的是关于构造器对象Constructor、方法对象Method、成员变量对象Flied,还有获取类接口数组、注解等信息的能力,可以说:

class对象在手,整个类对你而言都是没有秘密的,你可以获取关于类的一切信息,非常强大,所以,反射的入口,获取说前提,就是这么一个class对象。

下面就是一个Class类的全景图,可以构建个全局视野。

Class类的UML图
反射涉及的核心类:

Java 中与反射相关的核心类主要有以下四个:

类名 功能说明
Class 表示类的元数据,是反射入口
Method 表示类的方法,可调用方法
Field 表示类的字段,可访问和修改字段
Constructor 表示类的构造函数,可用于创建对象

1.2 完整示例体验

import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class ReflectDemo { public static void main (String[] args) throws Exception { // 1. 获取 Class 对象 Class<?> clazz = Class.forName( "com.example.Person" ); // 2. 创建对象 Object person = clazz.getDeclaredConstructor().newInstance(); // 3. 获取并调用 setName 方法 Method setName = clazz.getMethod( "setName" , String.class); setName.invoke(person, "Alice" ); // 4. 获取并调用 getName 方法 Method getName = clazz.getMethod( "getName" ); System.out.println(getName.invoke(person)); // 输出 Alice // 5. 获取并修改私有字段 secretName Field secretNameField = clazz.getDeclaredField( "secretName" ); secretNameField.setAccessible( true ); secretNameField.set(person, "PrivateValue" ); System.out.println(secretNameField.get(person)); // 输出 PrivateValue } }

1.3 构造方法Constructor

目的:获得Constructor对象来创建类的对象。

类中的每一个构造方法都是一个Constructor类的对象,万物皆对象,这是将类里面的所有成分都使用类维护起来,具体的时候,就是一个个对象。而构造器对象,按职责推理:创建对象。

Class对象获取Constructor对象

规则:

  • get表示获取
  • Declared可以获取所有声明的构造方法,包括私有
  • 最后的s表示所有,复数形式
  • 如果当前获取到的是私有的,必须要临时修改访问权限,setAccessiable(true),否则无法使用
方法名 说明
Constructor<?>[] getConstructors() 获得所有的构造(只能public修饰)
Constructor<?>[] getDeclaredConstructors() 获得所有的构造(包含private修饰)
Constructor getConstructor(Class<?>... parameterTypes) 获取指定构造(只能public修饰)
Constructor getDeclaredConstructor(Class<?>... parameterTypes) 获取指定构造(包含private修饰)

Constructor对象常用方法

  • setAccessible(boolean),要访问私有构造方法时,需要设置为true,表示可访问(暴力反射)
  • T newInstance(Object ... initargs),表示要创建一个T对象,而入参就是构造方法的可变参数

Constructor类UML图:

代码示例

实体Cat类

package com.water.base.reflect; public class Cat { private String name; private Integer age; public String address; Boolean isBoy; public Cat (String name, Integer age) { this .name = name; this .age = age; } public Cat () { } private Cat (String name) { this .name = name; } Cat(Integer age) { this .age = age; } private void run () { System.out.println( this .name + "跑得真快!" ); } public String getName () { return name; } public void setName (String name) { this .name = name; } public Integer getAge () { return age; } public void setAge (Integer age) { this .age = age; } @Override public String toString () { return "Cat{" + "name='" + name + '\'' + ", age=" + age + '}' ; } }

测试类:

// 构建实例,并初始化 public static void test2_constructor () throws Exception { Class<Cat> catClass = Cat.class; // 获取class对象 // get all public method ,获取所有公开方法get Constructor<?>[] allPublicConstructors = catClass.getConstructors(); for (Constructor<?> constructor : allPublicConstructors) { System.out.println( "public: " +constructor); } // get all method, no matter with private ,获取所有声明方法,包括私有的 Constructor<?>[] allConstructors = catClass.getDeclaredConstructors(); for (Constructor<?> constructor : allConstructors) { System.out.println( "all: " +constructor); } // get the constructor with params or not as you want,获取私有构造方法 Constructor<Cat> constructor = catClass.getDeclaredConstructor(String.class, Integer.class); System.out.println( "special: " +constructor.getName()); Cat cat = constructor.newInstance( "water" , 18 ); System.out.println( "new Cat by reflect: " +cat); // get private constructor,设置访问权限true,表示可以访问私有的资源 Constructor<Cat> privateConstructor = catClass.getDeclaredConstructor(String.class); privateConstructor.setAccessible( true ); // set accessible = true, which means without check Cat JerryCat = privateConstructor.newInstance( "Jerry" ); System.out.println(JerryCat); }

输出:

public : public com.water.base.reflect.Cat(java.lang.String,java.lang.Integer) public : public com.water.base.reflect.Cat() all: public com.water.base.reflect.Cat(java.lang.String,java.lang.Integer) all: public com.water.base.reflect.Cat() all: com.water.base.reflect.Cat(java.lang.Integer) all: private com.water.base.reflect.Cat(java.lang.String) special: com.water.base.reflect.Cat new Cat by reflect: Cat{name= 'water' , age= 18 } Cat{name= 'Jerry' , age= null }

1.4 成员方法Method

目的:操作Method对象来调用成员方法。
每一个成员方法都是一个Method类的对象。

Class对象获取Method对象

* Method getMethod (String name,Class...args) ; * 根据方法名和参数类型获得对应的构造方法对象,只能获得 public 的 * Method getDeclaredMethod (String name,Class...args) ; * 根据方法名和参数类型获得对应的构造方法对象,包括 private 的 * Method[] getMethods(); * 获得类中的所有成员方法对象,返回数组,只能获得 public 修饰的且包含父类的 * Method[] getDeclaredMethods(); * 获得类中的所有成员方法对象,返回数组,只获得本类的,包含 private 修饰的

Method对象常用方法

*  Object invoke (Object obj, Object... args) * 调用指定对象obj的该方法 * args:调用方法时传递的参数 * void setAccessible ( true ) 设置是否取消权限检查, true 取消权限检查, false 表示不取消(暴力反射)

Method类的UML图:

示例代码

// 调用方法 public static void test4_method () throws Exception { Class<Cat> catClass = Cat.class; Method[] publicMethods = catClass.getMethods(); // 1、获取所有public方法,包括父类 for (Method allMethod : publicMethods) { System.out.println( "public: " +allMethod); } Method[] allMethods = catClass.getDeclaredMethods(); // 2、获取本类所有申明方法,包括私有 for (Method allMethod : allMethods) { System.out.println( "all: " +allMethod); } // 3、获取一个私有方法对象,并且设置可访问权限,执行调用。 Method run = catClass.getDeclaredMethod( "run" ); // 获取私有方法对象 run.setAccessible( true ); // 获取访问权限 Cat cat1 = catClass.getConstructor().newInstance(); // 利用构造器对象创建对象 Cat cat2 = new Cat ( "Rose" , 7 ); // 直接new 一个对象 run.invoke(cat1); // 执行run方法,调用cat1对象 run.invoke(cat2); // 执行run方法,调用cat2对象 }

输出:

public : public java.lang.String com.water.base.reflect.Cat.getName() public : public java.lang.String com.water.base.reflect.Cat.toString() public : public void com.water.base.reflect.Cat.setName(java.lang.String) public : public void com.water.base.reflect.Cat.setAge(java.lang.Integer) public : public java.lang.Integer com.water.base.reflect.Cat.getAge() public : public boolean java.lang.Object.equals(java.lang.Object) public : public native int java.lang.Object.hashCode() public : public final native java.lang.Class java.lang.Object.getClass() public : public final native void java.lang.Object.notify() public : public final native void java.lang.Object.notifyAll() public : public final void java.lang.Object.wait( long ) throws java.lang.InterruptedException public : public final void java.lang.Object.wait( long , int ) throws java.lang.InterruptedException public : public final void java.lang.Object.wait() throws java.lang.InterruptedException all: public java.lang.String com.water.base.reflect.Cat.getName() all: private void com.water.base.reflect.Cat.run() all: public java.lang.String com.water.base.reflect.Cat.toString() all: public void com.water.base.reflect.Cat.setName(java.lang.String) all: public void com.water.base.reflect.Cat.setAge(java.lang.Integer) all: public java.lang.Integer com.water.base.reflect.Cat.getAge() null 跑得真快! Rose跑得真快!

1.5 成员变量Field

目的:通过Field对象给对应的成员变量赋值和取值。

每一个成员变量都是一个Field类的对象。

Class对象获取Field对象

* Field getField (String name) ; *  根据成员变量名获得对应Field对象,只能获得 public 修饰 * Field getDeclaredField (String name) ; *  根据成员变量名获得对应Field对象,包含 private 修饰的 * Field[] getFields(); * 获得所有的成员变量对应的Field对象,只能获得 public 的 * Field[] getDeclaredFields(); * 获得所有的成员变量对应的Field对象,包含 private 的

Field对象常用方法

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对应的属性值的,针对不同的类型选取不同的方法。

Field类UML图:

示例代码

// 给成员变量设置值 public static void test3_filed () throws Exception { Class<Cat> catClass = Cat.class; Field[] publicFields = catClass.getFields(); // 获取所有public方法,包括父类的 for (Field publicField : publicFields) { System.out.println( "public: " +publicField); } Field[] allFields = catClass.getDeclaredFields(); // 获取所有本类声明的方法,包括私有的 for (Field allField : allFields) { System.out.println( "all: " +allField); } // 获取声明的成员变量,设置访问权限,创建对象,赋值,打印输出 Field nameField = catClass.getDeclaredField( "name" ); nameField.setAccessible( true ); Cat cat = new Cat (); nameField.set(cat, "John" ); // 执行给cat对象的name属性赋值 John System.out.println(cat); }

输出:

public : public java.lang.String com.water.base.reflect.Cat.address all: private java.lang.String com.water.base.reflect.Cat.name all: private java.lang.Integer com.water.base.reflect.Cat.age all: public java.lang.String com.water.base.reflect.Cat.address all: java.lang.Boolean com.water.base.reflect.Cat.isBoy Cat{name= 'John' , age= null }

1.6 小结

    1. 构造器Constructor对象,主要作用是创建对象,等同new对象,初始化
    1. 方法Method对象,主要用于动态代理,可以执行目标方法,aop横切逻辑实现
    1. 成员变量Field对象,主要用于属性值注入设置,spring等框架中的依赖注入

学习了反射,下面我们可以通过反射,获取注解信息,给程序加入元数据的配置,更灵活实现框架功能。反射是获取注解的重要方式,注解是配置标记的重要手段,两者结合就是框架的灵魂。

二、注解

  • 注解是JDK1.5的新特性。
  • 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
  • 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
  • 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。

注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。

Java提供了几个内置注解:

  • @Override:表示方法覆盖父类方法
  • @Deprecated:表示方法或类已过时
  • @SuppressWarnings:抑制编译器警告
  • @SafeVarargs:表示方法对可变参数的使用是安全的
  • @FunctionalInterface:表示接口是函数式接口

2.1 注解的作用

注解的作用就是给程序带入参数。

以下几个常用操作中都使用到了注解:

    1. 生成帮助文档
  • @author:用来标识作者姓名。

  • @version:用于标识对象的版本号,适用范围:文件、类、方法。
    使用@author和@version注解就是告诉Javadoc工具(就是执行时会解析这些注解,拿出里面的注解信息)在生成帮助文档时把作者姓名和版本号也标记在文档中。如下图:
    1. 编译检查
  • @Override:用来修饰方法声明。重写覆盖

用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图
- 3. 框架的配置(框架=代码+配置)

如:Spring框架的数据源配置

@ConfigurationProperties( prefix = "spring.datasource" ) public class DataSourceProperties implements BeanClassLoaderAware , InitializingBean { private ClassLoader classLoader; private boolean generateUniqueName = true ; private String name; private Class<? extends DataSource > type; private String driverClassName; private String url; private String username; private String password; private String jndiName; private EmbeddedDatabaseConnection embeddedDatabaseConnection; private Xa xa = new Xa (); private String uniqueName; public DataSourceProperties () { }

上述有个注解@ConfigurationProperties配置属性,prefix值为spring.datasource对应的yml文件配置信息:

spring: datasource: url: jdbc:mysql: //127.0.0.1:3307/mall?useUnicode=true&characterEncoding=UTF-8&useSSL=false username: root password: 12356 driver-class-name: com.mysql.cj.jdbc.Driver # 初始化大小,最小,最大 initialSize: 5 minIdle: 5 maxActive: 200

配置文件上的值就会被注入到代码里面去。这样做的好处,值就是动态注入的了,解耦,利于扩展和动态调整。

2.2 自定义注解

定义格式

使用@interface关键字定义注解

public @interface 注解名{ } 如:定义一个名为Student的注解 public @interface Student { }
public @interface MyAnnotation { String value () default "default value" ; int count () default 0 ; }

注解的属性

    1. 属性的格式
  • 格式1:数据类型 属性名();
  • 格式2:数据类型 属性名() default 默认值;

    1. 属性定义示例
// 名称 String name () ; // 次数 int num () default 3 ; // 权限 String[] permits ();
    1. 属性适用的数据类型
* 八种数据数据类型( int , short , long , double , byte , char , boolean , float ) * String,Class,注解类型,枚举类 * 以上类型的数组形式

2.3 使用自定义注解

定义注解

package com.water.base.ann; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Mark { String name () ; String version () default "1.0.0" ; String desc () default "" ; }

使用注意事项

  • 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
  • 如果属性没有默认值,那么在使用注解时一定要给属性赋值。

使用注解

@Mark(name = "人员",version = "1.2.1",desc = "当前版本只需要满足基本需求即可") class Person { private String name; private Integer age; public Person (String name, Integer age) { this .name = name; this .age = age; } @Mark(name = "展示方法,输出当前对象姓名和年龄") public void show () { System.out.println( this .name + "今年:" + this .age+ "岁" ); } }

获取注解信息

public static void main (String[] args) throws Exception { // 1、反射创建对象 Class<Person> personClass = Person.class; // 获取person类的Class对象 Constructor<Person> constructor = personClass.getConstructor(String.class, Integer.class); // 获取Person类的构造方法对象 Person water = constructor.newInstance( "water" , 18 ); // 创建对象,初始化 water.show(); // 调用成员方法 // 2、获取类上面的注解信息 Mark mark = personClass.getAnnotation(Mark.class); System.out.println( "类上面的Mark注解信息:" ); System.out.println(mark.name()); System.out.println(mark.version()); System.out.println(mark.desc()); System.out.println( "===================" ); // 3、获取方法上面的注解Mark信息 Method method = personClass.getMethod( "show" ); Mark markMethod = method.getAnnotation(Mark.class); System.out.println( "方法上面的Mark注解信息:" ); System.out.println(markMethod.name()); System.out.println(markMethod.version()); System.out.println(markMethod.desc()); }

输出:

water今年: 18 岁 类上面的Mark注解信息: 人员 1.2 .1 当前版本只需要满足基本需求即可 =================== 方法上面的Mark注解信息: 展示方法,输出当前对象姓名和年龄 1.0 .0

特殊属性value

如果注解中只有一个属性且名字叫value,则在使用该注解时可以直接给该属性赋值,而不需要给出属性名。

如果注解中除了value属性之外还有其他属性且只要有一个属性没有默认值,则在给属性赋值时value属性名也不能省略了。

小结:如果注解中只有一个属性时,一般都会将该属性名命名为value。

package com.water.base.ann; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Permission { String value () ; }

使用:

class OrderService { @Permission("order-add") public Long add (Object request) { // ... return 1L ; } @Permission("order-update") public boolean update (Object request) { // ... return true ; } public List<MyOrder> listOrders () { //... return List.of( new MyOrder ( 1L , "milk" , 12.50 ), new MyOrder ( 2L , "milk2" , 12.30 ), new MyOrder ( 3L , "milk3" , 11.50 ), new MyOrder ( 4L , "milk4" , 1.50 ), new MyOrder ( 5L , "milk5" , 12.90 ) ); } } class MyOrder { private Long orderId; private String goodsName; private Double price; public MyOrder (Long orderId, String goodsName, Double price) { this .orderId = orderId; this .goodsName = goodsName; this .price = price; } }

多个属性:

@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 { }

2.4 注解之元注解

元注解概述

  • Java官方提供的注解
  • 用来定义注解的注解
  • 任何官方提供的非元注解的定义都使用到了元注解。

用于修饰其他注解的注解:

  • @Target:指定注解可以应用的位置(类、方法、字段等)
  • @Retention:指定注解的保留策略(SOURCE、CLASS、RUNTIME)
  • @Documented:表示注解应包含在Javadoc中
  • @Inherited:表示注解可以被子类继承
  • @Repeatable(Java 8+):表示注解可以重复使用

常用的元注解
1、@Target作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。可使用的值定义在ElementType枚举类中,常用值如下

  • TYPE,类,接口
  • FIELD, 成员变量
  • METHOD, 成员方法
  • PARAMETER, 方法参数
  • CONSTRUCTOR, 构造方法
  • LOCAL_VARIABLE, 局部变量

2、@Retention 作用:用来标识注解的生命周期(有效范围),可使用的值定义在RetentionPolicy枚举类中,常用值如下:

  • SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
  • CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
  • RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段

2.5 注解解析

什么是注解解析?使用Java技术获得注解上数据的过程则称为注解解析。与注解解析相关的接口

  • Annotation: 注解类,该类是所有注解的父类。
  • AnnotatedElement:该接口定义了与注解解析相关的方法
  • T getAnnotation(Class annotationClass) 根据注解类型获得对应注解对象
  • Annotation[] getAnnotations() 获得当前对象上使用的所有注解,返回注解数组,包含父类继承的
  • Annotation[] getDeclaredAnnotations() 获得当前对象上使用的所有注解,返回注解数组,只包含本类的
  • boolean isAnnotationPresent(Class annotationClass) 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false

获取注解数据的原理:注解作用在哪个成员上就会得该成员对应的对象来获得注解。因为都这些类的组成部分都实现了AnnotatedElement接口。

  • 比如注解作用成员方法,则要获得该成员方法对应的Method对象
  • 比如注解作用在类上,则要该类的Class对象
  • 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。

Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口

实战说明

    1. 定义注解Book,要求如下:
  • 包含属性:String value() 书名
  • 包含属性:double price() 价格,默认值为 100
  • 包含属性:String[] authors() 多位作者
  • 限制注解使用的位置:类和成员方法上
  • 指定注解的有效范围:RUNTIME

    1. 定义BookStore类,在类和成员方法上使用Book注解
    1. 定义TestAnnotation测试类获取Book注解上的数据

代码实现

注解Book

@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { String value () ; double price () default 100 ; String[] authros(); }

BookShelf类

@Book(value = "红楼梦",authros = {"曹雪芹"}) public class BookShelf { @Book(value = "西游记",authros = {"吴承恩","白求恩"},price = 200) public void showBook () { } }

TestAnnotation类

/** 什么是注解解析 * 使用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接口 */ public class Test { /* 获得类上使用的注解数据 */ @Test public void test02 () throws Exception { // 获得Class对象 Class c = BookShelf.class; // 判断类上是否使用Book注解 if (c.isAnnotationPresent(Book.class)){ // 根据注解的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.getAnnotations(); Annotation[] annotations = c.getDeclaredAnnotations(); System.out.println(Arrays.toString(annotations)); } /* 获得成员方法上注解的数据 */ @Test public void test01 () throws Exception { // 获得Class对象 Class c = BookShelf.class; // 获得成员方法对应的Method对象 Method m = c.getMethod( "showBook" ); // 根据注解的Class对象获得对应的注解对象 Book annotation = m.getAnnotation(Book.class); // 获得书名 System.out.println(annotation.value()); // 获得作者 System.out.println(Arrays.toString(annotation.authros())); // 获得价格 System.out.println(annotation.price()); } }

2.6 注解处理

编译时处理(APT)

通过javax.annotation.processing.Processor接口实现编译时处理:

@SupportedAnnotationTypes("com.example.MyAnnotation") @SupportedSourceVersion(SourceVersion.RELEASE_8) public class MyAnnotationProcessor extends AbstractProcessor { @Override public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 处理逻辑 return true ; } }

运行时处理(反射)

Method method = obj.getClass().getMethod( "testMethod" ); if (method.isAnnotationPresent(TestAnnotation.class)) { TestAnnotation annotation = method.getAnnotation(TestAnnotation.class); System.out.println( "Description: " + annotation.description()); System.out.println( "Priority: " + annotation.priority()); }

2.7 实战应用

简化配置

@Config(path = "/config/app.properties") public class AppConfig { @Value(key = "app.name") private String appName; // 通过反射读取配置并注入 }

单元测试框架

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test { boolean enabled () default true ; String expectedException () default "" ; } public class TestRunner { public static void runTests (Class<?> testClass) throws Exception { // 实现测试逻辑 } }

ORM框架

@Entity(tableName = "users") public class User { @Id @Column(name = "user_id") private Long id; @Column(name = "user_name", length = 50) private String name; // 通过注解生成SQL }

重复注解(Java 8+)

@Repeatable(Schedules.class) public @interface Schedule { String dayOfWeek () default "Mon" ; int hour () default 12 ; } @Schedule(dayOfWeek = "Mon", hour = 10) @Schedule(dayOfWeek = "Wed", hour = 14) public void doPeriodicTask () {}

类型注解(Java 8+)

public @NotNull String getName ( @Nullable Object obj) { // 方法实现 } List< @NonNull String> names = new ArrayList <>();

注解与AOP

结合Spring AOP使用注解:

@Aspect @Component public class LoggingAspect { @Before("@annotation(com.example.Loggable)") public void logBefore (JoinPoint joinPoint) { // 记录日志 } }

2.8 常见问题与总结

1、注解与接口的区别:注解主要用于元数据标记,而接口定义行为契约。

2、注解的局限性:注解不能包含业务逻辑,只能提供元数据信息。

总结:

Java注解是一个强大的元数据工具,合理使用可以:

  • 减少样板代码
  • 提高代码可读性
  • 实现框架级别的功能
  • 支持编译时检查

三、动态代理(原生)

为什么要有“代理”?生活中就有很多例子,例如委托业务等等,代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,这才是“代理”存在的原因。例如,我现在需要出国,但是我不愿意自己去办签证、预定机票和酒店(觉得麻烦 ,那么就可以找旅行社去帮我办,这时候旅行社就是代理,而我自己就是被代理了。

动态代理简单来说是:拦截对真实对象方法的直接访问,增强真实对象方法的功能。

动态代理详细来说是:代理类在程序运行时创建的代理对象被称为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。也就是说你想获取哪个对象的代理,动态代理就会动态的为你生成这个对象的代理对象。动态代理可以对被代理对象的方法进行增强可以在不修改方法源码的情况下,增强被代理对象方法的功能在方法执行前后做任何你想做的事情。动态代理技术都是在框架中使用居多,例如:Spring和Mybatis等一些主流框架技术中都使用了动态代理技术。

入门案例:

package com.water.base.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyTest { public static void main (String[] args) { Person person = new Person (); // 被代理对象 = 目标对象 Show proxy = (Show) Proxy.newProxyInstance( // 代理对象 person.getClass().getClassLoader(), // 类加载器 person.getClass().getInterfaces(), // 接口数组 new InvocationHandler () { // 调用处理器 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { System.out.println( "开始执行。。。" ); // 代理增强前置 method.invoke(person); // 执行被代理对象方法 System.out.println( "执行结束。。。" ); // 代理增强后置 return null ; } }); proxy.show(); // 代理对象调用,执行上述设置的invoke方法实现 } } class Person implements Show { @Override public void show () { System.out.println( "person info" ); } } interface Show { void show () ; }

输出:

开始执行。。。 person info 执行结束。。。

3.1 日志案例

现在,假设我们要实现这样的需求:在企业的大型系统中,每个业务层方法的执行,都需要有对应的日志记录,比如这个方法什么时候调用完成的,耗时多久等信息,根据这些日志信息,我们可以看到系统执行的情况,尤其是在系统出现错误的时候,这些日志信息就显得尤为重要了。现在有一种实现思路是这样的,业务层实现类代码如下:

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)); } } }

.

我们在业务层的方法每次执行的时候都去记录了开始时间和结束时间,这样虽然可以记录每个方法运行的截止时间和耗时情况,但是代码显得很臃肿,这些日记记录代码本身也是与业务功能无关的代码。

动态代理优化

那么有没有一种方式可以解决这个问题呢?动态代理就是解决此类问题非常好的实现手段,通过动态代理我们可以为该业务层实现类对象提供一个动态的代理对象。

该代理对象,可以为实现类的所有方法进行代理,并对代理的方法功能进行增强。也就是说只要调用了被代理实现类对象的方法,该方法的执行会先进入到代理对象中去,代理对象可以在该方法执行前记录开始时间,然后去触发该方法的执行,在方法执行完成以后再由代理对象去记录结束时间然后计算时间差作为日志记录,因为方法的日记记录由代理完成了,所以被代理对象的方法就无需自己单独记录日志操作了。这样就产生了一种非常好的设计模型。现在我们来使用动态代理写一个日志记录的代理类:

代码如下:

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)); } } }); } }

3.2 代理类Proxy剖析

在上述代码中 getProxy 方法即是用于获取某个实现类对象的一个代理对象。在该代码中,如果要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口:

java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一个静态方法来为一组接口的实现类动态地生成代理类及其对象。

Proxy方法一览:
newProxyInstance方法的三个参数的详解:

该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例

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方法的参数

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

1、Object proxy生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。

2、Method method:被代理的对象中被代理的方法的一个抽象。就是代表方法对象。

3、Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。

代理类定义完成以后业务层实现类的方法就无需再自己申明日记记录的代码了,因为代理对象会帮助做日志记录,修改后实现类代码如下:

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 ( "查询班级异常" ); } } }

开始使用动态代理去访问方法:

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)去真正触发该方法的执行,接下来代理对象进行方法结束时间的记录和日志的输出即可。这样整个过程就通过代理完美的实现了。

3.3 JDK代理小结

1、动态代理非常的灵活,可以为任意的接口实现类对象做代理。

2、动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
3、动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。动态代理同时也提高了开发效率。

4、缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。

四、代理机制(综合)

4.1 背景前言

在软件工程领域,代理模式是解决系统解耦和功能增强的经典方案。Java通过动态代理技术,实现了运行时对象行为的灵活扩展。从早期JDK 1.3引入动态代理,到CGLIB字节码增强,再到Spring AOP的完美集成,代理技术已成为现代Java框架的基石。

4.2 概念定义

代理模式:通过代理对象控制对真实对象的访问,在访问前后进行附加操作。分为三种形式:

  • 静态代理:手工编写代理类(编译时确定)
  • 动态代理:运行时生成代理类(JDK/CGLIB)
  • 虚拟代理:延迟加载真实对象(如图片加载代理)

核心价值

  • 职责分离(核心业务与附加功能解耦)
  • 功能增强(日志、事务、安全等非侵入式扩展)
  • 访问控制(权限校验、流量限制等)

4.3 基础使用

正确示例

静态代理(典型场景)
// 数据库操作接口 public interface DatabaseAccess { void query (String sql) ; } // 真实实现类 public class MySQLAccess implements DatabaseAccess { @Override public void query (String sql) { System.out.println( "执行MySQL查询: " + sql); } } // 代理类(添加日志功能) public class LoggingProxy implements DatabaseAccess { private DatabaseAccess realAccess; public LoggingProxy (DatabaseAccess realAccess) { this .realAccess = realAccess; } @Override public void query (String sql) { long start = System.currentTimeMillis(); realAccess.query(sql); long end = System.currentTimeMillis(); System.out.println( "查询耗时: " + (end - start) + "ms" ); } } // 使用示例 DatabaseAccess access = new LoggingProxy ( new MySQLAccess ()); access.query( "SELECT * FROM users" );
JDK动态代理(接口代理)
public class TransactionHandler implements InvocationHandler { private Object target; public TransactionHandler (Object target) { this .target = target; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { Connection conn = null ; try { conn = DataSource.getConnection(); conn.setAutoCommit( false ); Object result = method.invoke(target, args); conn.commit(); return result; } catch (Exception e) { if (conn != null ) conn.rollback(); throw e; } finally { if (conn != null ) conn.close(); } } } // 创建代理实例 DatabaseAccess proxy = (DatabaseAccess) Proxy.newProxyInstance( DatabaseAccess.class.getClassLoader(), new Class []{DatabaseAccess.class}, new TransactionHandler ( new MySQLAccess ()) );

反面案例

错误1:接口方法缺失
interface Service { void serve () ; } class RealService implements Service { public void serve () { /* ... */ } public void extraMethod () { /* ... */ } // 新增方法 } public static void main (String[] args) { Service proxy = (Service) Proxy.newProxyInstance(...); // 调用extraMethod()会抛出NoSuchMethodError }
错误2:错误使用this引用
class Target { public void method () { System.out.println( "Target: " + this .hashCode()); } } class FaultyHandler implements InvocationHandler { public Object invoke (Object proxy, Method method, Object[] args) { System.out.println( "Proxy: " + this .hashCode()); // 错误:this指向Handler实例 return method.invoke(proxy, args); // 导致无限递归 } }

4.4 高阶使用

正确实践

CGLIB方法过滤
public class SelectiveInterceptor implements MethodInterceptor { @Override public Object intercept (Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if (method.getName().startsWith( "get" )) { System.out.println( "拦截getter方法" ); } return proxy.invokeSuper(obj, args); } } // 创建代理时指定方法过滤 Enhancer enhancer = new Enhancer (); enhancer.setSuperclass(ProductService.class); enhancer.setCallbackFilter(method -> { return method.getName().startsWith( "update" ) ? 1 : 0 ; }); enhancer.setCallbacks( new Callback []{NoOp.INSTANCE, new SelectiveInterceptor ()});
Spring AOP注解驱动
@Aspect @Component public class CircuitBreakerAspect { private ConcurrentHashMap<String, CircuitState> stateMap = new ConcurrentHashMap <>(); @Around("@annotation(circuitBreaker)") public Object manageCircuit (ProceedingJoinPoint pjp, CircuitBreaker circuitBreaker) throws Throwable { String key = circuitBreaker.value(); CircuitState state = stateMap.getOrDefault(key, CircuitState.CLOSED); if (state == CircuitState.OPEN) { throw new ServiceUnavailableException ( "服务熔断中" ); } try { return pjp.proceed(); } catch (Exception e) { updateFailureCount(key); throw e; } } // 熔断状态机实现... }

常见误区

误用1:代理对象序列化
class UserServiceProxy implements Serializable { private UserService target; // 真实对象未序列化 // 反序列化时target将为null } // 正确做法:transient修饰或实现完整序列化逻辑
误用2:忽略final方法限制
class SecurityService { public final void checkPermission () { // final方法无法被CGLIB代理 // 权限检查逻辑 } } // 解决方案:使用接口代理或移除final修饰

4.5 核心类与方法

组件 核心类/接口 关键方法 说明
JDK代理 Proxy newProxyInstance() 创建动态代理实例
InvocationHandler invoke() 调用处理方法
CGLIB Enhancer create() 生成代理类
MethodInterceptor intercept() 方法拦截处理
Spring ProxyFactory getProxy() 创建Spring代理
Advised getTargetSource() 获取代理目标
AspectJ AspectJProxyFactory getProxy() AspectJ风格代理

4.6 主要应用场景

    1. 事务管理(Spring @Transactional
    1. 缓存代理(Guava Cache + AOP)
    1. 权限控制(方法级访问校验)
    1. 日志跟踪(调用链路记录)
    1. 服务熔断(Hystrix风格实现)
    1. 延迟加载(Hibernate懒加载)
    1. RPC客户端(Dubbo服务代理)
    1. Mock测试(单元测试桩对象)

4.7 开发应用要点

    1. 性能优化
  • 缓存代理实例避免重复创建
  • 对高频方法使用Method对象缓存
  • 优先选择接口代理(JDK动态代理)

    1. 安全实践
  • 避免在代理中暴露敏感信息
  • 对代理目标进行空值检查
  • 使用防御性拷贝保护可变参数

    1. 调试技巧
// 查看JDK代理类 System.setProperty( "jdk.proxy.ProxyGenerator.saveGeneratedFiles" , "true" ); // 查看CGLIB代理类 Enhancer enhancer = new Enhancer (); enhancer.setStrategy( new DefaultGeneratorStrategy () { public byte [] generate(ClassGenerator cg) { byte [] b = super .generate(cg); saveGeneratedClass(name, b); // 自定义保存逻辑 return b; } });
    1. 异常处理
public Object invoke (Object proxy, Method method, Object[] args) { try { return method.invoke(target, args); } catch (InvocationTargetException e) { Throwable realEx = e.getTargetException(); // 处理真实异常 } }

4.8 代理总结

Java代理技术是现代框架设计的核心支柱,理解其实现原理和适用场景对开发者至关重要:

  • 静态代理适用于简单场景,但易产生类膨胀
  • JDK动态代理需要接口支持,适合轻量级扩展
  • CGLIB代理可处理具体类,但需注意final方法限制
  • Spring AOP通过智能代理选择机制,实现了最佳实践

合理使用代理模式可以显著提升代码的可维护性和扩展性,但需警惕过度使用导致的性能问题和系统复杂性。

4.9 其他提醒

    1. 版本差异
  • JDK 8+对动态代理性能有显著优化
  • CGLIB 3.2+需要Java 8+环境

    1. 监控工具
  • 使用Arthas的jad命令反编译代理类
  • 通过VisualVM分析代理对象内存占用

    1. 替代方案
  • ByteBuddy:更现代的字节码操作库
  • Javassist:适合动态生成简单代理

    1. 设计警示
  • 避免形成"代理链地狱"(多层嵌套代理)
  • 谨慎处理代理对象的equals/hashCode方法
  • 注意类加载器导致的对象类型不一致问题

通过深入理解代理机制,开发者可以更好地驾驭Java生态中的各种框架,编写出更高效、更健壮的应用程序。

五、反射包Reflect

反射、注解、代理,其实都在JDK的reflect包下,说明他们的核心类彼此之间都是紧密联系的。
JDK的设计者,当时的想法就是想将reflect包打造成制作应用框架的基石,事实上也是如此。目前流行的框架,其底层实现基本都依赖这三:

    1. 反射:通过反射可以很好地控制对象的创建,实现控制反转(IOC)
    1. 注解:配置元数据,yml等配置信息,可以很方便通过反射获取注解获取,约定大于配置
    1. 代理:可以实现aop横切思想,动态生成接口实现类,如Mybatis的Mapper接口实现类等

六、Junit单元测试

单元测试,底层使用了反射+注解+代理等综合技术,可以充分感受其实现框架功能的强大。顺便学习如何使用该框架进行单元测试,保证代码质量。

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:用来静态修饰方法,该方法会在所有测试方法之后执行一次。

示例代码

/** 业务类:实现加减乘除运算 */ public class Cacluate { /* 业务方法1:求a和b之和 */ public int sum ( int a, int b) { return a + b + 10 ; } /* 业务方法2:求a和b之差 */ 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( "初始化操作" ); // 创建Cacluate对象 c = new Cacluate (); } @AfterClass // 用来静态修饰方法,该方法会在所有测试方法之后执行一次。 public static void close () { System.out.println( "释放资源" ); c = null ; } /* @Before // 用来修饰方法,该方法会在每一个测试方法执行之前执行一次。 public void init(){ System.out.println("初始化操作"); // 创建Cacluate对象 c = new Cacluate(); } @After // 用来修饰方法,该方法会在每一个测试方法执行之后执行一次。 public void close(){ System.out.println("释放资源"); c = null; }*/ @Test public void testSum () { int result = c.sum( 1 , 1 ); /* 断言:预习判断某个条件一定成立,如果条件不成立,则直接奔溃。 assertEquals方法的参数 (String message, double expected, double actual) message: 消息字符串 expected: 期望值 actual: 实际值 */ // 如果期望值和实际值一致,则什么也不发生,否则会直接奔溃。 Assert.assertEquals( "期望值和实际值不一致" , 12 ,result); System.out.println(result); } @Test public void testSub () { // 创建Cacluate对象 // Cacluate c = new Cacluate(); int result = c.sub( 1 , 1 ); // 如果期望值和实际值一致,则什么也不发生,否则会直接奔溃。 Assert.assertEquals( "期望值和实际值不一致" , 0 ,result); System.out.println(result); } }

七、总结

反射是基础是核心,由于反射技术的存在,我们一方面可以更深刻地理解Java的字节码文件对象,对于类各个成分的分析达到了显微镜扫描细胞的程度,我们对类的控制更加游刃有余。另一方面,我们可以施展魔法,针对类的各个成分做出不一样的处理。如:Constructor构造器对象用于创建对象,Method方法对象用于调用执行方法(代理中的处理器实现就是依赖该对象),Field成员变量对象可以获取值设置值,这些核心类各司其职,将反射的这张网,编织地恰如其分。

而注解,作为给代码提供元数据的载体,本人觉得不仅是程序受益匪浅,我们在开发过程中,也会形成一种“注解驱动”的模式进行开发,因为注解代表了标记、配置以及简洁。想想,添加一个@Async就能拥有异步的能力,添加@Schedule就能拥有定时周期执行的能力。人们,总是走在追求简洁的路上,因为简洁很多时候就是优雅,就是美。

反射+注解是框架的灵魂,其实代理也是框架的宠儿,作为一种代理模式,一种机制,可以动态生成一些代理对象,实现功能扩展,本质就是一种更高级的抽象横切,保护核心目标方法不受印象,同时增强功能。

最后简单学习一下Junit单元测试框架,其实也是这些技术的综合运用所实现的框架,感受其强大和便利性,也就更能体会:反射、注解、代理这三的强大了。我们一般不重复造轮子,但应该懂得如何造轮子,这三就是造轮子的一些核心技术,很多底层框架就是依赖他们,然后加上其他设计模式、抽象出来的,比如:耳熟能详的Java企业框架的事实标准:Spring框架。

深入学习框架源码前,建议将本文的知识点:反射、注解、代理掌握了,有一定了解了才去看源码,并且在看的过程中,不断印证:这里使用了反射,这里使用了注解配置、这里是注解的解析、这里是动态代理等等,这样就会有一种前后呼应的感觉。就像是有种感觉(不是错觉):换你来制作框架,你也行的样子(自信傲娇)。

源码,其实就是基础核心技术+设计模式的综合应用,框架的扩展性,便捷性,可配置性决定了框架必须得高度抽象,接口设计解耦、抽象基类、核心类协作紧密,浑然一体,这样才是一个优秀的框架。

一句话:基础不牢,地动山摇~

更多推荐