最近在准备实习面试,被学长问到了Java反射,注解和动态代理的内容,发现有点本身有点懵,这几天查了不少资料,就来讲下本身的理解吧【若有错误,望指正】html
首先,咱们得弄清一个,什么是反射(Reflection)。简单的来讲,反射就是让咱们在程序运行的时候可以查看到类的信息,获取并调用类的任意方法和属性。java
在Java运行时,系统会将全部的对象维护一个被称为运行是的类型标识,而后这个信息跟踪这每一个对象所属的类,咱们能够和根据Java专门的类访问这些信息,这个类就是Class
【实际上Class对象表示的是一个类型,它不必定是类,多是基本数据类型,好比int】。面试
经过getClass()获取数组
Student stu = new Student;
Class c = stu.getClass();
//若是这个类在包里面,则会将包名也打印出来
// getSimpleName只得到类名
System.out.println(c.getName());
使用forName获取
一样,咱们也可使用静态方法forName
得到Class对象。例如:dom
String className= "java.util.Random";
Class c2 = Class.forName(className);
固然,className必须为接口或者类名才能成功。ide
直接获取this
Class c3 = Student.class;
使用Class对象的newInstance()方法来建立Class对象spa
Class c3 = Test.class; Object o = c3.newInstance();
其中newInstance()会根据类的默认构造器【无参构造器】
建立新的对象,若是没有默认的构造器,就会报错。假如咱们的构造器里面有参数怎么办,这时候咱们就须要使用java.lang.reflect.Constructor
中的newInstance()
方法了。代理
使用Constructor类中的newInstance()code
// getConstructor里面是构造参数的形参内容
Constructor constructor = c3.getConstructor(String.class);
Object o = constructor.newInstance("你好");
在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 {
public void say(String msg){
System.out.println(msg);
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class c = Test.class;
// 返回惟一的方法,第一个参数是方法的名字,第二个是方法参数的类型
Method method = c.getMethod("say", String.class);
Object o = c.newInstance();
method.invoke(o,"你好");
}
}
Constructor的获取以及方法
Class textClass = Test.class;
// 一样getDeclaredConstructors()和getConstructors()
Constructor[] constructors = aClass.getConstructors();
方法的的使用和Method差很少,可是它没有getReturnType()方法。
这些方法我只是简单的介绍了一下,详细信息能够参考API。
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还内置了另外三种注解——>
Java注解的自定义以及实现
Java注解的自定义以下
@Target(value = {ElementType.METHOD,ElementType.TYPE}) // 注解的做用地方
@Retention(value = RetentionPolicy.RUNTIME) // 注解的生命周期
public @interface TestAnnotation {
String name() default "这是个类";
int time();
}
那么咱们该若是如何使用注解发挥做用呢?咱们能够想一想,若是咱们可以得到注解的信息,那么咱们是否是就能够根据注解的信息来对方法作适当的调整。这时候,固然是大名鼎鼎的反射出马了。
使用以下:
@TestAnnotation(time = 0)
public class Test {
@TestAnnotation(name = "这是个方法",time = 1)
public void say(){
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 得到类上面的注解
TestAnnotation classAnnotation = Test.class.getAnnotation(TestAnnotation.class);
System.out.println("类的名字为:"+classAnnotation.name()+"------类的时间是"+classAnnotation.time());
Method method = Test.class.getMethod("say");
// 得到方法上面的注解
TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class);
System.out.println("方法的名字是:"+methodAnnotation.name()+"------方法的时间是"+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 {
Method method = Test.class.getMethod("say",String.class);
// 得到方法上面的注解
TestAnnotation methodAnnotation = method.getAnnotation(TestAnnotation.class);
// 执行方法
method.invoke(Test.class.newInstance(),methodAnnotation.name());
}
// 输出结果:
// 信息是:你好
代理就是给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是经过代理对象间接地操控原对象。
代理分为:
有几个概念:
在说动态代理以前,咱们先说一下静态代理,静态代理很简单,就是工厂模式。
那么就让咱们来实现一下静态代理吧
抽象角色:接口类
public interface TestService {
void say();
void play();
}
实现角色:实现类
public class TestServiceImpl implements TestService {
@Override
public void say() {
System.out.println("说话乎");
}
@Override
public void play() {
System.out.println("浪的飞起");
}
}
代理类
public class Test implements TestService{
private TestService testService;
public Test(TestService testService) {
this.testService = testService;
}
@Override
public void say() {
System.out.println("开始说话");
testService.say();
System.out.println("结束说话");
}
@Override
public void play() {
System.out.println("开始浪");
testService.play();
System.out.println("是个狼人");
}
public static void main(String[] args) {
TestServiceImpl testImpl = new TestServiceImpl();
Test test = new Test(testImpl);
test.play();
test.say();
}
}
在这里面,咱们能够看到,从外表看起来say()
和play()
方法都是由test
这个代理来完成的,但实际上,真正的执行者是TestServiceImpl
来完成的,test
只是在执行的时候加了一些事务逻辑。
既然有了静态代理,为何咱们还须要动态代理呢?从代码中能够看出,代理类和实现类是一一对应的,若是咱们有N个实现类,都要在方法执行前加同样的逻辑,那么咱们不得不建立N个代理类。这时候,咱们就须要使用动态代理了。
本次动态代理是针对JDK动态代理进行探讨。
正如前面所说,若是咱们要在不少类使用同一种逻辑时,会心态爆炸,那么咱们怎么去解决这个问题呢,这时候,咱们能够想想反射。
在使用的动态代理的过程当中,有两个关键的东东,一个是InvocationHandler
接口,一个是Proxy
类。
每个动态代理类都必需要实现InvocationHandler这个接口,而且每一个代理类的实例都关联到了一个handler,当咱们经过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
proxy: 指代咱们所代理的那个真实对象,也就是
实现类
method: 指代的是咱们所要调用真实对象的某个方法的Method对象
args: 指代的是调用真实对象某个方法时接受的参数
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{
// object为实现类的对象
private Object object;
public TestHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始方法执行");
Object o = method.invoke(object,args);
System.out.println("方法结束");
return o;
}
}
实例化代理类,并
public static void main(String[] args) {
// 实现类
TestService testService = new TestServiceImpl();
// 里面传入要代理的实现类对象
TestHandler testHandler = new TestHandler(testService);
/** * testService.getClass().getClassLoader() 表明咱们使用这个类来加载咱们代理对象 * testService.getClass().getInterfaces() 表明咱们调用这些接口中的方法 * testHandler 将代理对象与testHandler关联 */
TestService service = (TestService) Proxy.newProxyInstance(testService.getClass().getClassLoader(),
testService.getClass().getInterfaces(),testHandler);
service.play();
service.say();
反射,注解,以及动态代理就简单地介绍完了,能够这样说反射是注解以及动态代理的基础,注解的实现和动态代理都要靠反射发挥做用。
仍是多读下书吧,面试实习是把杀猪刀