一、概述
反射机制是Java语言的一项特性,就是把Java类中的各个组成部分映射成相应的Java类。允许程序在运行时动态地获取类的信息,如类名、方法、属性等,并且可以在运行时调用这些信息。Java中的反射机制提供了一个强大的工具,可以实现很多灵活的编程方式,例如在运行时动态处理对象、生成动态代理等。
二、一般应用
2.1 反射的基础-Class
Java当中的类用来表示具有相同属性的方法和对象的结合,是抽象的概念。对象是类创造的,同一个类的不同对象具有不同的属性值。
Java当中定义的所有类都属于用一类事物,可以用Class来表示。
2.1.1 Class类的对象-字节码
Class类的对象就是不同的类对应的字节码。
获取Class对象的三种方法:
- 对象名.getClass()
- 类名.class
- Class.forName()方法(常用)
package reflect.Class;
public class Test1 {
public static void main(String[] args) throws Exception{
Class<String> aClass1= String.class;
String s = "dwadwadw";
Class<? extends String> aClass2 = s.getClass();
Class<?> aClass3 = Class.forName("java.lang.String");
System.out.println(aClass1.hashCode());
System.out.println(aClass2.hashCode());
System.out.println(aClass3.hashCode());
}
}
1523554304
1523554304
1523554304
2.1.2 基本数据类型的Class对象
Java的基本数据类型都有各自的Class对象。
void也有自己对应的Class对象。
基本数据类型对应的封装类有属性TYPE,这个属性代表了封装类所封装的基本数据类型的Class对象。
package reflect;
public class Test2 {
public static void main(String[] args) {
System.out.println(int.class == Integer.TYPE);
System.out.println(boolean.class == Boolean.TYPE);
System.out.println(float.class == Float.TYPE);
System.out.println(void.class.getName());
}
}
true
true
true
void
2.2 通过反射创建对象
Object obj = Class.forName(“java.xxx.xxx”).newInstance();
package reflect.Class;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Test3 {
public static void main(String[] args) throws Exception{
String className = Files.readAllLines(Paths.get("d:\\test\\21.txt")).get(0);
Class aClass = Class.forName(className);
Object o1 = aClass.newInstance();
Object o2 = aClass.newInstance();
Object o3 = aClass.newInstance();
System.out.println(o1);
System.out.println(o2);
System.out.println(o3);
}
}
reflect.Class.Order@4d405ef7
reflect.Class.Order@6193b845
reflect.Class.Order@2e817b38
通过这种方法只能调用该类public无参构造。
2.3 获取构造方法
Constructor类用来描述类中所定义的构造方法 。
创建几个工具类,方便功能实现:
package reflect.Class;
public class Example {
public Example(){
System.out.println("无参");
}
public Example(int a){
System.out.printf("有参: a=%d\n", a);
}
public Example(int a, double b){
System.out.printf("有参: a=%d,b=%f\n", a, b);
}
private Example(String s){
System.out.printf("有参: s=%s\n",s);
}
protected Example(String s, int a){
System.out.printf("有参: s=%d,a=%f",s,a);
}
}
package reflect.Class;
import java.util.Date;
public class Book {
public String bookName;
public int bookId;
private double date;
public Book(String bookName, int bookId, double date) {
this.bookName = bookName;
this.bookId = bookId;
this.date = date;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public double getDate() {
return date;
}
public void setDate(double date) {
this.date = date;
}
private void privateMethod(){
}
@Override
public String toString() {
return "Book{" +
"bookName='" + bookName + '\'' +
", bookId=" + bookId +
", date=" + date +
'}';
}
}
2.3.1 public构造方法
- 得到类的所有构造方法
Constructor constructors[]=Class.forName(“java.lang.String”).getConstructors();
2.得到类中某个具体的构造方法,在getConstructor中传入参数类型所对应的字节码
Constructor constructor=Example.class.getConstructor(int.class);
package reflect.Class;
import java.lang.reflect.Constructor;
public class Test4 {
public static void main(String[] args) throws Exception{
Class cls = Example.class;
Example ex1 = (Example) cls.newInstance();
System.out.println(ex1);
System.out.println("所有构造方法");
Constructor[] constructors = cls.getConstructors();
for (Constructor construct : constructors) {
System.out.println(construct);
}
Constructor constructor1 = cls.getConstructor();//获取无参构造方法
Constructor constructor2 = cls.getConstructor(int.class);//获取参数类型为int的构造方法
Constructor constructor3 = cls.getConstructor(int.class, double.class);//获取参数类型为(int, double)的构造方法
Example ex2 = (Example)constructor3.newInstance(434, 3.131);
System.out.println(ex2);
}
}
无参
reflect.Class.Example@5acf9800
所有构造方法
public reflect.Class.Example(int,double)
public reflect.Class.Example(int)
public reflect.Class.Example()
有参: a=434,b=3.131000
reflect.Class.Example@2ff4acd0
2.3.2获取所有构造方法并调用(包括不公开的)
package reflect.Class;
import java.lang.reflect.Constructor;
public class Test5 {
public static void main(String[] args) throws Exception{
Class classs = Example.class;
Constructor[] declaredConstructors = classs.getDeclaredConstructors();
for (Constructor construct :
declaredConstructors) {
System.out.println(construct);
}
//获取一个私有方法
Constructor constructor = classs.getDeclaredConstructor(String.class);
constructor.setAccessible(true);//允许调用私有的构造方法
Example ret = (Example)constructor.newInstance("鬼强");
System.out.println(ret);
}
}
protected reflect.Class.Example(java.lang.String,int)
private reflect.Class.Example(java.lang.String)
public reflect.Class.Example(int,double)
public reflect.Class.Example(int)
public reflect.Class.Example()
有参: s=鬼强
reflect.Class.Example@279f2327
2.4 获取接口、父类、该类所在package
package reflect.Class;
public class Test2 {
public static void main(String[] args) {
Class classs = String.class;
printClassInfo(classs);
}
public static void printClassInfo(Class cls) {
//类名
System.out.println("类(接口)的名称:" + cls.getSimpleName());
System.out.println("完全限定名:" + cls.getName());
System.out.println("类(接口)的类型名称" + cls.getTypeName());
//父类
Class superCls= cls.getSuperclass();
System.out.println("类的父类:" + superCls.toString());
//实现的接口
Class[] interfaceCls = cls.getInterfaces();
System.out.println("当前类实现的接口");
for (Class icls :
interfaceCls) {
System.out.println(icls);
}
//package包
Package pck = cls.getPackage();
if (pck != null){
System.out.println("类所在的包的名称" + pck.getName());
}
//判断类型
System.out.println("是否为接口:" + cls.isInterface());
System.out.println(cls.isArray());
System.out.println(cls.isEnum());
}
}
类(接口)的名称:String
完全限定名:java.lang.String
类(接口)的类型名称java.lang.String
类的父类:class java.lang.Object
当前类实现的接口
interface java.io.Serializable
interface java.lang.Comparable
interface java.lang.CharSequence
类所在的包的名称java.lang
是否为接口:false
false
false
2.5 获取Field字段
Field类用来表示类中的属性(字段)。
- Class,getFields():得到Class对象的所有字段,返回的是Field数组。
- Class.getField(String name)返回一个Field对象,它反映此Class对象所表示的类或接口的指定公有成员字段。
- Field的对象所代表的某一个类的属性,而不是那个类的某一个对象的属性。要得到某个对象对应的属性值,需要通过get(Obiect obj)方法与某个对象具体关联。
- 对于非公有属性只能通过Class的getDeclaredField(String fieldName)方法得到。
- 对于私有属性要得到他所关联到的对象的值,需通过Field的setAccessible(Boolean boolean)方法设置。
- Field类的getType()方法用来得到字段所属的类型。
package reflect.Class;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class Test8 {
public static void main(String[] args) {
Class cls = Book.class;
Field[] fields = cls.getDeclaredFields();
for (Field field :
fields) {
System.out.println("成员变量修饰符" + field.getModifiers());
System.out.println("成员变量访问修饰符:" + Modifier.toString(field.getModifiers()));
System.out.println("成员变量名称:" + field.getName());
System.out.println("成员变量类型:" + field.getType());
}
}
}
成员变量修饰符1
成员变量访问修饰符:public
成员变量名称:bookName
成员变量类型:class java.lang.String
成员变量修饰符1
成员变量访问修饰符:public
成员变量名称:bookId
成员变量类型:int
成员变量修饰符2
成员变量访问修饰符:private
成员变量名称:date
成员变量类型:double
2.6 获取Method字段
Method用来表示类中的方法。通过Class对象的如下方法得到Method对象
Method getMethod()
按名称得到某个特定的pubilc方法(包括从父类或接口继承的方法)
Method[] getMethods()
得到public方法(包括从父类或接口继承的方法)
Method[] getDeclaredMethods()
得到所有方法(不包括继承的方法)
Method getDeclaredMethod()
按名称得到某个特定的方法(不包括继承的方法)
2.6.1 获取public字段
public class Main {
public static void main(String[] args) throws Exception {
// String对象:
String s = "Hello world";
// 获取String substring(int)方法,参数为int:
Method m = String.class.getMethod("substring", int.class);
// 在s对象上调用该方法并获取结果:
String r = (String) m.invoke(s, 6);
// 打印调用结果:
System.out.println(r);
}
}
2.6.2 获取非public字段
package reflect.Class;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
public class Test11 {
public static void main(String[] args) {
Class cls = Book.class;
//获取所有定义的方法(当前类)
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("方法的访问修饰符:" + Modifier.toString(method.getModifiers()));
System.out.println("方法的返回值类型:" + method.getReturnType());
System.out.println("方法的名称:" + method.getName());
Parameter[] parameters = method.getParameters();
for (Parameter p : parameters) {
System.out.println(p.getName());
System.out.println(p.getType());
}
}
}
}
方法的访问修饰符:public
方法的返回值类型:class java.lang.String
方法的名称:toString
方法的访问修饰符:public
方法的返回值类型:double
方法的名称:getDate
方法的访问修饰符:public
方法的返回值类型:class java.lang.String
方法的名称:getBookName
方法的访问修饰符:public
方法的返回值类型:void
方法的名称:setBookName
arg0
class java.lang.String
方法的访问修饰符:public
方法的返回值类型:int
方法的名称:getBookId
方法的访问修饰符:public
方法的返回值类型:void
方法的名称:setDate
arg0
double
方法的访问修饰符:private
方法的返回值类型:void
方法的名称:privateMethod
方法的访问修饰符:public
方法的返回值类型:void
方法的名称:setBookId
arg0
int
得到某个方法对应的Method对象后,需要调用如下方法来在某个对象上执行该方法:
- invoke()方法用来调用Method所表示的方法。
package reflect.Class;
import java.lang.reflect.Method;
import java.util.Random;
public class Test12 {
public static void main(String[] args) throws Exception{
Class cls = Base.class;
Object o = cls.newInstance();
Method method1 = cls.getMethod("create");
Method method2 = cls.getMethod("create", int.class);
int r1 = (int)method1.invoke(o);
int r2 = (int)method2.invoke(o,1000);
System.out.println(r1);
System.out.println(r2);
}
}
class Base {
public int create(){
Random random = new Random();
int i = random.nextInt(10);
return i;
}
public int create(int a){
Random random = new Random();
int i = random.nextInt(a);
return i;
}
}
7
589
2.6.3 多态
package reflect.Class;
import java.lang.reflect.Method;
public class Test14 {
public static void main(String[] args) throws Exception {
// 获取Person的hello方法:
Method h = Person.class.getMethod("hello");
// 对Student实例调用hello方法:
h.invoke(new Student());
}
}
class Person {
public void hello() {
System.out.println("Person:hello");
}
}
class Student extends Person {
public void hello() {
System.out.println("Student:hello");
}
}
Student:hello
三、代理模式
代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。
代理 模式是一种结构型设计模式。 代理模式角色分为 3 种:
Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接
RealSubject(真实主题角色):真正实现业务逻辑的类;
Proxy(代理主题角色):用来代理和封装真实主题; 代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象 和代理对象,在代理模式中引入了抽象层。
如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色 的关系在运行前就确定了。
动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行 前并不存在代理类的字节码文件
3.1 静态代理模式
//接口
public interface UserService {
void select();
void update();
}
//接口实现类(目标代理对象)
public class UserServiceImpl implements UserService {
@Override
public void select() {
System.out.println("select * ..................");
System.out.println("!查询执行");
}
@Override
public void update() {
System.out.println("update ...................");
System.out.println("更新执行");
}
}
//代理对象
public class UserServiceProxy implements UserService{
private UserService target;
public UserServiceProxy(){
target = new UserServiceImpl();
}
@Override
public void select(){
before();
target.select();
}
@Override
public void update(){
target.update();
after();
}
private void before(){
System.out.println("before-----------");
}
private void after(){
System.out.println("after-----------");
}
}
before-----------
select * ..................
!查询执行
update ...................
更新执行
after-----------
通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一 个优点。虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式: 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类。
2、当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。
3.2动态代理模式
JDK动态代理主要涉及两个类: java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 。我们通过编写一个调用逻辑处理器 LogHandler 类案例来提供日志增强功能,并实现 InvocationHandler接口;在LogHandler中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke() 方法中编写方法调用的逻辑处理。
动态代理的处理类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;
public class LogHandler implements InvocationHandler {
Object target; // 被代理的对象,实际的方法执行者
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
before();
Object result = method.invoke(target, args); // 调用 target 的 method 方
法
after();
return result; // 返回方法的执行结果
}
// 调用invoke方法之前执行
private void before() {
System.out.println(String.format("log start time [%s] ", new Date()));
}
// 调用invoke方法之后执行
private void after() {
System.out.println(String.format("log end time [%s] ", new Date()));
}
}
测试类:
import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Test {
public static void main(String[] args) throws IllegalAccessException,
InstantiationException {
// 1. 创建被代理的对象,UserService接口的实现类
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 2. 获取对应的 ClassLoader
ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
// 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口
UserService,
Class[] interfaces = userServiceImpl.getClass().getInterfaces();
// 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
// 这里创建的是一个自定义的日志处理器,须传入实际的执行对象
userServiceImpl
InvocationHandler logHandler = new LogHandler(userServiceImpl);
/*
5.根据上面提供的信息,创建代理对象 在这个过程中,
a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节
码
b.然后根据相应的字节码转换成对应的class,
c.然后调用newInstance()创建代理实例
*/
UserService proxy = (UserService) Proxy.newProxyInstance(classLoader,
interfaces, logHandler);
// 调用代理的方法
proxy.select();
proxy.update();
}
}
结果:
方法select开始执行
select * ..................
!查询执行
方法select结束执行
方法update开始执行
update ...................
更新执行
方法update结束执行
3.3 动态代理的常见用途
-
实现AOP(面向切面编程):在不改变原有代码的情况下,为程序中的某个方法添加额外的功能,如日志记录、性能监控、事务管理等。
-
实现RPC(远程过程调用):通过动态代理可以将网络通信、序列化等技术抽象出来,从而实现远程方法调用。
-
实现延迟加载:在访问某个对象的属性或方法时,可以通过动态代理在必要时才实例化该对象,从而节省内存和资源。
-
实现数据校验:在对某个对象进行操作时,可以通过动态代理对其进行校验,以确保数据的正确性和合法性。
-
实现权限控制:通过动态代理可以对某些方法进行访问控制,从而限制某些用户的访问权限。
四、总结
反射是Java语言中一种强大的特性,它允许程序在运行时动态地获取和操作类、对象、方法以及字段等信息。以下是反射的一些总结:
-
反射提供了一种可以在运行时获取类的信息的机制,比如类的名称、修饰符、字段信息、方法信息、构造方法信息等。
-
反射可以动态地创建类的实例,可以通过Class类的newInstance()方法创建一个类的对象。
-
反射可以动态地获取和设置对象的属性值,可以通过反射获取对象的指定字段的值或设置它的值。
-
反射可以动态地调用对象的方法,可以通过反射获取对象的指定方法并调用它。
-
反射可以用于框架设计,可以通过反射动态地获取类和方法等信息,从而实现一些通用的功能,比如ORM框架等。
-
反射可能会对性能产生一定的影响,因为反射在运行时需要进行许多的额外操作,比如方法查找、字段查找等,所以在使用反射的时候应该尽可能地减少不必要的操作。