注解

注解、类加载器、动态代理

1.1      注解

1.1.1 注解概念和作用

  1. 什么是注解

Annotation注解是类的组成部分,给类携带一些额外的信息,是一种代码级别的说明,是JDK1.5之后的新特性

注释是给开发人员阅读的,注解是给程序提供相应信息的

  1. 注解的作用

2.1编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override

2.2编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容

2.3代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的

1.1.2 内置注解的使用

  1. 三个基本的Annotation:

    @Override: 限定重写父类方法, 该注解只能用于方法

    @Deprecated: 用于表示某个程序元素(类, 方法等)已过时

    @SuppressWarnings: 抑制编译器警告

1)        rawtypes 忽略类型安全

2)        unused 忽略不使用

3)        deprecation 忽略过时

4)        unchecked 忽略安全检查

5)        null 忽略空指针

6)        all 忽略所有

案例代码:

public class Demo01 {

 

    @SuppressWarnings({ "rawtypes", "unused", "deprecation", "null", "unchecked"})

    public static void main(String[] args) {

       Student s = new Student();

       s.sleep();

      

       List list = new ArrayList<>();

       List list2 = new ArrayList<>();

       list.add("abc");

      

       new Thread().stop();

      

       String str = null;

       str.length();

    }

 

}

 

class Person {

    public void sleep() {

       System.out.println("人睡觉");

    }

}

 

class Student extends Person {

    @Override

    public void sleep() {

       System.out.println("学生学习后睡觉");

    }

   

    @Deprecated

    public void playLol() {

       System.out.println("完LOL");

    }

}

1.2      自定义注解

1.2.1 自定义注解的语法

修饰符 @interface 注解名 {

}

如:定义一个名为Student的注解

public @interface StudentAnno {

}

1.2.2 注解的使用

@注解名

如:

@StudentAnno

public class Demo02 {

}

1.2.3 3.注解的属性

1) 注解属性的作用:可以给每个注解加上多个不同的属性,用户使用注解的时候,可以传递参数给属性,让注解的功能更加强大。

2) 属性声明方式: 属性类型 属性名();

public @interface StudentAnno {

    // 给注解添加属性

    String name(); //字符串

    int age(); //整型

    String[] hobby(); //数组类型

}

   注:如果定义了属性,那么在使用注解的时候,就一定要给属性赋值

@StudentAnno(name="悟空", age=500, hobby={"吃桃子", "打妖怪"})

public class Demo02 {

}

3) 属性默认值:

String name() default "默认值";

    如果属性有默认值,则使用注解的时候,这个属性就可以不赋值。也可以重新赋值,覆盖原有的默认值。

如定义注解:

public @interface StudentAnno {

    // 3.给注解添加属性

    String name(); // 字符串

    int age() default 18; // 整型

    String[] hobby(); // 数组类型

}

使用注解时不赋值,使用默认值:

@StudentAnno(name="悟空", hobby={"吃桃子", "打妖怪"})

public class Demo02 {

 

}

4) 特殊属性名value

    如果注解中只有一个名称value的属性,那么使用注解时可以省略value=部分,只写属性值即可。无论这个value是单个元素还是数组,都可以省略。但如果还有其它属性需要赋值,则调用时value名字不能省略。

public @interface EmoplyeeAnno {

    // 注解只有一个属性,且属性名是value,给value属性赋值可以写成  @EmoplyeeAnno("张三")

    String value();

}

 

使用:

@EmoplyeeAnno("abc")

//  @EmoplyeeAnno({"abc", "cba"})

public static void main(String[] args) {

 

}

1.2.4 自定义注解练习

1) 注解名为BookAnno

2) 包含属性:String value(); //书名

3) 包含属性:double price(); //价格,默认值为 100

4) 包含属性:String[] authors(); //多位作者

案例代码:

public @interface BookAnno {

    String value();   // 书名

    double price() default 100; // 价格

    String[] authors();  // 作者

}

使用注解:

@BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})

public class Demo03 {

 

}

1.3      元注解

元注解概念:在自定义注解时使用的注解,给自定义注解添加约束,称为元注解。查看@Override的源码,即可看到元注解,任何一个注解都有元注解

1.3.1 @Target元注解

@Target作用:指明此注解用在哪个位置,如果不写默认是全部位置

@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.CONSTRUCTOR, ElementType.LOCAL_VARIABLE})

ElementType.TYPE: 用在类和接口上

ElementType.FIELD:用在成员变量上

ElementType.METHOD: 用在方法上

ElementType.PARAMETER:用在参数上

ElementType.CONSTRUCTOR:用在构造方法上

ElementType.LOCAL_VARIABLE:用在局部变量

案例代码:

@Target(ElementType.TYPE)

public @interface BookAnno {

    String value();   // 书名

    double price() default 100; // 价格

    String[] authors();  // 作者

}

自定义注解使用:

@BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})

public class Demo04 {

    // @Target(ElementType.TYPE) 明确只能用在类上面

    // @BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})

    public static void main(String[] args) {

    }

}

1.3.2 @Retention元注解

@Retention功能:限制自定义注解的作用范围

枚举值

作用

RetentionPolicy.SOURCE

注解只存在于Java源代码中,编译成字节码文件以后删除。

RetentionPolicy.CLASS

注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。

RetentionPolicy.RUNTIME

注解存在于Java源代码中、编译以后的字节码文件中、运行时的内存中,程序可以通过反射获取该注解。

如:Override就限定只能用在源代码上:

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.SOURCE)

public @interface Override {

}

注意:如果要通过反射拿到注解,这个自定义的注解必须设置为: RetentionPolicy.RUNTIME

1.4      解析注解

AnnotatedElement是一个接口,只要实现这个接口的类都可以获取注解.

Class,Constructor,Field,Method都实现这个接口

 

1.4.1   AnnotatedElement接口中的方法:

<T extends Annotation> T

getAnnotation(Class<T> annotationClass)
          通过注解类名得到注解

 Annotation[]

getAnnotations()
          返回此元素上存在的所有注解

 Annotation[]

getDeclaredAnnotations()
          返回此元素上的所有注解

 boolean

isAnnotationPresent(Class<? Extends Annotation> annotationClass)
          如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。

1.4.2 获得注解

 

public final class Class<T> implements AnnotatedElement

得到注解类对象的原则:这个注解在哪个成员上,就通过哪个对象来得到它的注解。

如:注解用在类上,就通过类对象得到它的注解。注解用在方法上,就通过方法对象得到它的注解

 

@BookAnno(value="西游记", price=20, authors={"吴承恩", "金正恩"})

public class Demo04 {

    public static void main(String[] args) throws Exception {

       // 获取类上面的注解

       boolean b = Demo04.class.isAnnotationPresent(BookAnno.class);

       if (b) {

           BookAnno anno = Demo04.class.getAnnotation(BookAnno.class);

          

           System.out.println(anno.value());

           System.out.println(anno.price());

           System.out.println(Arrays.toString(anno.authors()));

       } else {

           System.out.println("类上面没有对应注解");

       }

      

       System.out.println("------------------");

       // 获取方法上面的注解

       Method method = Demo04.class.getMethod("test");

       boolean b2 = method.isAnnotationPresent(BookAnno.class);

       if (b2) {

           BookAnno anno = method.getAnnotation(BookAnno.class);

           System.out.println(anno.value());

           System.out.println(anno.price());

           System.out.println(Arrays.toString(anno.authors()));

       } else {

           System.out.println("方法上面没有对应注解");

       }

    }

   

    @BookAnno(value="水浒传", price=10, authors={"施耐庵", "施主"})

    public static void test() {

    }

}

1.1     注解案例-模拟@Test注解

案例需求:模拟系统自带的@Test注解,自动运行带@Test注解的方法

  1) 模拟JUnit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。

  2) 其次编写目标类,然后给目标方法使用@MyTest注解,编写三个方法,其中两个加上@MyTest注解。

  3) 最后编写调用类,使用main方法调用目标类,模拟JUnit的运行,只要有@MyTest注释的方法都会运行。

案例实现:

1) 步骤1:编写自定义注解类@MyTest

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface MyTest {

 

}

2) 步骤2:编写目标类MyTestDemo

public class MyTestDemo {

    @MyTest

    public void test1(){

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

    }

   

    @MyTest

    public void test2(){

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

    }

   

    public void test3(){

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

    }

}

3) 步骤3:编写测试方法

    3.1) 得到使用注解的类对象

    3.2) 创建类的实例

    3.3) 得到类中所有公有的方法

3.4) 遍历所有的方法,如果方法上有注解,则运行此方法

public class Demo05 {

 

    public static void main(String[] args) throws Exception {

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

       Class clazz = MyTestDemo.class;

       // 1.2 获得实例对象

       Object obj = clazz.newInstance();

 

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

       Method[] allMethod = clazz.getMethods();

       // 3 遍历所有的方法

       for (Method method : allMethod) {

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

           if (method.isAnnotationPresent(MyTest.class)) {

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

              method.invoke(obj);

           }

       }

    }

}

第2章     类加载器

2.1      类加载器的作用

类加载器是负责将class字节码文件,从硬盘加载到内存中,并且生成Class类对象

 

类加载器的共同父类:java.lang.ClassLoader

2.2      三种类加载器

1) 引导类加载器:

    BootstrapClassLoader 最底层的加载器,由C和C++编写,不是Java中的类。

    作用:加载JRE最基础的Java类,如:rt.jar

        

2) 扩展类加载器:

    ExtClassLoader 由Java程序编写,是一个Java类。作用:加载JRE中的扩展类

        

3) 应用类加载器:

    AppClassLoader 由Java程序编写,是一个Java内部类。作用:加载CLASSPATH指定的jar(包括第三方的库)和自己编写的类。

  

4. 如何得到类加载器:

    类名.class.getClassLoader()

2.3      类加载器的示例

  1. 示例:创建三个测试类,每个测试类写2个测试方法。

l  预备知识:

    1) 使用System.getProperties()查看所有JVM加载的系统环境配置信息

      通过System.getProperty(常量名),指定常量的名字,得到一个常量的值。

public class Demo06 {

 

public static void main(String[] args) {

     Properties properties = System.getProperties();

     Set<String> names = properties.stringPropertyNames();

     for (String name : names) {

        System.out.println(name + "=" + System.getProperty(name));

     }

}

}

    2) 输出每种类加载器加载的内容

      其实是一些加载路径,使用分号分隔,我们可以使用split()方法拆分以后输出。

public class Demo06 {

public static void main(String[] args) {

     /*Properties properties = System.getProperties();

     Set<String> names = properties.stringPropertyNames();

     for (String name : names) {

        System.out.println(name + "=" + System.getProperty(name));

     }*/

    

     String boots = System.getProperty("sun.boot.class.path");

     String[] strings = boots.split(";");

     for (String string : strings) {

        System.out.println(string);

     }

    

     System.out.println("-------------------");

    

     String exts = System.getProperty("java.ext.dirs");

     String[] strings2 = exts.split(";");

     for (String string : strings2) {

        System.out.println(string);

     }

}

}

 

    3) 输出每种类加载器的类型,使用方法getClassLoader()。

 

  2. 引导类加载器

    ● 常量:sun.boot.class.path

1) 代码:使用String类,因为String类在java.lang包中,由引导类加载器加载。

// 测试引导类加载器

@Test

public void test2() {

   System.out.println("引导类加载器加载路径");

   String boots = System.getProperty("sun.boot.class.path");

   String[] strings = boots.split(";");

   for (String string : strings) {

       System.out.println(string);

   }

 

   ClassLoader loader = String.class.getClassLoader();

   System.out.println("引导类加载器 " + loader);

}

    2) 加载的内容:

C:\develop\Java\jdk1.7.0_72\jre\lib\resources.jar

C:\develop\Java\jdk1.7.0_72\jre\lib\rt.jar

C:\develop\Java\jdk1.7.0_72\jre\lib\sunrsasign.jar

C:\develop\Java\jdk1.7.0_72\jre\lib\jsse.jar

C:\develop\Java\jdk1.7.0_72\jre\lib\jce.jar

C:\develop\Java\jdk1.7.0_72\jre\lib\charsets.jar

C:\develop\Java\jdk1.7.0_72\jre\lib\jfr.jar

C:\develop\Java\jdk1.7.0_72\jre\classes

    3) 加载器的类型:

      因为引导类加载器不是类,所以返回null。

 

  3. 扩展类加载器

    ● 常量:java.ext.dirs

    注:如果要使用lib/ext包中的类,要在eclipse中要进行如下设置。

    在“Project Properties-->Java Build Path”中的指定JRE包的访问规则,Edit规则。

    Accessible,指定为sun/**,指定可以在eclipse中访问sun开头的包。

 

   

1) 代码:使用任何一个在ext包中的类

// 测试扩展类加载器

@Test

public void test3() {

   System.out.println("扩展类加载器加载路径");

   String boots = System.getProperty("java.ext.dirs");

   String[] strings = boots.split(";");

   for (String string : strings) {

       System.out.println(string);

   }

 

   ClassLoader loader = DNSNameService.class.getClassLoader();

   System.out.println("扩展类加载器 " + loader);

}

2) 加载的内容:

C:\develop\Java\jdk1.7.0_72\jre\lib\ext

C:\Windows\Sun\Java\lib\ext

 

    3) 加载器的类型:

sun.misc.Launcher$ExtClassLoader@153d05b

 

  4. 应用类加载器

    ● 常量:java.class.path

   

    1) 代码:自己编写的类,由应用类加载器加载

// 测试应用类加载器

@Test

public void test4() {

   System.out.println("应用类加载器加载路径");

   String boots = System.getProperty("java.class.path");

   String[] strings = boots.split(";");

   for (String string : strings) {

       System.out.println(string);

   }

 

   ClassLoader loader = Demo06.class.getClassLoader();

   System.out.println("应用类加载器 " + loader);

}

    2) 加载的内容:

C:\Users\zhangping\work\bin

C:\develop\eclipse\plugins\org.junit_4.12.0.v201504281640\junit.jar

C:\develop\eclipse\plugins\org.hamcrest.core_1.3.0.v201303031735.jar

/C:/develop/eclipse/configuration/org.eclipse.osgi/383/0/.cp/

/C:/develop/eclipse/configuration/org.eclipse.osgi/382/0/.cp/

3) 加载器的类型:

sun.misc.Launcher$AppClassLoader@7692ed85

   

1.2     三个类加载器的关系

  1. 示例:得到当前类的类加载器,并输出每个类加载器的父类加载器。

// 测试三个类加载器关系

@Test

public void test5() {

    ClassLoader loader = Demo06.class.getClassLoader();

    // sun.misc.Launcher$AppClassLoader@a53948

    System.out.println("应用类加载器" + loader);

 

    ClassLoader parent = loader.getParent();

    // sun.misc.Launcher$ExtClassLoader@153d05b

    System.out.println("上一层类加载器" + parent);

 

    ClassLoader parent2 = parent.getParent();

    // null表示引导类加载器

    System.out.println("上一层的上一层类加载器" + parent2);

}

   

● 输出结果

应用类加载器 sun.misc.Launcher$AppClassLoader@a53948

上一层类加载器sun.misc.Launcher$ExtClassLoader@153d05b

上一层的上一层类加载器null

      

  2. 结论:

    1) 应用类加载器AppClassLoader的父加载器是扩展类加载器ExtClassLoader

2) 扩展类加载器ExtClassLoader的父加载器是引导类加载器BootstrapClassLoader

3) 注:不存在父类与子类的关系,它们都是Launcher的内部类

 

1.3     类加载器中的方法

  1. URL getResource() 查找具有给定名称的资源,返回URL。

         一般使用URL类中的getPath()方法,得到具体的路径。

   

  2. InputStream getResourceAsStream(String name) 返回读取指定资源的输入流

   

  3. 示例:在src目录下创建一个bean.xml文件,使用类加载器的方法

    1) 得到文件的路径

    2) 得到文件的输入流

public class Demo07 {

    public static void main(String[] args) throws Exception {

       ClassLoader loader = Demo07.class.getClassLoader();

       // 得到类路径下指定的文件的路径

       URL url = loader.getResource("Info.txt");

       System.out.println(url.getPath());

      

       // 得到类路径下指定的文件,并且转成相应的输入流

       InputStream is = loader.getResourceAsStream("Info.txt");

       byte[] b = new byte[1024];

       int len = 0;

       while ((len = is.read(b)) != -1) {

           System.out.println(new String(b, 0, len));

       }

    }

}

 

第3章     代理模式

3.1      代理模式的分类

  代理模式分成静态代理和动态代理 

3.2      代理模式涉及到的对象

 

1. 抽象对象:是真实对象与代理对象的共同接口

2. 真实对象:对象本身,如:明星

       3. 代理对象:相当于真实对象的一个代理对象如:经纪人

1) 拥有真实对象的成员变量

2) 通过构造方法传入真实对象

3) 可以改写真实对象的方法或对真实对象的方法进行拦截

  4. 调用者:使用真实对象的消费者,如:明星的粉丝

3.3      静态代理的实现

1. 抽象对象:明星

public interface Star {

    // 唱歌方法

    public abstract void sing();

   

    // 跳舞方法

    public abstract void dance();

}

 

2. 真实对象:

public class BabyStrong implements Star {

 

    @Override

    public void sing() {

       System.out.println("王宝强: 唱天下无贼...");

    }

 

    @Override

    public void dance() {

       System.out.println("王宝强: 跳一个人的武林...");

    }

}

 

3. 代理对象:

public class StarProxy implements Star {

    private BabyStrong bs;

   

    public StarProxy(BabyStrong bs) {

       this.bs = bs;

    }

 

    @Override

    public void sing() {

       System.out.println("经纪人: 弹出场费");

       bs.sing();

       System.out.println("经纪人: 收取出场费,和宝强分成");

    }

 

    @Override

    public void dance() {

       System.out.println("经纪人: 出场费不够,谈崩了...没有下文");

    }

}

 

4. 调用者:

public class Fans {

    public static void main(String[] args) {

       // 创建真实对象

       BabyStrong bs = new BabyStrong();

       // 创建代理对象

       StarProxy sp = new StarProxy(bs);

       // 调用代理对象的方法

       sp.sing();

       sp.dance();

    }

}

3.4      静态代理模式的特点

1. 优点:

在不修改现有类的前提下,对现有类的功能进行扩展和修改

可以拦截真实对象的方法

 

2. 缺点:

一个真实对象必须对应一个代理对象,如果大量使用会导致类的急剧膨胀。

如果抽象对象中方法很多,则代理对象也要编写大量的代码。

3.5      动态代理模式

  1. 特点:

    1) 动态生成代理对象,不用手动编写代理对象

    2) 不需要编写目标对象中所有同名的方法

   

  2. 动态代理类相应的API:

1. Proxy类:

static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

²  newProxyInstance()作用:生成动态代理对象

 

²  参数说明:

loader参数:真实对象的类加载器

interfaces参数:真实对象所有实现的接口数组

h参数: 具体的代理操作,InvocationHandler是一个接口,需要传入一个实现了此接口的实现类。

Object返回值:生成的代理对象

2.InvocationHandler接口:

Object invoke(Object proxy, Method method, Object[] args)

²  invoke作用:每次调用代理对象的方法,都会走到这个方法来.在这个方法中实现对真实方法的增强或拦截

 

²  参数说明:

proxy:即方法newProxyInstance()方法返回的代理对象,该对象一般不要在invoke方法中使用,容易出现递归调用。

method: 真实对象的方法对象,会进行多次调用,每次调用method对象都不同。

args:代理对象调用方法时传递的参数

返回值:是真实对象方法的返回值

3.6      动态代理模式的开发步骤

  1. 定义接口
  2. 定义真实类
  3. 直接创建真实对象
  4. 通过Proxy类,创建代理对象
  5. 调用代理方法,其实是调用InvocationHandler接口中的invoke()方法

3.7      动态代理模式代码

Work接口:

public interface Work {

    // 搬砖方法

    public abstract void moveBricks(int count);

   

    // 敲代码方法

    public abstract void coding();

}

 

LaoWang真实对象类:

public class LaoWang implements Work {

 

    @Override

    public void moveBricks(int count) {

       System.out.println("王宝强: 一次搬" + count + "块转");

    }

 

    @Override

    public void coding() {

       System.out.println("王宝强: 疯狂的敲代码");

    }

}

 

Boss调用类:

public class Boss {

 

    public static void main(String[] args) {

       // 创建真实对象

       final LaoWang laoW = new LaoWang();

      

       // 创建动态代理,这个代理实现Work接口

       // loader:真实对象的类加载器 LaoWang.class

       // interfaces: 真实对象的类所有实现的接口数组Work

       // h: 具体怎么代理

       Work obj = (Work)Proxy.newProxyInstance(

              // 真实对象的类加载器 LaoWang.class

              LaoWang.class.getClassLoader(),

              // 真实对象的类所有实现的接口数组

//            new Class[]{Work.class}

              LaoWang.class.getInterfaces(),

              // 具体的代理操作

              new InvocationHandler() {

                  @Override

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

                     // 代理对象

//                   System.out.println(proxy);

                     // 被代理对象调用的方法

//                   System.out.println(method.getName());

                     // 被调用方法的参数

//                   System.out.println(Arrays.toString(args));

                    

                     System.out.println("先执行代理,代理类再调用真实类的方法: " + method.getName());

                     method.invoke(laoW, args);

                     return null;

                  }

              });

      

       // 使用代理对象调用方法

       obj.moveBricks(10);

       obj.coding();

    }

}

3.8      动态代理练习

需求:

有一个ArrayList<String> arrayList = new ArrayList<>();使用动态代理增强ArrayList的add方法,每个add到ArrayList中的元素都添加”代理 :”字符串.如arrayList.add(“张三”);实际添加到ArrayList中的内容为”代理 :张三”

案例代码:

public class Demo10 {

 

    @SuppressWarnings("unchecked")

    public static void main(String[] args) {

       //  真实对象

       final ArrayList<String> arrayList = new ArrayList<>();

      

       // 创建动态代理

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

              arrayList.getClass().getClassLoader(),

              arrayList.getClass().getInterfaces(),

              new InvocationHandler() {

                 

                  @Override

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

                    

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

                         args[0] = "代理 : " + args[0];

                     }

                     Object result = method.invoke(arrayList, args);

                     return result;

                  }

              });

      

       listProxy.add("张三");

       listProxy.add("李四");

       listProxy.add("王五");

      

       for (String obj : arrayList) {

           System.out.println(obj);

       }

    }

 

}

posted on 2020-05-14 14:35  fyccaishao  阅读(102)  评论(0编辑  收藏  举报

导航