一、反射
什么是反射
反射就是:以编程的方式获取类中的各种成分(成员变量、方法、构造器等)
反射的特点
使用反射创建对象,无视修饰符,功能更强大灵活, 代码更复杂
反射的作用
可以得到一个类的全部成分然后操作
可以破坏封装性
更重要的用途是:适合做java的开发框架,基本上主流框架都会基于反射设计一些通用技术功能。
为了更好的看懂框架
获取类
三种方式获取到的是同一个Class对象
1、Class c1 = 类名.class
2、Object提供的方法: public Class getClass(),对象.getClass()
3、调用Class提供方法:public static Class forName(String package)
方式一 Class stu1 = Student.class; 方式二 Student student = new Student(); Class stu2 = student.getClass(); 方式三 //类全名 全限定类名 【包名+类名】 Class stu3 = Class.forName("com.demo02_反射.demo01_获取class对象.Student"); System.out.println(stu1 == stu2); //true System.out.println(stu3 == stu2); //true
获取类的构造器
Class提供了,从类中获取构造器的方法
获取到构造器的作用:依然是初始化一个对象返回。
如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象
一般获取到类的成员对象都设置暴力反射
public static void main(String[] args) throws Exception { Class student = Class.forName("com.demo02_反射.demo02_获取构造器.Student"); Constructor con1 = student.getDeclaredConstructor(); //获取private无参构造器 con1.setAccessible(true); //最好设置暴力反射 Object oj1 = con1.newInstance(); //不设置暴力反射无法用private构造器new对象 System.out.println(oj1); Constructor con2 = student.getConstructor(String.class, int.class); //public满参构造器 con2.setAccessible(true); Student obj2 = (Student) con2.newInstance("张三", 100); System.out.println(obj2); }
获取类的成员变量
Class提供了从类中获取成员变量的方法。
获取到成员变量的作用:依然是赋值、取值。
public static void main(String[] args) throws Exception { Class student = Class.forName("com.demo02_反射.demo03_成员变量.Student"); Constructor con = student.getDeclaredConstructor(); con.setAccessible(true); Object stu = con.newInstance(); Field name = student.getDeclaredField("name"); name.setAccessible(true); name.set(stu, "张三"); Object getName = name.get(stu); //stu为类的对象, new一个对象也行 System.out.println(getName); }
获取类的成员方法
Class提供了从类中获取成员方法的API。
获取到成员方法的作用:依然是执行该方法。
public static void main(String[] args) throws Exception { Class student = Class.forName("com.demo02_反射.demo04_方法.Student"); Constructor con = student.getDeclaredConstructor(); con.setAccessible(true); Object stu = con.newInstance(); Method method1 = student.getDeclaredMethod("setName", String.class); method1.setAccessible(true); method1.invoke(stu, "张三"); Method method2 = student.getDeclaredMethod("getName"); method2.setAccessible(true); Object invoke1 = method2.invoke(stu); System.out.println(invoke1); }
综合案例
public static void main(String[] args) throws Exception {
Student s1 = new Student("张三", 23, 6668, "广州");
Teacher t1 = new Teacher("0001", "Mylo", "男", 234567);
Dog d1 = new Dog("小哈", "哈士奇", "黑白");
ArrayList list = new ArrayList();
Collections.addAll(list, s1, t1, d1);
// 0.创建流
PrintWriter pw = new PrintWriter("day14_junit-reflection-proxy-anno\\file\\objectInfo.txt");
for (Object o : list) {
// 1.先获取当前对象的class对象
Class clazz = o.getClass();
String[] className = clazz.getName().split("\\.");
pw.println("================" + className[className.length - 1] + "======================");
// 2.获取所有的字段
Field[] fields = clazz.getDeclaredFields();
// 3. 遍历所有的字段,获取对应字段的值出来
for (Field field : fields) {
field.setAccessible(true);
Object value = field.get(o);
pw.println(field.getName() + "=" + value);
}
}
pw.close();
}
二、注解【了解】
就是java代码里的特殊标记 例如:@Override @Test
java程序中的类上、构造器上、方法上、成员变量上、参数等都可以用注解进行标记
注解的作用
对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定。
例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行。
自定义注解
public @interface 注解名称 {
public 属性类型 属性名() default 默认值 ;
}public class Demo11 {
@MyAnno2(name = "水浒传", price = 9.9)
public static void main(String[] args) {
}
}
自定义注解的属性类型
基本数据类型
String
Class
注解
枚举
及以上类型的 一维数组
注解特殊属性value
value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写!!
但是如果有多个属性, 且多个属性没有默认值,那么value名称是不能省略的
元注解
修饰注解的注解
元注解有两个:
@Target: 约束自定义注解只能在哪些地方使用,
@Retention:申明注解的生命周期
@Target中可使用的值定义在ElementType枚举类中,常用值如下
- TYPE,类,接口
- FIELD, 成员变量
- METHOD, 成员方法
- PARAMETER, 方法参数
- CONSTRUCTOR, 构造器
- LOCAL_VARIABLE, 局部变量
Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下
- SOURCE: 注解只作用在源码阶段,生成的字节码文件中不存在
- CLASS: 注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值.
- RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段(开发常用)
SOURCE(源代码) CLASS(字节码) RUNTIME(运行时)
xxx.java -> javac编译 -> xxx.class -> java运行 -> 程序运行
注解的解析
注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。
与注解解析相关的接口
Annotation: 注解的顶级接口,注解都是Annotation类型的对象
AnnotatedElement:该接口定义了与注解解析相关的解析方法
所有的类成分Class, Method , Field , Constructor,都实现了AnnotatedElement接口他们都拥有解
析注解的能力
注解解析的步骤
1. 先获取class对象
2. 获取注解所在的成分【方法、字段 、 类..】
3.判断当前成分上是否有指定注解
isAnnotationPresent(注解类型)
4. 获取这个注解
getDeclaredAnnotation(注解类型)
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnno { String value(); double price(); String[] authors(); }@MyAnno(value = "《母猪的产后护理》", price = 250.0, authors = {"666", "777"}) public class BookStore { @MyAnno(value = "《母猪的产后护理2.0》", price = 2500.0, authors = {"666", "777"}) public void show() { } }public static void main(String[] args) throws Exception { Class<?> book = Class.forName("com.itheima.demo04_注解练习.BookStore"); if (book.isAnnotationPresent(MyAnno.class)) { MyAnno annotation = book.getDeclaredAnnotation(MyAnno.class); String value = annotation.value(); double price = annotation.price(); String[] authors = annotation.authors(); System.out.println(value); System.out.println(price); System.out.println(Arrays.toString(authors)); } Method show = book.getDeclaredMethod("show"); if (show.isAnnotationPresent(MyAnno.class)) { MyAnno method = show.getDeclaredAnnotation(MyAnno.class); System.out.println(method.value()); System.out.println(method.price()); System.out.println(Arrays.toString(method.authors())); } }
解析注解的技巧
注解在哪个成分上,我们就先拿哪个成分对象。
比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
三、动态代理
什么是代理
代理思想就是被代理者没有能力,或者不愿意去完成某件事情,需要找个人(代理)代替自己去完
成这件事。
动态代理的作用
动态代理主要是对被代理对象的行为进行代理。 对功能进行增强。
动态代理的开发步骤
- 必须定义接口,里面定义一些行为,用来约束被代理对象和代理对象都要完成的事情。
- 定义一个实现类实现接口,这个实现类的对象代表被代理的对象。
- 定义一个测试类,在里面创建被代理对象,然后为其创建一个代理对象返回。(重点)
- 代理对象中,需要模拟收首付款,真正触发被代理对象的行为,然后接收尾款操作。
- 通过返回的代理对象进行方法的调用,观察动态代理的执行流程。
Java中代理的代表类是:java.lang.reflect.Proxy,它提供了一个静态方法,用于为被代理对象,产
生一个代理对象返回
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
为被代理对象返回一个代理对象。
参数一:类加载器 加载代理类,产生代理对象。,。
参数二:真实业务对象的接口。(被代理的方法交给代理对象)
参数三:代理的核心处理程序。
通过代理对象调用方法,执行流程是什么样的?
先走向代理
代理中可以真正触发被代理对象的方法执行。
回到代理中,由代理负责返回结果给调用者。
public interface kun { void sing(); void dance(); void rap(); int getMax(int a, int b); }public class kunKun implements kun { @Override public void sing() { System.out.println("giegie在sing"); } @Override public void dance() { System.out.println("giegie在dance"); } @Override public void rap() { System.out.println("giegie在rap"); } @Override public int getMax(int a, int b) { return Math.max(a, b); } }public class proxyKun { public static void main(String[] args) { kunKun kunKun = new kunKun(); //被代理对象 //代理对象 强转 kun kun = (kun) Proxy.newProxyInstance( proxyKun.class.getClassLoader(), //一般是当前类 kunKun.getClass().getInterfaces(), //一般是被代理对象 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* proxy => 代理对象 --> kun 不能用 method => 就是代理对象当前执行的方法 args => 执行方法的参数 */ System.out.println("giegie要表演了,给米"); Object result = method.invoke(kunKun, args); System.out.println("giegie辛苦了,给米"); return result; } }); kun.sing(); kun.dance(); kun.rap(); int max = kun.getMax(10, 20); System.out.println(max); } }
动态代理的优点
可以在不改变方法源码的情况下,实现对方法功能的增强,提高了代码的复用。
简化了编程工作、提高了开发效率,同时提高了软件系统的可扩展性。
可以为被代理对象的所有方法做代理。
非常的灵活,支持任意接口类型的实现类对象做代理。