每天开心一点

Java 反射(reflection)/注释(Annotation)/监听器(Listener)/装饰器(wrapper)/过滤器(Filter)(一)

2018-01-10 08:53:00    六月    1295    来源: https://www.2cto.com/kf/201708/665618.html

Java 反射(reflection)/注释(Annotation)/监听器(Listener)/装饰器(wrapper)/过滤器(Filter)。区分学习Java和JavaWeb中的几个特性。

一 反射(reflection)

1.1 作用

Java反射指的是可以于运行时加载/探知和使用编译期间完全未知的类。程序在运行状态中, 可以动态加载一个只有名称的类, 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能调用他的任意一个方法和属性。

加载完类之后, 在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射。

1.2 实现原理

反射基于如下四个类实现:

java.lang.Class; java.lang.reflect.Constructor; java.lang.reflect.Field; java.lang.reflect.Method; java.lang.reflect.Modifier;

反射的实现分为如下三步。

1.2.1 获取Class对象

Java中,无论生成某个类的多少个对象,这些对象都会对应于同一个Class对象。这个Class对象是由JVM生成的,通过它能够获悉整个类的结构。常用的获取Class对象的方法有三种:

1.使用Class类的静态方法。例如:

?
1
Class.forName("java.lang.String");

2. 使用类的“Class”方法。例如:

?
1
String.class;

3.使用对象的getClass()方法。如:

?
1
2
String str ="aa";
Class<!--?--> classType1 = str.getClass();

1.2.2创建对象

通过反射来生成对象的方式有两种: 1. 使用Class对象的 newInstance()方法来创建该Class对象对应类的实例(这种方式要求该Class对象的对应类有 默认构造器).

?
1
2
3
4
Class c =Class.forName("Employee");
  
 //创建此Class 对象所表示的类的一个新实例
Objecto = c.newInstance();//调用了Employee的无参数构造方法.

2. 先使用Class对象获取指定的Constructor对象, 再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例).

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 使用getConstructors获取构造器   
Constructor<!--?-->[] constructors = classType.getConstructors(); 
for(Constructor<!--?--> m : constructors) 
    System.out.println(m); 
                
System.out.println(); 
                
// 使用getDeclaredConstructors获取构造器    
constructors = classType.getDeclaredConstructors(); 
for(Constructor<!--?--> m : constructors) 
    System.out.println(m); 
    
输出: 
publiccom.quincy.ExtendType() 
    
publiccom.quincy.ExtendType() 
com.quincy.ExtendType(int,java.lang.String)

可以调用无参的默认构造函数和有参构造函数完成任务:

?
1
2
3
4
5
6
7
8
Class<!--?--> classType = ExtendType.class;
Constructor<!--?--> constructor1 = classType.getConstructor();
Object inst = constructor1.newInstance();
System.out.println(inst);
  
Constructor<!--?--> constructor2 = classType.getDeclaredConstructor(int.class, String.class);
Object inst = constructor2.newInstance(1,"123");
System.out.println(inst);

1.2.3 获取属性和方法

可以通过反射机制得到某个类的某个属性,然后改变对应于这个类的某个实例的该属性值。JAVA 的Class类提供了几个方法获取类的属性。

publicFieldgetField(Stringname)

返回一个 Field 对象,它反映此 Class 对象所表示的类或接口的指定公共成员字段

publicField[] getFields()

返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问公共字段

publicFieldgetDeclaredField(Stringname)

返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段

publicField[] getDeclaredFields()

返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段

getFields和getDeclaredFields区别:

getFields返回的是申明为public的属性,包括父类中定义,getDeclaredFields返回的是指定类定义的所有定义的属性,不包括父类的。

通过反射机制得到某个类的某个方法,然后调用对应于这个类的某个实例的该方法,Class类提供了几个方法获取类的方法。

publicMethodgetMethod(Stringname,Class... parameterTypes)

返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法

publicMethod[] getMethods()

返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法

publicMethodgetDeclaredMethod(Stringname,Class... parameterTypes)

返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法

publicMethod[] getDeclaredMethods()

返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法

1.2.4 调用类的方法

通过反射获取类Method对象,调用Field的Invoke方法调用函数。

?
1
2
3
4
Class<!--?--> classType = ExtendType.class; 
Object inst = classType.newInstance(); 
Method logMethod = classType.getDeclaredMethod("Log", String.class); 
logMethod.invoke(inst,"test");

当调用的方法为private属性时调用不成功,需要在invoke前添加setAccessible。

?
1
2
3
4
5
Class<!--?--> classType = ExtendType.class; 
Object inst = classType.newInstance(); 
Method logMethod = classType.getDeclaredMethod("Log", String.class); 
logMethod.setAccessible(true);
logMethod.invoke(inst,"test");

1.3 应用场景

反射机制最常用的场景即JDBC中 数据库引擎的加载(待学习补充,目前就学了这个)。

1.4 范例

使用JDBC的时候要去下载对应的驱动程序,使用 mysql,就要去mysql的官网下载,使用oracle,就去oracle的官网下载,然后把类库导入到工程中。这些驱动程序,其实就是实现了JDBC规范的类库。我使用的是mysql。1.首先通过反射com.mysql.jdbc.Driver类,实例化该类的时候会执行该类内部的静态代码块,该代码块会在Java实现的DriverManager类中注册自己,DriverManager管理所有已经注册的驱动类,当调用DriverManager.geConnection方法时会遍历这些驱动类,并尝试去连接数据库,只要有一个能连接成功,就返回Connection对象,否则则报异常。2.通过使用DriverManager.geConnection(url,user,password)函数,传入url,数据库用户名,数据库密码,得到数据库的Connection对象。 com.mysql.jdbc.Driver是mysql驱动类的全名,oracle驱动类的全名是oracle.jdbc.driver. OracleDriver。 连接数据库时要传入相应的url, mysql url的格式是:jdbc:mysql://:3306/ oracle url的格式是:jdbc:oracle:thin::1521:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
publicvoidTestJDBC2(){
try{
//通过反射实例化com.mysql.jdbc.Driver,
Driver driver = (Driver)Class.forName("com.mysql.jdbc.Driver").newInstance();
//得到数据库的连接对象
Connection conn =
DriverManager.getConnection("jdbc: mysql://127.0.0.1:3306/notedb","root","");
System.out.println(conn);
} catch(InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

从上面可以看出,我们把数据库的驱动名和url写死在了程序中,那么更换数据库的时候还是需要更改程序代码的。

我们可以将信息写在配置普文件中,程序运行时读取配置信息,然后通过配置信息连接数据库,那么程序和数据库的耦合度就很低了,可以随时更换数据库。

1.在程序目录下新建jdbc.properties配置文件,并写入连接数据库有关的信息。

2.读取配置信息,获取连接数据库所需要的属性值。

3.通过JDBC连接数据库。

参考:https://blog.csdn.net/yongjian1092/article/details/7364451

https://www.cnblogs.com/justPassBy/p/5296271.html