今日学习目标 :
- 能够使用Junit进行单元测试
- 能够说出注解的作用
- 能够使用JDK提供的3个注解
- 能够根据基本语法编写自定义注解实现类
- 能够了解自定义注解解析
- 能够了解元注解使用
- 能够根据上课案例分析,编写模拟@Test案例
- 能够理解动态代理原理
- 能够使用动态代理Proxy编写代理类
-
- Junit介绍与注解概述 (*****了解*****)
Junit是Java语言编写单元测试框架,最直观的理解,就是取代java类中的main方法。Junit属于第三方工具,一般情况下需要导入jar包,而多数Java开发环境都集成了Junit。
Java Unit Java单元测试框架
Junit是Java语言编写单元测试框架,最直观的理解,就是取代java类中的main方法。Junit属于第三方工具,一般情况下需要导入jar包,而多数Java开发环境都集成了Junit。
Junit的使用
创建“MyJunit”java项目,并创建“cn.itcast.junit”包
- 编写测试类,简单理解Junit可以用于取代java的main方法
- 在测试类方法上添加注解 @Test
- @Test修饰的方法要求:public void 方法名() {…} ,方法名自定义建议test开头,没有参数。
- 添加Eclipse中集成的Junit库,鼠标点击“@Test”,使用快捷键“ctrl + 1”,点击“Add Junit …”
结果
- 使用:选中方法右键,执行当前方法;选中类名右键,执行类中所有方法(方法必须标记@Test)
- 常用注解
@Test,用于修饰需要执行的方法
@Before,测试方法前执行的方法
@After,测试方法后执行的方法
- import org.junit.After;
- import org.junit.Before;
- import org.junit.Test;
- public class JunitDemo {
- @Before
- public void test_Before() {
- System.out.println("Before 注解的方法 ...");
- }
- @After
- public void test_After() {
- System.out.println("After 注解的方法 ...");
- }
- @Test
- public void test_01() {
- System.out.println("测试 test01 方法 ...");
- }
- @Test
- public void test_02() {
- System.out.println("测试 test02 方法 ...");
- }
- @Test
- public void test_03() {
- System.out.println("测试 test03 方法 ...");
- }
- }
- 常见使用错误,如果没有添加“@Test”,使用“Junit Test”进行运行,将抛异常
常见的第三方注解 :
Annotation 其实就是代码里的特殊标记, 有了注解技术后,开发人员可以通过注解告诉类如何运行。在Java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。
掌握注解技术的要点:
1. 如何定义注解
2. 如何反射注解,并根据反射的注解信息,决定如何去运行类
* 注解不但可以通知编译器,在运行时通知信息给 JVM虚拟机.
对比注释:注释是给开发人员阅读的,注解是给计算机提供相应信息的。
- 什么是注解:Annotation注解,是一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次
- 注解的作用:
- 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
- 代码分析:通过代码里标识注解,对代码进行分析。 例如 : @Test
- 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
目的 : 为了替代配置文件. xml -> 替代配置文件 properities 属性集类
-
- JDK提供的注解 (*****了解*****)
JDK 5.0 之后出现注解的技术 :
JDK官方提供了三个注解
@Override: 限定重写父类方法, 该注解只能用于方法 ------ 编译时检查,不构成覆盖 报错
* JDK5.0 override注解只能用于方法覆盖 JDK6.0 该注解可以用于接口的方法实现
@Deprecated: 用于表示某个程序元素(类, 方法等)已过时 ----- 在编译器进行编译时 报出一个警告
* 为什么会有过时方法: 提供了新的方法取代之前方法、发现某个方法存在安全隐患,不建议用户再使用
@SuppressWarnings: 抑制编译器警告. ---- 通知编译器在编译时 不要报出警告信息
* 使用 all 忽略所有警告信息
rawtypes ,忽略类型安全
unused ,忽略不使用
serial, 忽略序列号
all,忽略所有
- package cn.itcast.annotation;
- import java.util.ArrayList;
- @SuppressWarnings("all")
- public class JDKAnnotationDemo01 {
- public static void main(String[] args) {
- // JDK提供的注解 (了解)
- Wolf wolf = new Wolf();
- wolf.eat();
- wolf.lovingSheep();
- // @SuppressWarnings("unused")
- int sum = 10 + 20;
- // @SuppressWarnings({"rawtypes", "unused"})
- ArrayList list = new ArrayList();
- }
- }
- interface Inter {
- void shout();
- }
- class Animal {
- public void eat() {
- System.out.println("随便吃...");
- }
- }
- class Wolf extends Animal implements Inter {
- // 被 @Override 修饰的方法必须是父类中重写的方法或是接口中的抽象方法
- @Override
- public void eat() {
- System.out.println("狼吃肉...");
- }
- @Override
- public void shout() {
- System.out.println("嗷嗷嗷...");
- }
- @Deprecated
- public void lovingSheep() {
- System.out.println("狼爱上了羊...");
- }
- }
-
- 自定义注解:定义—基本语法 (*****重点*****)
首先介绍一下注解的分类 :
元注解类型 :
@Target 与 @Retention 取值类型 :
如何自定义注解 :
所有的Annotation自动实现java.lang.annotation.Annotation接口
注解支持类型:八种基本数据类型和String、Enum、Class、annotation以及以上类型的数组)
- // 注解的作用域
- @Target({ElementType.METHOD, ElementType.TYPE, ElementType.FIELD})
- // 注解的保留策略 (生命周期)
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- @interface Description {
- // 格式 : 数据类型 属性名();
- // 合法类型 : 8中基本数据类型, String, Class, Annotation, Enumeration. (共12中类型限制)
- // 注解可以没有任何成员, 没有成员的注解被称为 `标识注解`.
- String desc();
- String author();
- int age() default 18;
- }
-
- 自定义注解:使用 (*****重点*****)
- // 自定义一个类, 使用自定义注解
- class Student {
- // 格式: @注解名(成员1=值1,成员2=值2...)
- @Description(desc="学霸兼学渣", author="Jack", age=30)
- public void introduce() {
- System.out.println("大家好, 我是学生.");
- }
- }
-
- 自定义注解 : 解析 (*****重点*****)
- // 自定义注解 : 解析
- public class CustomAnnotationDemo02 {
- public static void main(String[] args) throws Exception {
- // 需求 : 解析 Student 类中 introduce() 方法上定义的注解信息
- /*
- // 说明 : 该类必须是一个独立的 .java 文件, 类的修饰符必须为 public
- Class<?> cls1 = Class.forName("cn.itcast.annotation.Student");
- System.out.println(cls1 == cls2);
- */
- // 1. 获取 Student 类的 Class 对象
- Class<?> cls = Student.class;
- // 2. 通过反射获取到 introduce() 方法对象
- Method method = cls.getMethod("introduce", new Class[]{});
- // 3. 判断该方法上是否存在指定类型的注解信息
- if (method.isAnnotationPresent(Description.class)) {
- Description annotation = method.getAnnotation(Description.class);
- // 根据获取的 annotation 对象, 取出注解中定义的信息
- String desc = annotation.desc();
- String author = annotation.author();
- int age = annotation.age();
- System.out.println(desc + ", " + author + ", " + age);
- }
- }
- }
详细讲解 :
声明注解的属性 :
如果注解中有一个名称value的属性,那么使用注解时可以省略value=部分
特殊属性String value, String[] value;
自定义一个注解类 :
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- //@Target(value={ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
- //@Retention(value=RetentionPolicy.RUNTIME)
- @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyAnnotation {
- String value();
- }
定义一个类, 使用自定义注解 :
- // @MyAnnotation(value="Jack")
- @MyAnnotation("Jack")
- public class AnnotationClass {
- // 属性
- @MyAnnotation("itcast007")
- private String id;
- // 行为
- @MyAnnotation("Java")
- public void study() {
- System.out.println("好好学习, 天天向上!");
- }
- }
定义一个测试类, 解析注解中的信息 :
- import java.lang.reflect.Field;
- import java.lang.reflect.Method;
- public class AnnotationDemo02 {
- public static void main(String[] args) throws Exception {
- // 1. 获取 AnnotationClass 类的字节码对象
- Class<?> cls = AnnotationClass.class;
- // 2. 获取类上的注解
- if (cls.isAnnotationPresent(MyAnnotation.class)) {
- MyAnnotation anno = cls.getAnnotation(MyAnnotation.class);
- String value = anno.value();
- System.out.println("类上的注解信息为 : " + value);
- }
- // 3. 获取属性上的注解
- Field field = cls.getDeclaredField("id");
- field.setAccessible(true);
- if (field.isAnnotationPresent(MyAnnotation.class)) {
- MyAnnotation anno = field.getAnnotation(MyAnnotation.class);
- String value = anno.value();
- System.out.println("属性上的注解信息为 : " + value);
- }
- // 4. 获取方法上的注解
- Method method = cls.getMethod("study", new Class[]{});
- if (method.isAnnotationPresent(MyAnnotation.class)) {
- MyAnnotation anno = method.getAnnotation(MyAnnotation.class);
- String value = anno.value();
- System.out.println("方法上的注解信息为 : " + value);
- }
- }
- }
-
- 案例:自定义@Test (*****练习*****)
- 案例分析
- 案例:自定义@Test (*****练习*****)
- 模拟Junit测试,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 其次编写目标类(测试类),然后给目标方法(测试方法)使用@MyTest注解
- 最后编写测试类,使用main方法模拟Junit的右键运行。
注解程序开发流程
1、编写注解类
使用@interface 定义
所有注解类都默认继承 java.lang.annotation.Annotation 接口
注解支持类型:八种基本数据类型和String、Enum、Class、Annotation以及以上类型的数组)
如果注解属性提供默认值 ,使用注解时可以不设置新的值
如果注解只有一个value 属性,使用注解 省略value=
2、在一个类 应用注解类
3、通过反射技术获得注解信息
通过java.lang.reflect.AnnotatedElement 在程序运行时 获得注解信息
元注解:修饰注解的注解
@Retention 声明注解存活生命周期 --Source Class Runtime --- 开发中主要使用Runtime
@Target 声明注解可以修饰对象类型 ---- FIELD 、TYPE、METHOD
@Documented 注解可以被 生成API文档
@Inherited 注解在使用时,可以被子类继承
-
-
- 案例实现 – MyTest 实现
-
- 步骤1:编写自定义注解类@MyTest
- @Target(ElementType.METHOD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface MyTest {
- }
- 步骤2:编写目标类MyTestClass
- public class MyTestClass {
- @MyTest
- public void demo01() {
- System.out.println("测试一方法执行...");
- }
- @MyTest
- public void demo02() {
- System.out.println("测试二方法执行...");
- }
- @MyTest
- public void demo03() {
- System.out.println("测试三方法执行...");
- }
- }
- 步骤3:编写测试方法
- import java.lang.reflect.Method;
- public class MyTestDemo {
- public static void main(String[] args) throws Exception {
- // 需求 : 运行指定类中所有被 @MyTest 注解修饰的方法.
- // 1. 获取指定该类的 Class 对象
- Class<?> cls = MyTestClass.class;
- // 2. 创建一个cls表示的实例对象
- Object obj = cls.newInstance();
- // 3. 获取该类中的所有公共方法
- Method[] methods = cls.getMethods();
- // 4. 遍历方法数组, 获取每一个方法对象
- for (Method method : methods) {
- // 4.2 判断方法上是否存在 MyTest 类型的注解
- if (method.isAnnotationPresent(MyTest.class)) {
- // 4.3 如果存在, 反射执行该方法
- // method.invoke(obj, new Object[]{});
- method.invoke(obj);
- }
- }
- }
- }
补充 : 添加 @MyBefore 和 @MyAfter 注解 :
2. 动态代理 (*****了解*****)
- 定义接口和实现类, 并完成测试
- public interface ArithmeticInter {
- // 抽象方法
- void sum(int num1, int num2);
- void minus(int num1, int num2);
- void multiply(int num1, int num2);
- void divide(int num1, int num2);
- }
- public class ArithmeticImpl1 implements ArithmeticInter {
- @Override
- public void sum(int num1, int num2) {
- int result = num1 + num2;
- System.out.println(result);
- }
- @Override
- public void minus(int num1, int num2) {
- int result = num1 - num2;
- System.out.println(result);
- }
- @Override
- public void multiply(int num1, int num2) {
- int result = num1 * num2;
- System.out.println(result);
- }
- @Override
- public void divide(int num1, int num2) {
- int result = num1 / num2;
- System.out.println(result);
- }
- }
- import org.junit.Test;
- public class DynamicProxyDemo {
- @Test
- public void demo01() {
- ArithmeticInter arithmetic = new ArithmeticImpl1();
- arithmetic.sum(10, 20);
- arithmetic.minus(20, 8);
- arithmetic.multiply(5, 5);
- arithmetic.divide(20, 5);
- }
- }
- 增强一些功能
直接修改实现类的每个方法中的代码 :
- public class ArithmeticImpl1 implements ArithmeticInter {
- @Override
- public void sum(int num1, int num2) {
- System.out.println("The method sum begins with " + num1 + ", " + num2 + " ^v^");
- int result = num1 + num2;
- System.out.println(result);
- System.out.println("The method sum ends with " + num1 + ", " + num2 + " ^v^");
- }
- @Override
- public void minus(int num1, int num2) {
- System.out.println("The method minus begins with " + num1 + ", " + num2 + " ^v^");
- int result = num1 - num2;
- System.out.println(result);
- System.out.println("The method minus ends with " + num1 + ", " + num2 + " ^v^");
- }
- @Override
- public void multiply(int num1, int num2) {
- System.out.println("The method multiply begins with " + num1 + ", " + num2 + " ^v^");
- int result = num1 * num2;
- System.out.println(result);
- System.out.println("The method multiply ends with " + num1 + ", " + num2 + " ^v^");
- }
- @Override
- public void divide(int num1, int num2) {
- System.out.println("The method divide begins with " + num1 + ", " + num2 + " ^v^");
- int result = num1 / num2;
- System.out.println(result);
- System.out.println("The method divide ends with " + num1 + ", " + num2 + " ^v^");
- }
- }
- import org.junit.Test;
- public class DynamicProxyDemo {
- @Test
- public void demo01() {
- ArithmeticInter arithmetic = new ArithmeticImpl1();
- arithmetic.sum(10, 20);
- arithmetic.minus(20, 8);
- arithmetic.multiply(5, 5);
- arithmetic.divide(20, 5);
- }
- }
- 动态代理的实现思路详解
- 动态代理的代码实现 (*****练习*****)
- @Test
- public void demo02() {
- // 1. 创建了一个 `实现类` 的对象
- final ArithmeticInter arithmetic = new ArithmeticImpl2();
- // 2. 使用 `动态代理` 实现方法的调用
- // 准备参数
- // 参数1 : ClassLoader loader 运行时新创建的类, 需要被加载到内存, 只有类加载器负责将类加载到内存
- ClassLoader loader = DynamicProxyDemo.class.getClassLoader();
- // 参数2 : Class[] interfaces 需要确定代理类实现的所有接口
- Class<?>[] interfaces = ArithmeticImpl2.class.getInterfaces();
- // 参数3 : InvocationHandler 接口, 动态代理具体处理的实现代码, 一般都使用匿名内部类实现
- ArithmeticInter proxy = (ArithmeticInter) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- // 参数1 : Object proxy 代理类本身 (很少使用)
- // 参数2 : Method method 当前执行的方法
- // 参数3 : Object[] args 当前方法的实际参数
- System.out.println("The method "+method.getName()+" begins with "+Arrays.toString(args)+" ^v^");
- Object obj = method.invoke(arithmetic, args);
- System.out.println("The method "+method.getName()+" ends with "+Arrays.toString(args)+" ^v^");
- return obj;
- }
- });
- // 使用代理调用方法
- proxy.sum(10, 20);
- proxy.minus(20, 8);
- proxy.multiply(5, 5);
- proxy.divide(20, 5);
- }
- 模拟一个歌手的动态代理实现 : (*****练习*****)
- package cn.itcast.dynamicproxy2;
- public interface SuperStar {
- // 方法
- void sing(int money);
- void liveShow(int money);
- void sleep();
- }
- package cn.itcast.dynamicproxy2;
- public class LiuYan implements SuperStar {
- @Override
- public void sing(int money) {
- System.out.println("唱了一首 <<真的爱你>>");
- System.out.println("赚了: " + money + " 元");
- }
- @Override
- public void liveShow(int money) {
- System.out.println("参加了 Running Man 节目");
- System.out.println("赚了: " + money + " 元");
- }
- @Override
- public void sleep() {
- System.out.println("好累, 休息一下, 马上回来...");
- }
- }
- public class SuperStarDemo {
- @Test
- public void demo01() {
- LiuYan liuYan = new LiuYan();
- liuYan.sing(5);
- liuYan.liveShow(10);
- liuYan.sleep();
- }
- @Test
- public void demo02() {
- final LiuYan liuYan = new LiuYan();
- // 有一个代理 (proxy)
- ClassLoader loader = SuperStarDemo.class.getClassLoader();
- Class<?>[] interfaces = liuYan.getClass().getInterfaces();
- SuperStar proxy = (SuperStar) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- String methodName = method.getName();
- if ("sing".equals(methodName)) {
- int money = (int) args[0];
- if (money < 10000) {
- System.out.println("滚, 你这个穷丝, 回家撸代码去...");
- return null;
- }
- System.out.println("代理抽取了 : " + money * 0.3);
- return method.invoke(liuYan, (int)(money * 0.7));
- } else if ("liveShow".equals(methodName)) {
- int money = (int) args[0];
- if (money < 100000) {
- System.out.println("滚, 怎么还是你这个穷丝, 继续回家撸代码去...");
- return null;
- }
- System.out.println("代理抽取了 : " + money * 0.3);
- return method.invoke(liuYan, (int)(money * 0.7));
- } else {
- return method.invoke(liuYan, args);
- }
- }
- });
- proxy.sing(5);
- proxy.liveShow(10);
- proxy.sleep();
- }
- }
代理 : 对被代理对象的拦截和控制.