一.反射
1.认识反射
- 反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量、方法、构造等)
- 作用: 可以操作一个不存在的类,创建该类的对象,调用该类的方法。所有的Java框架底层都是反射(Spring,Mybatis…)
代码执行过程
- 1.反射第一步:加载类,获取类的字节码:Class对象
- 2、获取类的构造器:Constructor对象
- 3、获取类的成员变量:Field对象
- 4.获取类的成员方法:Method对象
2.获取加载类三种方法````````````````````````
//直接通过全类名获取类对象
Class aClass = Class.forName("com.dream.reflect.Student");
//通过对象名获取类对象
Student student = new Student();
Class aClass1 = student.getClass();
//通过类名称获取类对象
Class studentClass = Student.class;
System.out.println(aClass == aClass1);
System.out.println(aClass1 == studentClass);
3.Class提供了从类中获取构造器的方法以及创建对象
- 注意: 如果构造方法是私有化的不能直接通过newInstance()创建对象,需要使用到暴力反射,setAccessible(true)
package com.dream.reflect;
import java.lang.reflect.Constructor;
import java.util.Arrays;
public class GetConstructorAndInstance {
public static void main(String[] args) throws Exception {
//1.获取到class字节码文件
Class clazz = Class.forName("com.dream.reflect.Student");
//获取所有被public 修饰的构造方法
Constructor[] constructors = clazz.getConstructors();
System.out.println(Arrays.toString(constructors));
//获取到所有的构造方法,包括private
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
System.out.println(Arrays.toString(declaredConstructors));
//获取到某一个被public 修饰的构造方法
Constructor cs = clazz.getConstructor(String.class, int.class);
System.out.println(cs);
//创建对象使用newInstance()
Object obj = cs.newInstance("小明", 12);
System.out.println(obj);
//获取到某一个被private 修饰的构造方法
Constructor dc = clazz.getDeclaredConstructor(String.class);
//由于private修饰的构造方法不能直接使用,需要设置 accessible 为 true,称为暴力反射
dc.setAccessible(true);
Object obj2 = dc.newInstance("小红");
System.out.println(obj2);
//获取无参构造
Constructor cs2 = clazz.getConstructor();
Object obj3 = cs2.newInstance();
System.out.println(obj3);
}
}
4.反射获取类中的变量并操作
代码演示
package com.dream.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
public class GetFiled {
public static void main(String[] args) throws Exception {
//1.获取类的字节码文件对象
Class clazz = Class.forName("com.dream.reflect.Student");
//获取到无参构造
Constructor dsc = clazz.getDeclaredConstructor();
System.out.println(dsc);
//创建一个对象
Object obj = dsc.newInstance();
// 获取到所有被public修饰的成员变量
Field[] fields = clazz.getFields();
System.out.println(Arrays.toString(fields));
//获取到类中的所有成员变量,无视权限修饰符
Field[] declaredFields = clazz.getDeclaredFields();
System.out.println(Arrays.toString(declaredFields));
//获取到name成员变量
Field ageFiled = clazz.getField("age");
System.out.println(ageFiled);
// 获取到name成员变量的值,无视权限修饰符
Field nameFiled = clazz.getDeclaredField("name");
System.out.println(nameFiled);
//给age赋值
ageFiled.set(obj, 18);
//给name赋值,因为name是private修饰的,想要赋值必须使用暴力反射
nameFiled.setAccessible(true);
nameFiled.set(obj, "小明");
//获取name的值
System.out.println(nameFiled.get(obj));
System.out.println(ageFiled.get(obj));
}
}
5.通过反射获取到类中的方法
代码演示
public static void main(String[] args) throws Exception{
//1.获取到类的字节码文件对象
Class clazz = Class.forName("com.dream.reflect.Student");
//创建目标对象
Object target = clazz.getConstructor().newInstance();
//获取到所有被public修饰的方法
Method[] methods = clazz.getMethods();
System.out.println(Arrays.toString(methods));
//获取到所有的方法,无视权限修饰符
Method[] declaredMethods = clazz.getDeclaredMethods();
System.out.println(Arrays.toString(declaredMethods));
//获取到public 修饰的method方法
Method method = clazz.getMethod("method", String.class);
//调用method方法
method.invoke(target, "hello");
//获取到指定方法,无视权限修饰符
Method method2 = clazz.getDeclaredMethod("show");
//暴力反射
method2.setAccessible(true);
method2.invoke(target);
}
二.注解
就是java代码里的特殊标记,比如:@Override,@Test等,作用是: 让其他程序根据注解信息来决定怎么职系那个该程序
1.自定义注解
- 1.注解的本质就是接口
- 2.所有的注解都继承了java.lang.annotation.Annotation接口
- 3.注解的属性本质上就是接口中的抽象方法
-
-
bbbb使用注解,本质上是提供了一个Anno2接口的实现类对象
-
- 注解就是对程序的特殊标识,给程序看的
2.元注解
准备操作
定义一个枚举
package com.dream.enumerage;
public enum Modify {
ADD,
DELETE,
MODIFY
}
1.定义注解
package com.dream.anno;
import com.dream.enumerage.Modify;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
String name();
int age();
Modify value();
}
package com.dream.anno;
import com.dream.enumerage.Modify;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Fly {
Modify value();
}
2.使用注解
package com.dream.annoTest;
import com.dream.anno.AutoFill;
import com.dream.anno.Fly;
import com.dream.enumerage.Modify;
public class Message {
@Fly(value = Modify.ADD)
@AutoFill(value = Modify.MODIFY,name = "小明", age = 18)
public void show() {
}
}
3.注解的解析
package com.dream.Controller;
import com.dream.anno.RequestMapping;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class SpringMvc {
public static void main(String[] args) throws Exception{
//0.创建一个map集合
Map<String,Method> map = new HashMap<>();
//1.获取到UserController的字节码文件对象
Class clazz = Class.forName("com.dream.Controller.UserController");
//2.获取到无参构造并且创建一个目标对象
Object target = clazz.getConstructor().newInstance();
//3.获取到字节码文件中的所有方法
Method[] methods = clazz.getMethods();
//4.遍历所有方法,过滤出含有@RequestMapping注解的方法
Arrays.stream(methods).filter(method->method.isAnnotationPresent(RequestMapping.class)).forEach(method->{
//5.获取到方法上注解的value
RequestMapping declaredAnnotation = method.getDeclaredAnnotation(RequestMapping.class);
String value = declaredAnnotation.value();
//6.将value和对应的方法存放到一个map集合中
map.put(value,method);
});
//7.用户录入访问路径,匹配
Scanner sc = new Scanner(System.in);
while (true) {
System.out.print("请输入访问路径:");
String path = sc.next();
if ("exit".equals(path)){
break;
}
//8.获取到指定路径的方法
Method method = map.get(path);
if (method == null){
System.out.println("404,方法不存在,访问失败");
continue;
}
//9.激活方法
method.invoke(target);
}
}
}
三.动态代理
- 动态代理是对象的另一种出创建方式
- 内存中动态生成一个类,并根据该类创建对象返回
- 在不修改源码的情况下,增强方法
1.创建代理对象的步骤
(0).准备好要创建代理对象的类或者接口
(1).首先是使用Proxy.newProxyInstance()创建一个代理对象,并且将代理对象返回
- 参数一: 就是代理对象所在类的类加载器,使用类名.class.getclassLoader()
- 参数二: 就是你要为哪些接口创建代理对象,将接口的字节码文件传入,接口名.class
- 参数三: InvocationHandler是一个接口,需要创建它的匿名内部类对象,重写它的invoke方法
- invoke方法每次通过代理对象调用方法都会执行一次invoke里面的代码
public static Object newProxyInstance(
ClassLoader loader, 类加载器 当前所在类类名.class.getClassLoader()
Class<?>[] interfaces, 代理对象实现的接口,new Class[]{接口名.class}
InvocationHandler h 接口,使用匿名内部类的方式创建对象,重写invoke方法
invoke方法每次通过动态代理对象调用方法都会执行该方法
)
其中invoke方法也有三个参数
参数一:是一个object代理对象
参数二:是使用代理对象调用方法的那个方法比如sale()通过代理对象调用,此处的method代表的就是sale
参数三: 就是方法传递过来的参数
当然我们最后可以将方的返回值返回出去
(2).使用代理对象
2.使用一个基本案例剖析动态代理
-
这里我是事先创建了定义好了一个接口和接口的实现类对象,现在我需要接口中的方法在不改变java源代码的情况下为接中的方法添加一个计算方法执行耗时的功能
-
1.准备好源代码的jar包
-
2.创建动态代理的对象
package com.dream.proxyCalculateTime;
import com.itheima.proxy.Computer;
import com.itheima.proxy.ThinkPad;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyCalculateTime {
public static Computer getComputerProxy() {
Computer c = new ThinkPad();
//1.创建一个代理对象
Computer proxy = (Computer) Proxy.newProxyInstance(ProxyCalculateTime.class.getClassLoader(), new Class[]{Computer.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
//方法返回结果
Object result = method.invoke(c, args);
long end = System.currentTimeMillis();
System.out.println(method.getName() + "执行耗时:" + (end - start));
return result;
}
});
return proxy;
}
}
- 3.使用动态代理对象
四.总结
本章总结了java中的框架底层常用到的高级技术知识点,首先是总结了java中的反射,学会如何去创建一个不在的类的对象,去调用方法,给全局变量赋值等如何调用java中有权限修饰符的方法,我们称为暴力反射,其次总结了注解的知识,注解是一种标识,它可以让java框架去识别,去做一些指定的操作,创建注解必须实现两个元注解一个target,一个Retention,最后总结了java中的动态代理,它主要是为我们方法加强功能的,使用动态代理可以在不修改源文件的情况下去批量增强方法的功能。