Java反射,注解,以及动态代理
Java 反射,注解,以及动态代理
最近在准备实习面试,被学长问到了 Java 反射,注解和动态代理的内容,发现有点自己有点懵,这几天查了很多资料,就来说下自己的理解吧【如有错误,望指正】
Java 反射
首先,我们得弄清一个,什么是反射 (Reflection)。简单的来说,反射就是让我们在程序运行的时候能够查看到类的信息,获取并调用类的任意方法和属性。
在 Java 运行时,系统会将所有的对象维护一个被称为运行是的类型标识,然后这个信息跟踪这每个对象所属的类,我们可以和根据 Java 专门的类访问这些信息,这个类就是Class
【实际上 Class 对象表示的是一个类型,它不一定是类,可能是基本数据类型,比如 int】。
Class 获取方法
-
通过 getClass() 获取
Student stu = new Student; Class c = stu.getClass(); // 如果这个类在包里面,则会将包名也打印出来 // getSimpleName 只获得类名 System.out.println(c.getName());
-
使用 forName 获取
同样,我们也可以使用静态方法forName
获得 Class 对象。例如:String className= "java.util.Random"; Class c2 = Class.forName(className);
当然,className 必须为接口或者类名才能成功。
-
直接获取
Class c3 = Student.class;
由 Class 得到对象
-
使用 Class 对象的 newInstance() 方法来创建 Class 对象
Class c3 = Test.class; Object o = c3.newInstance();
其中 newInstance() 会根据类的
默认构造器【无参构造器】
创建新的对象,如果没有默认的构造器,就会报错。假如我们的构造器里面有参数怎么办,这时候我们就需要使用java.lang.reflect.Constructor
中的newInstance()
方法了。 -
使用 Constructor 类中的 newInstance()
// getConstructor 里面是构造参数的形参内容 Constructor constructor = c3.getConstructor(String.class); Object o = constructor.newInstance("你好");
java 反射中最重要的内容——检查类的结构
在 Java 的 java.lang.reflect 中有三个类:Field、Method、Constructor 分别来描述类的域【也就是变量】,方法和构造器。
-
Field 的获取以及方法
Class textClass = Test.class; // getDeclaredFields() 获得这个类的全部域 // getField() 获得公有域以及其父类的公有域 Field[] fields = textClass.getDeclaredFields();
简单的来说,通过 Field 可以获得:
变量的
权限
——getModifiers(),返回 int,然后通过 Modifier.toString(int) 获得访问权限获得变量的
类型
——getType()变量的
名字
——getName() -
Method 的获取以及方法
Class textClass = Test.class; // 同样可以使用 getMethods()和 getDeclaredMethods() 返回接口和类的方法 Method[] methods = textClass.getMethods();
通过 Method 可以获取:
方法的
权限
——getgetModifiers()方法的
返回值类型
——getReturnType(), 方法返回类型为Class
,然后你懂得。方法的
所有参数
——Parameter[] parameters = method.getParameters();方法的
执行
——invoke()。在获取一个方法后,我们可以使用invoke()
来调用这个方法。Object invoke(Object obj,Object...args),obj 为实例化后的对象【对于静态方法可以被设置 null】,args 为方法调用的参数
例如,
public class Test {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">say</span><span class="hljs-params">(String msg)</span>{ System.out.println(msg); } <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { <span class="hljs-type">Class</span> <span class="hljs-variable">c</span> <span class="hljs-operator">=</span> Test.class; <span class="hljs-comment">// 返回唯一的方法,第一个参数是方法的名字,第二个是方法参数的类型</span> <span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> c.getMethod(<span class="hljs-string">"say"</span>, String.class); <span class="hljs-type">Object</span> <span class="hljs-variable">o</span> <span class="hljs-operator">=</span> c.newInstance(); method.invoke(o,<span class="hljs-string">"你好"</span>); }
}
-
Constructor 的获取以及方法
Class textClass = Test.class; // 同样 getDeclaredConstructors()和 getConstructors() Constructor[] constructors = aClass.getConstructors();
方法的的使用和 Method 差不多,但是它没有 getReturnType() 方法。
这些方法我只是简单的介绍了一下,详细信息可以参考 API。
神奇的 Java 注解
Java 注解可以很简单的说,就是为方法或者其他数据提供描述的东西。
它的本质就是一个接口, 一个继承了 Annotation 的接口。
-
基本 java 注解的类型
【元注解】:也就是在自定义一个注解时,可以注解在注解上面,有以下几个元注解——>-
@Target:注解的作用目标,用来指明注解可以作用的目标是谁,例如类,方法或者字段属性, 里面的 value【为一个 ElementType 数组】可以指明值如下:
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
ElementType.FIELD:允许作用在属性字段上
ElementType.METHOD:允许作用在方法上
ElementType.PARAMETER:允许作用在方法参数上
ElementType.CONSTRUCTOR:允许作用在构造器上
ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
ElementType.ANNOTATION_TYPE:允许作用在注解上
ElementType.PACKAGE:允许作用在包上
-
@Retention:注解的生命周期,里面的 value【枚举类型】可以指明值如下:
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
RetentionPolicy.RUNTIME:永久保存,可以反射获取
- @Documented:注解是否应当被包含在 JavaDoc 文档中
- @Inherited:是否允许子类继承该注解
- @Repeatable:重复注解,允许这个注解在某个方法和其他数据上面重复使用
【Java 内置三大注解】:除了上述元注解,Java 还内置了另外三种注解——>
- @Override:子类重写父类的方法时,会使用该注解。用于检查父类是否包含该注解
- @Deprecated:当某一方法和字段不推荐使用时,使用该注解标注。
- @SuppressWarnings:压制 Java 的警告
-
-
Java 注解的自定义以及实现
Java 注解的自定义如下
@Target(value = {ElementType.METHOD,ElementType.TYPE}) // 注解的作用地方 @Retention(value = RetentionPolicy.RUNTIME) // 注解的生命周期 public @interface TestAnnotation { String name() default "这是个类"; int time(); }
那么我们该如果如何使用注解发挥作用呢?我们可以想想,如果我们能够获得注解的信息,那么我们是不是就可以根据注解的信息来对方法做适当的调整。这时候,当然是大名鼎鼎的反射出马了。
- java.lang.Package.getAnnotation(Class<A> annotationClass) 获得这个指令类型的注解。
使用如下:
@TestAnnotation(time = 0) public class Test {
<span class="hljs-meta">@TestAnnotation(name = "这是个方法",time = 1)</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">say</span><span class="hljs-params">()</span>{ } <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> <span class="hljs-keyword">throws</span> ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException { <span class="hljs-comment">// 获得类上面的注解</span> <span class="hljs-type">TestAnnotation</span> <span class="hljs-variable">classAnnotation</span> <span class="hljs-operator">=</span> Test.class.getAnnotation(TestAnnotation.class); System.out.println(<span class="hljs-string">"类的名字为:"</span>+classAnnotation.name()+<span class="hljs-string">"------类的时间是"</span>+classAnnotation.time()); <span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> Test.class.getMethod(<span class="hljs-string">"say"</span>); <span class="hljs-comment">// 获得方法上面的注解</span> <span class="hljs-type">TestAnnotation</span> <span class="hljs-variable">methodAnnotation</span> <span class="hljs-operator">=</span> method.getAnnotation(TestAnnotation.class); System.out.println(<span class="hljs-string">"方法的名字是:"</span>+methodAnnotation.name()+<span class="hljs-string">"------方法的时间是"</span>+methodAnnotation.time()); }
}
// 输出:
// 类的名字为:这是个类 ------ 类的时间是 0
// 方法的名字是:这是个方法 ------ 方法的时间是 1现在我们知道如何进行自定义注解的使用了,那么我们怎么能够根据注释内容的不同去改变方法的执行呢?这时候,我们我们就可以使用
invoke()
方法了。举个最简单的栗子:
@TestAnnotation(name = "你好") public void say(String msg){ System.out.println("信息是:"+msg); } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
<span class="hljs-type">Method</span> <span class="hljs-variable">method</span> <span class="hljs-operator">=</span> Test.class.getMethod(<span class="hljs-string">"say"</span>,String.class); <span class="hljs-comment">// 获得方法上面的注解</span> <span class="hljs-type">TestAnnotation</span> <span class="hljs-variable">methodAnnotation</span> <span class="hljs-operator">=</span> method.getAnnotation(TestAnnotation.class); <span class="hljs-comment">// 执行方法</span> method.invoke(Test.class.newInstance(),methodAnnotation.name()); } <span class="hljs-comment">// 输出结果:</span> <span class="hljs-comment">// 信息是:你好</span>
代理
代理就是给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
代理分为:
- 静态代理:代理类是在编译时就已经实现好了,成为了一个 class 文件
- 动态代理:是在程序运行时动态地生成类字节码,然后加载到 JVM 中
有几个概念:
- 抽象角色:接口类
- 实现角色:实现类
- 代理角色:代理实现的类,最终使用的对象
静态代理
在说动态代理之前,我们先说一下静态代理,静态代理很简单,就是工厂模式。
那么就让我们来实现一下静态代理吧
抽象角色:接口类
public interface TestService {
void say();
void play();
}
实现角色:实现类
public class TestServiceImpl implements TestService {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">say</span><span class="hljs-params">()</span> {
System.out.println(<span class="hljs-string">"说话乎"</span>);
}
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">play</span><span class="hljs-params">()</span> {
System.out.println(<span class="hljs-string">"浪的飞起"</span>);
}
}
代理类
public class Test implements TestService{
<span class="hljs-keyword">private</span> TestService testService;
<span class="hljs-keyword">public</span> <span class="hljs-title function_">Test</span><span class="hljs-params">(TestService testService)</span> {
<span class="hljs-built_in">this</span>.testService = testService;
}
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">say</span><span class="hljs-params">()</span> {
System.out.println(<span class="hljs-string">"开始说话"</span>);
testService.say();
System.out.println(<span class="hljs-string">"结束说话"</span>);
}
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">play</span><span class="hljs-params">()</span> {
System.out.println(<span class="hljs-string">"开始浪"</span>);
testService.play();
System.out.println(<span class="hljs-string">"是个狼人"</span>);
}
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
<span class="hljs-type">TestServiceImpl</span> <span class="hljs-variable">testImpl</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TestServiceImpl</span>();
<span class="hljs-type">Test</span> <span class="hljs-variable">test</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Test</span>(testImpl);
test.play();
test.say();
}
}
在这里面,我们可以看到,从外表看起来say()
和play()
方法都是由test
这个代理来完成的,但实际上,真正的执行者是TestServiceImpl
来完成的,test
只是在执行的时候加了一些事务逻辑。
既然有了静态代理,为什么我们还需要动态代理呢?从代码中可以看出,代理类和实现类是一一对应的,如果我们有 N 个实现类,都要在方法执行前加一样的逻辑,那么我们不得不创建 N 个代理类。这时候,我们就需要使用动态代理了。
动态代理
本次动态代理是针对 JDK 动态代理进行探讨。
正如前面所说,如果我们要在很多类使用同一种逻辑时,会心态爆炸,那么我们怎么去解决这个问题呢,这时候,我们可以想一想反射。
在使用的动态代理的过程中,有两个关键的东东,一个是InvocationHandler
接口,一个是Proxy
类。
- InvocationHandler
每一个动态代理类都必须要实现 InvocationHandler 这个接口,并且每个代理类的实例都关联到了一个 handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代我们所代理的那个真实对象, 也就是
实现类
method: 指代的是我们所要调用真实对象的某个方法的 Method 对象
args: 指代的是调用真实对象某个方法时接受的参数
- Proxy
Proxy 这个类的作用就是用来动态创建一个代理对象的类
其中我们使用最多是newProxyInstance()
去创建代理类
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
loader:一个 ClassLoader 对象,定义了由哪个 ClassLoader 对象来对生成的代理对象进行加载
interfaces:一个 Interface 对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口 (多态),这样我就能调用这组接口中的方法了
h:一个 InvocationHandler 对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个 InvocationHandler 对象上
创建一个代理类,实现方法调用前或后的逻辑
public class TestHandler implements InvocationHandler{
<span class="hljs-comment">// object为实现类的对象</span>
<span class="hljs-keyword">private</span> Object object;
<span class="hljs-keyword">public</span> <span class="hljs-title function_">TestHandler</span><span class="hljs-params">(Object object)</span> {
<span class="hljs-built_in">this</span>.object = object;
}
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Object <span class="hljs-title function_">invoke</span><span class="hljs-params">(Object proxy, Method method, Object[] args)</span> <span class="hljs-keyword">throws</span> Throwable {
System.out.println(<span class="hljs-string">"开始方法执行"</span>);
<span class="hljs-type">Object</span> <span class="hljs-variable">o</span> <span class="hljs-operator">=</span> method.invoke(object,args);
System.out.println(<span class="hljs-string">"方法结束"</span>);
<span class="hljs-keyword">return</span> o;
}
}
实例化代理类,并
public static void main(String[] args) {
<span class="hljs-comment">// 实现类</span>
<span class="hljs-type">TestService</span> <span class="hljs-variable">testService</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TestServiceImpl</span>();
<span class="hljs-comment">// 里面传入要代理的实现类对象</span>
<span class="hljs-type">TestHandler</span> <span class="hljs-variable">testHandler</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TestHandler</span>(testService);
<span class="hljs-comment">/**
* testService.getClass().getClassLoader() 代表我们使用这个类来加载我们代理对象
* testService.getClass().getInterfaces() 代表我们调用这些接口中的方法
* testHandler 将代理对象与testHandler关联
*/</span>
<span class="hljs-type">TestService</span> <span class="hljs-variable">service</span> <span class="hljs-operator">=</span> (TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(),
testService.getClass().getInterfaces(),testHandler);
service.play();
service.say();
反射,注解,以及动态代理就简单地介绍完了,可以这样说反射是注解以及动态代理的基础,注解的实现和动态代理都要靠反射发挥作用。
还是多读下书吧,面试实习是把杀猪刀