首页 > 代码库 > 第17天(基础加强_注解_类加载器_动态代理)_学习目标版本

第17天(基础加强_注解_类加载器_动态代理)_学习目标版本

学习目标

  • 能够使用Junit进行单元测试
  • 能够说出注解的作用
  • 能够使用JDK提供的3个注解
  • 能够根据基本语法编写自定义注解实现类
  • 能够了解自定义注解解析
  • 能够了解元注解使用
  • 能够根据上课案例分析,编写模拟@Test案例
  • 能够理解动态代理原理
  • 能够使用动态代理Proxy编写代理类
  1. Junit单元测试

    1. Junit介绍

JUnit是一个Java语言的单元测试框架,简单理解为可以用于取代java的main方法。Junit属于第三方工具,一般情况下需要导入jar包,不过,多数Java开发环境已经集成了JUnit作为单元测试工具。

  1. Junit的使用

创建"MyJunit"java项目,并创建"com.itheima_00_Junit"包

技术分享

  • 编写测试类,简单理解Junit可以用于取代java的main方法
  • 在测试类方法上添加注解 @Test
  • @Test修饰的方法要求:public void 方法名() {…} ,方法名自定义建议test开头,没有参数。

    技术分享

  • 添加Eclipse中集成的Junit库,鼠标点击"@Test",使用快捷键"ctrl + 1",点击"Add Junit…"

    技术分享

    结果

    技术分享

  • 使用:选中方法右键,执行当前方法;选中类名右键,执行类中所有方法(方法必须标记@Test)

技术分享

  • 常用注解

    @Test,用于修饰需要执行的方法

    @Before,测试方法前执行的方法

    @After,测试方法后执行的方法

publicclass JunitDemo_1 {

    @Test

    publicvoidmyTest(){

        System.out.println("测试 test");

    }

    

    @Before

    publicvoidmyBefore(){

        System.out.println("方法前");

    }

    

    @After

    publicvoidmyAfter(){

        System.out.println("方法后");

    }

 

    /*运行结果:

     *    方法前

     *    测试 test

     *    方法后

     */

}

 

  • 常见使用错误,如果没有添加"@Test",使用"Junit Test"进行运行,将抛异常

技术分享

 

  1. 注解

    1. 注解概述

  • 什么是注解:Annotation注解,是一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次
    • 对比注释:注释是给开发人员阅读的,注解是给计算机提供相应信息的。
  • 注解的作用:
    • 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
    • 代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的。
    • 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
  •  

 

  1. JDK提供的注解

  • @Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。
    • 一般被标记位过时的方法都存在不同的缺陷:1安全问题;2新的API取代
  • @Override JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
  • @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略

    deprecation ,或略过时

    rawtypes ,忽略类型安全

    unused ,忽略不使用

    unchecked ,忽略安全检查

    null,忽略空指针

serial, 忽略序列号

    all,忽略所有

 

  • @Deprecated

//#1 方法过期

class Parent1_1{

    @Deprecated

    public void init(){

        

    }

}

技术分享

 

  • @Override 复写父类方法

//#2.1 JDK5.0 复写父类方法

class Parent1_2{

    public void init(){

        

    }

}

class Son1_2 extends Parent1_2{

    @Override

    public void init() {

    }

}

 

  • @Override 实现接口方法

//#2.2 JDK6.0 实现父接口方法

interface Parent1_3{

    public void init();

}

class Son1_3 implements Parent1_3{

    @Override

    public void init() {

        

    }

}

 

  • @SupperssWarings

//#3 抑制警告

// serial : 实现序列号接口,但没有生产序列号

@SuppressWarnings("serial")

class Parent1_4 implements java.io.Serializable{

    

    //null :空指针

    @SuppressWarnings("null")

    public void init(){

        

        //rawtypes :类型安全,没有使用泛型

        //unused : 不使用

        @SuppressWarnings({ "rawtypes", "unused" })

        List list = new ArrayList();

          

        

        String str = null;

        

        str.toString();

        

    }

    

}

 

  • 没有抑制警告

 

 

  1. 自定义注解:定义—基本语法

  • 定义注解使用关键字: @interface
    • 定义类: class
    • 定义接口:interface
    • 定义枚举:enum

// #1 定义注解

@interface MyAnno1{

    

}

  • 定义带有属性的注解

//#2 定义含有属性的注解

@interface MyAnno2{

    public String username() default "jack";

}

  • 属性格式:修饰符返回值类型属性名() [default 默认值]
    • 修饰符:默认值 public abstract ,且只能是public abstract。

      技术分享

    • 返回值类型:基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组

      技术分享

    • 属性名:自定义
    • default 默认值:可以省略
  • 完整案例

//#3 完整含属性注解

@interface MyAnno3{

    int age() default 1;

    String password();

    Class clazz();

    MyAnno2 myAnno(); // 注解

    Color color(); // 枚举

    String[] arrs();

}

enum Color{

    BLUE,RED;

}

 

  1. 自定义注解:使用

  • 使用格式:@注解类名( 属性名= 值 , 属性名 = 值 , .....)

@MyAnno_1

@MyAnno_2(username="rose")

@MyAnno_3(

        age=18 ,

        password="1234" ,

        clazz=String.class ,

        myAnno=@MyAnno_2 ,

        color = Color.RED ,

        arrs = {"itcast","itheima"}

)

publicclass MyAnnoTest_4 {

 

}

  • 注解使用的注意事项:
    • 注解可以没有属性,如果有属性需要使用小括号括住。例如:@MyAnno1或@MyAnno1()
    • 属性格式:属性名=属性值,多个属性使用逗号分隔。例如:@MyAnno2(username="rose")
    • 如果属性名为value,且当前只有一个属性,value可以省略。
    • 如果使用多个属性时,k的名称为value不能省略
    • 如果属性类型为数组,设置内容格式为:{ 1,2,3 }。例如:arrs = {"itcast","itheima"}
    • 如果属性类型为数组,值只有一个{} 可以省略的。例如:arrs = "itcast"
    • 一个对象上,注解只能使用一次,不能重复使用。

 

  1. 自定义注解:解析

  • 如果给类、方法等添加注解,如果需要获得注解上设置的数据,那么我们就必须对注解进行解析,JDK提供java.lang.reflect.AnnotatedElement接口允许在运行时通过反射获得注解。

    技术分享

  • 常用方法:
    • boolean isAnnotationPresent(Class annotationClass) 当前对象是否有注解
    • T getAnnotation(Class<T> annotationClass) 获得当前对象上指定的注解
    • Annotation[] getAnnotations() 获得当前对象及其从父类上继承的,所有的注解
    • Annotation[] getDeclaredAnnotations() 获得当前对象上所有的注解

    技术分享

 

  • 测试

publicclass MyAnnoTest_5 {

    publicstaticvoid main(String[] args) {

booleanb = MyAnnoTest_4.class.isAnnotationPresent(MyAnno_1.class);

        System.out.println(b);    //false

    }

}

当运行上面程序后,我们希望输出结果是true,但实际是false。TestAnno2类上有@MyAnno1注解,但运行后不能获得,因为每一个自定义注解,需要使用JDK提供的元注解进行修饰才可以真正的使用。

 

  1. 自定义注解:定义—元注解

  • 元注解:用于修饰注解的注解。(用于修饰自定义注解的JDK提供的注解)
  • JDK提供4种元注解:
    • @Retention 用于确定被修饰的自定义注解生命周期
      • RetentionPolicy.SOURCE 被修饰的注解只能存在源码中,字节码class没有。用途:提供给编译器使用。
      • RetentionPolicy.CLASS 被修饰的注解只能存在源码和字节码中,运行时内存中没有。用途:JVM java虚拟机使用
      • RetentionPolicy.RUNTIME 被修饰的注解存在源码、字节码、内存(运行时)。用途:取代xml配置
    • @Target 用于确定被修饰的自定义注解使用位置
      • ElementType.TYPE 修饰类、接口
      • ElementType.CONSTRUCTOR 修饰构造
      • ElementType.METHOD 修饰方法
      • ElementType.FIELD 修饰字段
    • @Documented 使用javaDoc生成 api文档时,是否包含此注解 (了解)
    • @Inherited 如果父类使用被修饰的注解,子类是否继承。(了解)

    技术分享

    技术分享

 

  • 修改注解类,在运行测试实例,输出结果为:true。

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@interface MyAnno1{

    

}

 

  1. 案例:自定义@Test

    1. 案例分析

  • 模拟Junit测试,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修改方法,且在运行时可以获得。
  • 其次编写目标类(测试类),然后给目标方法(测试方法)使用@MyTest注解
  • 最后编写测试类,使用main方法模拟Junit的右键运行。

 

 

  1. 案例实现

  • 步骤1:编写自定义注解类@MyAnno

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public@interfaceMyAnno {

 

}

  • 步骤2:编写目标类AnnotationDemo

publicclass AnnotationDemo_0 {

    @MyAnno

    publicvoid demo1(){

        System.out.println("demo1执行了...");

    }

    @MyAnno

    publicvoid demo2(){

        System.out.println("demo2执行了...");

    }

    publicvoid demo3(){

        System.out.println("demo3执行了...");

    }

}

  • 步骤3:编写测试方法

publicclass AnnotationTest_1 {

    publicstaticvoid main(String[] args) {

        try {

            //1.1 反射:获得类的字节码对象.Class

            Classclazz = AnnotationDemo_0.class;

            //1.2 获得实例对象

            Object obj = clazz.newInstance();

            

            //2 获得目标类所有的方法

            Method[] allMethod = clazz.getMethods();

            //3 遍历所有的方法

            for (Method method : allMethod) {

                //3.1 判断方法是否有MyTest注解

                booleanflag = method.isAnnotationPresent(MyAnno.class);

                if (flag) {

                    //4 如果有注解运行指定的类

                    method.invoke(obj, null);

                }

            }

        } catch (Exception e) {

            e.printStackTrace();

        }

        

        /* 输出结果:

         * demo1执行了...

         * demo2执行了...

         */

    }

}

  1. 类加载器

  • 类加载器:类加载器是负责加载类的对象。将class文件(硬盘)加载到内存生成Class对象。

    所有的类加载器都是 java.lang.ClassLoader 的子类

    技术分享

  • 使用    类.class.getClassLoader() 获得加载自己的类加载器
  • 类加载器加载机制:全盘负责委托机制

    全盘负责:A类如果要使用B类(不存在),A类加载器C必须负责加载B类。

        技术分享

    委托机制:A类加载器如果要加载资源B,必须询问父类加载是否加载。

        如果加载,将直接使用。

        如果没有机制,自己再加载。

  • 采用全盘负责委托机制保证一个class文件只会被加载一次,形成一个Class对象。
  1. 动态代理

java代理有jdk动态代理、cglib代理,这里只说下jdk动态代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包)

  1. Proxy类

原理是(歌手、经纪人做例子):

  • 建立一个公共的接口,比如:歌手public interface Singer;
  • 用具体的类实现接口,比如:周杰伦,他是歌手所以实现Singer这个类,class MySinger implements Singer,重写singer方法.
  • 建立代理类,这里也就是经纪人,他需要实现InvocationHandler接口,并重写invoke方法
  • 这样当有什么事情,要找周杰伦(具体类)唱歌的时候,就必须先到经纪人(代理类)那里处理,代理人在决定要不要与你见面(该方法要不要执行),找到经纪人方法invoke,经纪人方法invoke来找周杰伦的singer方法

 

 

动态代理:程序运行时,使用JDK提供工具类(Proxy),动态创建一个类,此类一般用于代理。

代理:你 -- 代理(增强) -- 厂商

代理类:目标类:被代理的

动态代理使用前提:必须有接口

 

Object proxyObj = Proxy.newProxyInstance(参数1,参数2,参数3);

参数1:ClassLoader,负责将动态创建类,加载到内存。当前类.class.getClassLoader();

参数2:Class[] interfaces ,代理类需要实现的所有接口(确定方法),被代理类实例.getClass().getInterfaces();

参数3:InvocationHandler, 请求处理类,代理类不具有任何功能,代理类的每一个方法执行时,调用处理类invoke方法。

voke(Object proxy ,Method ,Object[] args)

        参数1:代理实例

        参数2:当前执行的方法

        参数3:方法实际参数。

动态代理案例: 模拟Collections工具类的静态方法

技术分享

 

 

    @Test

    publicvoid test_2() {

        List<String>list = new ArrayList<String>();

        list.add("123");

        System.out.println(list.size());

        list = myProxy(list);

        list.set(0, "1");

        System.out.println(list.size());

    }

 

    publicstatic List<String> myProxy(List<String>list) {

 

        List<String>listProxy = (List) Proxy.newProxyInstance(

                ReflectTest.class.getClassLoader(), list.getClass()

                        .getInterfaces(), new MyPro(list));

        returnlistProxy;

 

publicclass MyPro implements InvocationHandler{

    private List<String>list ;

    public MyPro(List<String>list){

        this.list =list;

    }

    

    public Object invoke(Object porxy,Method method,Object[] arge) throws Exception{

        if("add".equals(method.getName()))

            thrownew UnsupportedOperationException("add NO");

        if("set".equals(method.getName()))

            thrownew UnsupportedOperationException("set NO");

        if("remove".equals(method.getName()))

            thrownew UnsupportedOperationException("remove NO");

        

        returnmethod.invoke(list, arge);

    }

}

 

@Test

    publicvoid test_2() {

        List<String>list = new ArrayList<String>();

        list.add("123");

        System.out.println(list.size());

        list = myProxy(list);

        list.set(0, "1");

        System.out.println(list.size());

    }

    publicstatic List<String> myProxy(final List<String>list) {

 

        List<String>list2= (List) Proxy.newProxyInstance(ReflectTest.class

.getClassLoader(), list.getClass().getInterfaces(),new InvocationHandler() {

    @Override

        public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {

        if ("add".equals(method.getName()))

            thrownew UnsupportedOperationException("add NO");

        if ("set".equals(method.getName()))

            thrownew UnsupportedOperationException("set NO");

        if ("remove".equals(method.getName()))

            thrownew UnsupportedOperationException("r NO");

            returnmethod.invoke(list, args);

                    }

                });

        returnlist2;

    }

第17天(基础加强_注解_类加载器_动态代理)_学习目标版本