目录
反射
1、概念
反射是指可以在运行时动态的获取已加载类的信息(构造器、成员变量、方法等),并可以对这些信息进行操作。简单的理解就是可以通过反射机制把一个类中的构造器、变量、方法转换成一个对象来使用。
2、用途
- 获取类信息(类名、父类、修饰符、实现的接口等)
- 获取类的构造器、成员变量、方法并转换成对象
- 处理类、方法、字段上的注解动态实现注解相关功能
注:需要注意的是,反射会破坏封装性,因为我们可以通过反射机制获取到一个类的私有变量、方法、构造器(下面会有相关的代码例子参考)。在平时的业务代码编写一般很少用到反射机制,反射机制大多是结合注释来实现框架的开发、动态代理、单元测试等等。
3、相关代码实现以及demo
获取Class类对象
@Test
public void classReflectTest() throws ClassNotFoundException {
// 获取class对象-方法1
Class<Cat> catClass1 = Cat.class;
System.out.println(catClass1.getName());
// 获取class对象-方法2
// 类的名字从包的路径开始
Class<?> catClass2 = Class.forName("ahaaaa.cn.reflect.Cat");
// 获取class对象-方法3
Cat cat = new Cat();
Class<? extends Cat> catClass3 = cat.getClass();
}
获取构造器对象
注:getXxx 和 getDeclaredXxx 的区别在于getDeclaredXxx还可以获取私有的构造器、变量、方法,而getXxx不能。
@Test
public void constructorReflectTest(){
// 获取class对象
Class<Cat> catClass = Cat.class;
// 获取Cat类的构造器
Constructor<?>[] constructors = catClass.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor.getName()+"--->"+constructor.getParameterCount());
}
}
获取成员变量
@Test
public void fieldReflectTest(){
// 获取class对象
Cat cat = new Cat();
Class<? extends Cat> catClass = cat.getClass();
// 获取Cat的成员变量
Field[] fields = catClass.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
}
获取方法
@Test
public void methodReflectTest() throws Exception {
// 获取class对象
Class<?> catClass = Class.forName("ahaaaa.cn.reflect.Cat");
Cat cat = new Cat();
// 获取Cat的方法
Method[] declaredMethods = catClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName()+"--->"+declaredMethod.getParameterCount());
}
// 获取方法后可以通过method.invoke()来调用方法执行。
// 第一个参数需要该方法所在类的一个对象,第二个参数为方法的参数(可以多个,没有可以不填)
Method eat = catClass.getDeclaredMethod("eat",String.class);
// 默认是没有访问权限的,需要调用setAccessible改为true(称为暴力反射)
eat.setAccessible(true);
eat.invoke(cat,"鱼儿");
}
综合demo
设计一个小的反射demo,接收一个类对象获取他的属性并输出到文件中;
@Test
public void FrameReflectTest() throws Exception {
Cat cat = new Cat("小黑", 11);
Person person = new Person("ahaaaa", 178.5, 130);
ObjectFrame.saveObject(cat);
ObjectFrame.saveObject(person);
}
public static void saveObject(Object obj) throws Exception{
PrintStream printStream = new PrintStream(new FileOutputStream("E:\\桌面\\basic-java-learning\\src\\main\\java\\ahaaaa\\cn\\reflect\\data.txt", true));
Class<?> aClass = obj.getClass();
String simpleName = aClass.getSimpleName();
printStream.println("--------------"+simpleName+"--------------");
for (Field declaredField : aClass.getDeclaredFields()) {
declaredField.setAccessible(true);
printStream.println(declaredField.get(obj));
}
}
运行结果:
同样的也可以获取类的其他信息,然后调用相关的方法进行操作。
注解
1、概念
Java中的注解为类、属性、方法等元数据的一种机制。简单的理解就是为类或者类中的某个方法、字段打上标记,后续通过程序可以调用相关方法获取到被标记的对象,然后再做额外的处理。
举个例子:比如我们经常用的@Override注解,就是标记该方法是覆盖父类的方法。再如@Deprecated注解,标记已过时的方法或类。在idea中会检查所有带上@Deprecated注解的方法和类,如果有加的话会提示该方法已过时。
2、作用
- 通过打上注解可以带上元数据;
- 编译时检查,例如@Deprecated;
- 运行时动态处理,通过注解标记可以在代码中做检查并增加额外的代码对打上注解的类或者方法等进行处理。
- 配置参数
3、自定义注解
package ahaaaa.cn.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Description of the class.
*
* @Author ahaaaa
* @Date 2023-09-04 16:21
* @Description
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
int age();
}
其中@Target和@Retention称为元注解,是用来标记注解的注解。
4、注解的获取解析
直接上代码比较容易理解。
package ahaaaa.cn.annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* Description of the class.
*
* @Author ahaaaa
* @Date 2023-09-04 16:23
* @Description
*/
@MyAnnotation(name = "ahaaaa", age = 21)
public class MyAnntationTest {
@MyAnnotation(name = "chamu", age = 21)
public void test1(){
System.out.println("test1");
}
public void test2(){
System.out.println("test2");
}
@MyAnnotation(name = "cpx", age = 20)
public void test3(){
System.out.println("test3");
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
// 获取注解类Class对象
Class<MyAnntationTest> myAnntationTestClass = MyAnntationTest.class;
Method[] declaredMethods = myAnntationTestClass.getDeclaredMethods();
MyAnntationTest myAnntationTest = new MyAnntationTest();
for (Method declaredMethod : declaredMethods) {
// 判断方法MyAnnotation中注解是否存在
if (declaredMethod.isAnnotationPresent(MyAnnotation.class)){
// 获取方法上的注解对象
MyAnnotation annotation = declaredMethod.getAnnotation(MyAnnotation.class);
System.out.println(annotation.name());
System.out.println(annotation.age());
// 触发有注解标记的方法
declaredMethod.invoke(myAnntationTest);
}
}
}
}
动态代理
1、概念
Java中的动态代理是一种在程序运行期间动态生成代理对象的机制。它允许在不修改原始类的情况下,通过代理类来对目标类进行扩展和增强。简单来说就是通过动态代理可以让原本某个方法多执行动态代理对象中的对象,即原方法上功能的增加。可以通过下面的一个简单demo例子来理解。
2、两个核心接口
-
InvocationHandler接口:该接口定义了一个invoke方法,用于在代理对象的方法被调用时进行拦截和处理。开发者需要自定义一个InvocationHandler实现类,并重写invoke方法,实现自己的逻辑。
-
Proxy类:Proxy类是动态代理的核心类,它提供了创建代理对象的静态方法。其中,newProxyInstance()方法用于创建动态代理对象。它接收三个参数:ClassLoader对象、Class数组和InvocationHandler对象。
3、代码示例
有个老板Boss(被代理对象)要开会,助理(代理)帮忙召集人(增加召集员工功能)、组织大家听讲(增加组织大家功能)。
Boss类
package ahaaaa.cn.proxy;
/**
* Description of the class.
*
* @Author ahaaaa
* @Date 2023-09-04 16:44
* @Description
*/
public class Boss implements Person{
private String name;
public Boss(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void speak() {
System.out.println(name+":公司上市了,大家辛苦了!");
}
@Override
public void praise() {
System.out.println(name+":今年大家奖金翻倍!!!");
}
}
ProxyUtil类(代理)
package ahaaaa.cn.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Description of the class.
*
* @Author ahaaaa
* @Date 2023-09-04 16:49
* @Description
*/
public class ProxyUtil {
public static Person createProxy(Boss boss){
// 第一个参数为当前类的类加载器即可,第二个为代理类的接口类对象,增加的处理逻辑写在第三个参数invoke方法中
Person person = (Person) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("speak")){
System.out.println("代理助手:大家来一下,林老板有话要讲。");
}else if (method.getName().equals("praise")){
System.out.println("代理助手:大家安静下,"+boss.getName()+"还有一件事要宣布。");
}
return method.invoke(boss);
}
});
return person;
}
}
ProxyTest测试类
package ahaaaa.cn.proxy;
import org.junit.Test;
/**
* Description of the class.
*
* @Author ahaaaa
* @Date 2023-09-04 16:56
* @Description
*/
public class ProxyTest {
@Test
public void test1(){
Boss boss = new Boss("林总");
Person proxy = ProxyUtil.createProxy(boss);
proxy.speak();
proxy.praise();
}
}
运行结果:
概括与个人理解
反射+注解可以实现框架,简单的描述一下,例如我开发了一个框架,我提供了@RemoveCache注解(限定加在方法上,功能为移除该方法对应返回值的缓存),别人调用我这个注解时,我会通过反射机制获取类的所有方法,判断是否存在@RemoveCache,如果存在的话,我就调用写好的removeCache( )方法来清理缓存。这就是一个简单的框架注解实现,当然真正实现的时候逻辑不只是这么简单,还有很多判断和异常需要处理等操作。
动态代理,我个人理解他是增强方法,就是在我原本的方法执行前、后增加操作、处理我原本方法的参数、返回值等等操作,学过spring的伙伴就知道他是用来实现aop的关键之一。
上述示例代码我都放在了gitee中,需要的伙伴可以自己拿。