09_反射:方法调用
目标
- 能够使用Method对象执行方法 【掌握】
路径
- 案例:调用无参无返回值的方法
- 案例:调用有参有返回值的方法
- 案例:调用私有方法
- 案例:调用静态方法
案例:调用无参无返回值的方法
@Test public void testMethod1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //获取Class对象 Class stuClass = Student.class; //因为在调用Method时,需要传递Student对象 Constructor con = stuClass.getConstructor(); Student stu = (Student)con.newInstance(); //获取public void study()方法的对象 Method method = stuClass.getMethod("study"); //使用Method对象 执行study()方法 method.invoke( stu ); }
案例:调用有参有返回值的方法
//有参有返回值 @Test public void testMethod2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //获取Class对象 Class stuClass = Student.class; //因为在调用Method时,需要传递Student对象 Constructor con = stuClass.getConstructor(); Student stu = (Student)con.newInstance(); //获取public String sayHello(String name)方法的Method对象 Method method = stuClass.getMethod("sayHello", String.class); //调用method方法 Object result = method.invoke(stu,"波波"); System.out.println(result); }
案例:调用私有方法
//私有方法 @Test public void testMethod3() throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException { //获取Class对象 Class stuClass = Student.class; //因为在调用Method时,需要传递Student对象 Constructor con = stuClass.getConstructor(); Student stu = (Student)con.newInstance(); //获取 private void eat(String name)方法的Method对象 Method method = stuClass.getDeclaredMethod("eat", String.class); //去除JVM对当前次权限的检查 method.setAccessible(true); //执行method方法 method.invoke(stu,"红烧肉"); }
案例:调用静态方法
//静态方法 @Test public void testMethod4() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { //获取Class对象 Class stuClass = Student.class; //静态方法的调用不需要对象。"类名.静态方法()" //获取public static void sleep()方法的Method对象 Method method = stuClass.getMethod("sleep"); //执行静态方法 method.invoke(null);//不需要传递对象(null就表示执行静态方法) }
小结
Method对象的使用步骤:
1、获取Class对象
2、基于Class对象,获取Method对象
//有参方法 Method method = Class对象.getMethod("方法名",参数1类型.class,参数2类型.class ...); //无参方法 Method method = class对象.getMethod("方法名");
3、使用Method对象,执行方法
//调用非静态方法 method对象.invoke(实例对象,方法中需要的实参) //调用静态方法 method对象.invoke(null,方法中需要的实参)
反射的作用案例演示
- 作用
反射是框架的灵魂!框架的底层一定会用到反射技术。 - 需求:要把猫的睡觉方法 变成 狗的吃饭方法
- 效果:使用反射+Properties完成配置文件。把需要修改的灵活的内容写在配置文件中,代码不需要做任何的改动。
- 案例演示
public class Dog { public void eat(){ System.out.println("狗爱吃肉"); } public void sleep(){ System.out.println("狗睡觉流口水"); } } public class Cat { public void eat(){ System.out.println("猫爱吃鱼"); } public void sleep(){ System.out.println("猫睡觉打呼噜"); } } public class Demo { public static void main(String[] args) throws Exception{ //不使用反射 //需求: 要把猫的睡觉方法 变成 狗的吃饭方法 //Dog d = new Dog(); //d.eat(); //使用反射 //properties Properties pro = new Properties(); //load():可以把文件中的键值对读取到集合中 FileReader fr = new FileReader("day21\\aaa.txt"); pro.load(fr); //通过键获取值 String cn = pro.getProperty("className"); String mn = pro.getProperty("methodName"); //获取字节码对象 Class c = Class.forName(cn); //获取空参构造 Constructor con = c.getConstructor(); //执行构造方法 Object o = con.newInstance(); //获取方法 Method m = c.getMethod(mn); //执行方法 m.invoke(o); } } 配置文件: className=com.itheima_05.Cat methodName=sleep
10_注解:概述
目标
- 能够理解注解在程序中的作用 【了解】
路径
- 什么是注解
- 注解的作用
注解
什么是注解?
注解(Annotation)也称为元数据,是一种代码级别的说明
注解是JDK1.5版本引入的一个特性,和类、接口是在同一个层次
注解可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明
注解:就是具有特殊含义的标记(注解是给机器阅读的)
注解的作用
注解就是在代码里添加一些特殊标志,这些标志可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行操作
注解的作用:
- 编译检查:
- @Override:用来修饰方法声明。
- 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败
- 代码分析:
- 通过代码里标识的注解对代码进行分析
- 框架的配置( 框架 = 代码 + 配置 )
- 具体使用请关注框架课程的内容的学习(注解去配置数据)
- 生成帮助文档:
- @author:用来标识作者姓名
- @version:用于标识对象的版本号,适用范围:文件、类、方法
- 使用@author和@version注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标记在文档中
使用过的注解:
- @Override
- 用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败
- @Test
- Junit测试注解
- @FunctionalInterface //函数式接口
- 小结
注解:在代码中添加的标记,JVM看到注解标记后会执行一些操作
注解的作用:
- 编译检查
- 运行时代码分析
- 生成文档
11_注解:自定义注解
目标
- 能够熟悉自定义注解的格式 【会用】
路径
- 注解的定义格式
- 定义带有属性的注解
注解的定义格式
定义注解使用关键字:@interface
public @interface 注解名{ //内容 }
注解本质上就是一个接口。所有注解都会继承一个接口:Annotation
public interface 自定义注解名 extends java.lang.annotation.Annotation {}
带有属性的注解
- 属性的格式
- 格式1:数据类型 属性名(); //无默认值的属性
- 格式2:数据类型 属性名() default 默认值; //有默认值的属性
- 属性定义示例
public @interface Student { String name(); // 姓名 int age() default 18; // 年龄 String gender() default "男"; // 性别 } // 该注解就有了三个属性:name,age,gender
小结
注解的定义格式:
public @interface 注解名 { //属性格式 : 数据类型 属性名() String 属性名(); int 属性名() default 默认值; } //注意:属性的类型只能是8种基本数据类型、String、Class、枚举、注解、(以及一维数组形式)
12_注解:注解的使用
目标
- 能够掌握注解的基本使用 【会用】
路径
- 注解的使用格式
- 案例代码
- 特殊属性value
注解的使用格式
格式:
//无属性注解 @注解名 例:@Test //有属性注解 @注解名(属性=值,属性=值)
注解可以在类上,成员变量上,构造方法上,方法上,参数上…
有默认值的属性,可以不用进行赋值。
案例代码
将Book注解使用起来
public @interface Book { String name(); double price() default 100.0; String[] author(); }
建立一个BookStore的类,在类中定义成员变量,构造方法,成员方法
@Book(name = "西游记", author = {"吴承恩", "张三"}) public class BookStore { @Book(name = "三国", price = 10, author = {"罗贯中"}) private String book; @Book(name = "三国", price = 10, author = {"罗贯中"}) public BookStore() { } @Book(name = "三国", price = 10, author = {"罗贯中"}) public void test() { } }
特殊属性value
如果注解中只有一个属性要赋值,而且名字是value,可以将value给省略,可以直接给值
@interface A{ String value(); } @interface B{ String value(); String name(); } @interface C{ String value(); int price() default 100; //有默认值 } //测试类 public class Demo { @A("值") //当自定义注解中仅有一个value属性时,可以省略value属性名 public void test1() { } @B(value = "值",name="aa") //当自定义注解中除了value属性外,还有其它属性,value不能省略 public void test2() { } @C("值")//自定义注解中除了value属性外,还有其它带有默认值的属性,value可以省略 public void test3() { } }
小结
自定义注解:
public @interface Book{ //属性 String name(); double price() default 100; }
在程序代码中使用自定义注解:
//注解可以应用在:类、方法、变量、构造方法 @Book(name="属性值") //price属性使用默认值 public class BookStore{ @Book(name="属性值",price="新的值") //新的price值,会覆盖默认值 public void method(){ } }
1.如果自定义注解中的属性不给默认值,那么使用的时候必须赋值,如果有默认值,使用的时候可以不给值 2.使用注解:@注解名(属性名=属性值,属性名=属性值,....) 3.如果注解中没有属性,使用的时候@注解名 4.如果属性是数组,那么赋值方式: @注解名(属性名=属性值,属性名=属性值,属性名={属性值,属性值,..}....) 5.如果属性是数组类型,并且使用的时候只给一个值,那么可以省略大括号 6.如果属性只有一个并且属性名是value,那么给value赋值的时候可以不写value 7.如果含有多个属性,并且没有默认值,那么给value赋值的时候必须书写value 8.如果含有多个属性,并且具有默认值,那么给value赋值的时候可以不写value******************** 9.同一个注解不能同时修饰同一个方法或者同一个类 10.不同注解可以同时修饰同一个方法或者同一个类
13_注解:元注解
目标
- 熟悉Target和Retention两个元注解的作用 【会用】
路径
- 元注解的作用
- 常用的元注解
- 元注解的使用
元注解的作用
默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方
如果要限制自定义注解的使用位置怎么办?那就要学习一个新的知识点:元注解
结论:元注解是用来约束自定义注解的使用范围、生命周期
常用的元注解
常用元注解:@Target、@Retention
@Target
- 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用
- @Target可选的参数值在枚举类ElemenetType中包括:
- TYPE: 用在类,接口上
- FIELD:用在成员变量上
- METHOD: 用在方法上
- PARAMETER:用在参数上
- CONSTRUCTOR:用在构造方法上
- LOCAL_VARIABLE:用在局部变量上
- @Retention
- 作用:定义该注解的生命周期(有效范围)。
- @Retention可选的参数值在枚举类型RetentionPolicy中包括:
- SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了
- 使用场景:针对一些检查性的操作,比如:@Override ,就使用SOURCE注解
- CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值
- 使用场景:在编译时进行一些预处理操作,比如:生成一些辅助代码,就用CLASS注解
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解
使用场景:要在运行时去动态获取注解信息,那只能用 RUNTIME 注解
元注解的使用
@Target({ElementType.METHOD,ElementType.TYPE}) //元注解 @interface Stu{ String name(); } //类 @Stu(name="jack") //成功 public class AnnotationDemo02 { // 成员变量 @Stu(name = "lily") // 编译失败 private String gender; // 成员方法 @Stu(name="rose") //成功 public void test(){ } // 构造方法 @Stu(name="lucy") // 编译失败 public AnnotationDemo02(){} }
小结
元注解的作用:
- 限定自定义注解的使用范围、生命周期
- 限制使用范围:@Target
- 限定生命周期:@Retention
14_注解:注解解析
目标
- 能够使用反射的技术完成注解数据的解析 【会用】
路径
- 获取注解数据的原理
- 案例代码
获取注解数据的原理
想要对注解中的数据进行解析,需要借助:AnnotatedElement接口
- Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口
AnnotatedElement 是一个接口,定义了解析注解的方法:
1. boolean isAnnotationPresent(Class<Annotation> annotationClass) 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false public class BookStore{ @Book(name="书名") public void buy() // Method对象 判断该对象上是否使用了@Book注解 { // boolean flag = Method对象.isAnnotationPresent(Book.class) } } 2. T getAnnotation(Class<T> annotationClass) 根据注解类型获得对应注解对象 // 获取对象上的自定义注解 // Book bookAnno = Method对象.getAnnotation(Book.class); // bookAnno.属性 //获取注解中属性的值
Class,Constructor,Method,Field都会实现AnnotatedElement 接口
- 解析类型上的注解:借助字节码对象(Class对象)
- 解析构造方法上的注解:借助构造器对象(Constructor对象)
- 解析方法上的注解:借助方法对象(Method对象)
- 解析字段上的注解:借助字段对象(Field对象)
注解解析的步骤:(注解是书写在:类、方法、变量上)
1、利用反射技术获取注解作用的对象:类、方法、变量、构造方法
2、判断对象上是否有自定义注解存在
3、有:获取对象上的自定义注解
4、使用获取到的自定义注解对象,拿到注解中的属性值
注意:注解解析必须保证自定义注解生命周期在RUNTIME(程序运行中)
案例代码
需求如下:
- 定义注解Book,要求如下:
- 注意:注解解析必须保证自定义注解生命周期在RUNTIME(程序运行中)
案例代码
需求如下:
- 定义注解Book,要求如下:
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义TestAnnotation测试类获取Book注解上的数据
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.METHOD,ElementType.TYPE}) //使用范围:方法、类 @Retention(RetentionPolicy.RUNTIME) //保证注解在程序执行时有效(适用于注解解析) public @interface Book { String value(); double price() default 100; String[] authors(); } //类 public class BookStore { @Book(value = "Java入门", authors = {"张老师", "毕老师"}) public void buy() { System.out.println("购书....."); } } //对注解中的数据进行解析 public class TestBookStore { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //反射套路:1、Class 2、构造器 3、Method Class<BookStore> bookStoreClass = BookStore.class; //获取构造器 Constructor<BookStore> con = bookStoreClass.getConstructor(); BookStore bookStore = con.newInstance();//实例化对象 //获取Method Method method = bookStoreClass.getMethod("buy"); //解析注解的步骤 if(method.isAnnotationPresent(Book.class)){ //获取Method对象上的Book注解 Book bookAnno = method.getAnnotation(Book.class); //获取注解上的数据 String bookName = bookAnno.value(); double bookPrice = bookAnno.price(); String[] bookAuthors = bookAnno.authors(); System.out.println("书名:"+bookName); System.out.println("价格:"+bookPrice); System.out.println("作者:"+ Arrays.toString(bookAuthors)); } } }
小结
注解解析的步骤:
- 利用反射技术获取相关的对象:类、构造器、方法、变量
- 使用方法getAnnotation,获取自定义注解对象
- 使用注解对象,分别获取注解中的属性值
注解扩展小示例:
public class Student{ private String name;//姓名 private int age;//年龄 private String gender;//性别 private double score;//成绩 private String id;//学号 }
数据: 张三,20,男,90,it1001
利用注解+反射,给Student类中的成员变量赋值
//自定义注解 public @interface Entity{ String value; }
//Student类 public class Student{ @Entity(value="张三") private String name;//姓名 @Entiry(value="20") private int age;//年龄 private String gender;//性别 private double score;//成绩 private String id;//学号 }
15_注解:综合案例
目标
- 熟悉反射结合注解的使用 【会用】
路径
- 案例需求
- 案例分析
- 案例代码
需求
需求:模拟Junit测试的@Test
案例分析
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行
案例代码
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //自定义注解 @Target(ElementType.METHOD)//仅能应用在方法上 @Retention(RetentionPolicy.RUNTIME)//生命周期 :运行时 public @interface MyTest { //无属性注解 } public class TestAnnotationParse { //方法1 @MyTest public void method1(){ System.out.println("我是方法1"); } @MyTest public void method3(){ System.out.println("我是方法3"); } public void method2(){ System.out.println("我是方法2"); } } public class Test { public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException { //获取Class对象 Class<TestAnnotationParse> testAnnotationParseClass = TestAnnotationParse.class; //获取Class对象中,所有的Method方法 Method[] methods = testAnnotationParseClass.getMethods(); //遍历数组 for (int i = 0; i < methods.length; i++) { //获取每一个Method对象 Method method = methods[i]; //判断Method对象上,是否存在@MyTest if(method.isAnnotationPresent(MyTest.class)){ method.invoke(testAnnotationParseClass.newInstance()); } } } }
小结
注解解析的步骤:
- 获取Class对象 //类名.class、对象名.class、Class.forName(“…”)
- 基于Class对象,来获取:构造器/成员方法/成员变量 //getConstructor() getMethod() getField()
- 基于构造器/成员方法/成员变量,判断是否存在自定义注解 // isAnnotationPresent(注解.class)
存在:获取自定义注解对象 // 注解 对象 = 构造器/方法/变量/类 .getAnnotation(注解.class)
基于自定注解对象,获取其属性值 // 注解对象.属性()
16代理模式:动态代理_准备案例、提出问题
- 需求:模拟某企业用户管理业务,需包含用户登录,用户删除,用户查询功能,并要统计每个功能的耗时
- 分析:
- 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
- 定义一个实现类UserServiceImpl实现UserService,并完成相关功能,且统计每个功能的耗时。
- 定义测试类,创建实现类对象,调用方法。
- 代码实现:
package com.itheima.sh.web; public interface UserService { void login(); void delete(); void query(); } package com.itheima.sh.web; public class UserServiceImpl implements UserService{ @Override public void login() { try { long start = System.currentTimeMillis(); System.out.println("登录"); Thread.sleep(3000); long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void delete() { try { long start = System.currentTimeMillis(); System.out.println("删除"); Thread.sleep(4000); long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void query() { try { long start = System.currentTimeMillis(); System.out.println("查询"); Thread.sleep(5000); long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start)); } catch (InterruptedException e) { e.printStackTrace(); } } } package com.itheima.sh.web; public class Test01 { public static void main(String[] args) { UserService userService = new UserServiceImpl(); userService.login(); userService.delete(); userService.query(); } }
- 本案例存在哪些问题?
业务对象的的每个方法都要进行性能统计,存在大量重复的代码。
17代理模式:使用动态代理解决问题介绍
- 概念
- 代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。
- 关键步骤
- 1.必须有接口,实现类要实现接口(代理通常是基于接口实现的)。
- 2.创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象。
- 相关API介绍
动态代理API
在java中实现动态代理,关键要使用到一个Proxy
类和一个InvocationHandler
接口
Proxy类
java.lang.reflect.Proxy
:是 Java 动态代理机制的主类(父类),它提供了用于创建动态代理类和实例的静态方法
public static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces, InvocationHandler handle ) **解释说明: - 返回值: 该方法返回就是动态生成的代理对象 - 参数列表说明: ClassLoader loader :定义代理类的类加载器 Class<?>[] interfaces :代理类要实现的接口列表,要求与被代理类的接口一样 InvocationHandler handle :就是具体实现代理逻辑的接口 //参数的应用: ClassLoader loader //对象.getClass().getClassLoader() //目标对象通过getClass方法获取类的所有信息后,调用getClassLoader()方法来获取类加载器 /*获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要*/ Class<?>[] interfaces //对象.getClass().getInterfaces() //获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法 InvocationHandler //用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类方法的处理以及访问
InvocationHandler接口
java.lang.reflect.InvocationHandler
是代理对象的实际处理代理逻辑的接口,具体代理逻辑在其 invoke 方法中实现
public Object invoke(Object proxy, Method method, Object[] args) **解释说明: - 返回值:方法被代理后执行的结果 - 参数列表说明: Object proxy : 就是代理对象(通常不使用) Method method : 代理对象调用的方法 Object[] args : 被代理方法中的参数 (因为参数个数不定,所以用一个对象数组来表示) 如果方法不使用参数,则为 null //所有代理对象调用的方法,执行是都会经过invoke //因此如果要对某个方法进行代理增强,就可以在这个invoke方法中进行定义
18代理模式:使用动态代理解决问题代码实现
package com.itheima.sh.web; public interface UserService { void login(); void delete(); void query(); } package com.itheima.sh.web; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test02 { public static void main(String[] args) { ClassLoader loader = UserService.class.getClassLoader(); Class[] interfaces = {UserService.class}; InvocationHandler h = new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Exception{ String name = method.getName(); long start = System.currentTimeMillis(); if("login".equals(name)){ //登录 System.out.println("登录"); Thread.sleep(3000); }else if("delete".equals(name)){ //删除 System.out.println("删除"); Thread.sleep(4000); }else if("query".equals(name)){ //查询 System.out.println("查询"); Thread.sleep(5000); } long end = System.currentTimeMillis(); System.out.println(name+"耗时:"+(end-start)); return null; } }; UserService proxyUserServiceImpl = (UserService) Proxy.newProxyInstance(loader, interfaces, h); proxyUserServiceImpl.query(); proxyUserServiceImpl.login(); proxyUserServiceImpl.delete(); } }
19代理模式:使用动态代理解决问题代码实现
- 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。
- 可以为被代理对象的所有方法做代理。
- 可以在不改变方法源码的情况下,实现对方法功能的增强
- 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。