JavaJAVAjava

Java基础 :反射、注解、代理、线程池、依赖的学习和理解

2017-06-13  本文已影响333人  庞哈哈哈12138

高新技术的重要性

这里的高新技术指的是Java基础中的知识,比如:反射、注解、代理、线程池、依赖注入等等。
市面上的开源框架大多都是使用了这些Java基础的知识去实现的,掌握了这些Java基础的知识,能帮助我们更好的理解一些好的开源框架的实现原理。

反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
说白了就是在Android中给我们提供了一个android.jar可以调用android的api,但是还有一部分api的方法是没有暴露出来的,那么如果我们想要调用这些方法,就需要通过反射来调用。

用处:
l 在运行时判断任意一个对象所属的类;
Ø obj instanceof Object => obj.getClass().equals(Object.class)
l 在运行时构造任意一个类的对象;
l 在运行时判断任意一个类所具有的成员变量和方法;
l 在运行时调用任意一个对象的方法;
l 生成动态代理。
注意:
一般如果不是必要情况下,尽量不要使用反射,反射会影响app的性能。
获取Class对象
万事万物皆对象,每个类中都具有成员变量,构造方法,成员方法,所以可以使用Class类来表示每个类,每个类是Class类的实例对象。
Class的实例对象是各个类在内存中的那份字节码文件。
基本数据类型,String,void,数组,引用类型都存在类类型Class。
Class常见方法
l newInstance(); 创建实例对象
l getName(); 获取类的名字,带包名的
l getSimpleName(); 获取不带包名的类名
l getMethod(方法名,方法参数的类类型); 获取指定公有的方法
l getDeclaredMethod(方法名,方法参数的类类型); 获取所有的指定的方法
l getMethods(); 获取所有公有地方法
l getDelcaredMethods(); 获取类上面所有的方法
l getFields(); 获取所有公有的成员变量
l getField("成员变量的名称"); 通过成员变量的名字获取公有的成员变量
l getDeclaredField("成员变量的名称"); 通过成员变量的名字获取成员变量
l getDeclaredFields(); 获取所有的成员变量
l getConstructor("","","");
l getConstructors();
l getDeclaredConstructor(parameterTypes);
获取Class文件的三种方式
获取Class文件的三种方式:

  1. 类名.class;
  2. 对象名.getClass();
  3. Class.forName(“类的包名+类的名字”);
/**
* 获取Class的三种方式:
*       类名.class
*       对象.getClass()
*       Class.forName("类名")
*/
public class GetClass {
   public static void main(String[] args) throws Exception {
       //方式1:Class.forName("类名")
       Class clazz1= Class.forName("com.fuyuan.example.bean.People");
       System.out.println("Class.forName()方式 :" + clazz1);

       //第二种:对象.getClass()方法
       People people=new People();
       Class clazz2 = people.getClass();
       System.out.println("对象.getClass()方式 :" + clazz2 + ", "  + (clazz1 == clazz2));

       //第三种:类名.class方法
       Class clazz3 = People.class;
       System.out.println("类名.class方式 :" + clazz3 + ", "  + (clazz1 == clazz3));
   }
}

运行结果:

6.png

.]

动态加载类和静态加载类

静态加载:在编译时期加载的类,叫静态加载。
动态加载:在运行时期加载的类,叫动态加载。

简单来说:
在代码中写死的,直接new出来的类,就属于是静态加载;
运行期间通过配置信息来动态获取相关类的实例,就属于动态加载。

动态加载的优点:提高程序的可扩展性

动态加载范例
现有Excel、Word、PPT 3个类,他们都实现了Office接口,重写了Office接口的startWork方法。
现在通过动态加载的方式,使用相应的类去执行相关的操作。

接口Office,包含一个startWork方法:Office.java


public interface Office {
    public void startWork();
  }

Office的实现类Excel:


public class Excel implements Office{
    @Override
    public void startWork() {
        System.out.println("Excel start work......");
    }
}

Office的实现类Word:


public class Word implements Office{
    @Override
    public void startWork() {
        System.out.println("Word start work......");
    }
}

写一个测试类,演示动态加载的使用场景:DynamicLoad.java

/**
 * 动态加载
 */
  public class DynamicLoad {
  
    public static void main(String[] args) {
        String worker = "com.fuyuan.example.bean." + "Excel";
        startWord(worker);
    }
  
    public static void startWord(String worker) {
        try {
            // 1. 获取字节码文件
            Class clazz = Class.forName(worker);
            // 2. 获取类的对象
            Office office = (Office) clazz.newInstance();
            // 3. 调用相关方法
            office.startWork();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  }

打印类中方法的信息
Class中用于获取方法的API为:
l getMethod(方法名,方法参数的类类型); 获取指定公有的方法
l getDeclaredMethod(方法名,方法参数的类类型); 获取所有的指定的方法
l getMethods(); 获取所有公有地方法
l getDelcaredMethods(); 获取类上面所有的方法

Method的常见API:
l method.getReturnType(); 获取返回值的类类型
l method.getName(); 获取方法的名字
l method.getParameterTypes(); 获取所有参数的类类型

需求:给定一个对象,打印出这个对象身上所有的方法的返回值、方法名和参数

/**
    * 打印方法的信息,包括:  返回值  方法名  参数
    */
   public static void printMethodMessage(Object object){
      String temp="";

      //1.获取字节码文件
      Class c=object.getClass();
      //2.获取类上面的方法
      /**
       * 获取一个方法
       * 参数1:方法的名字
       * 参数2:方法参数的类类型 
       */
//    c.getMethod(name, parameterTypes);

      //获取所有的公有地方法
      Method[] methods = c.getMethods();

      //获取类上面的所有方法
//    c.getDeclaredMethod(name, parameterTypes)
//    c.getDeclaredMethods();

      for (Method method : methods) {
         //返回值的类类型
         Class<?> returnType = method.getReturnType();
         temp+=returnType.getName()+"  ";

         //获取方法的名字
         String name=method.getName();
         temp+=name+"(";

         //获取参数
         Class<?>[] parameterTypes = method.getParameterTypes();
         for (Class<?> class1 : parameterTypes) {
            String parameterName=class1.getName();
            temp+=parameterName+",";
         }
         temp+=")";

            // 打印方法信息
         System.out.println(temp);
         temp="";
      }
   }

调用此方法打印String身上的所有方法信息,运行结果:

string1.png

打印类中成员变量的信息
Class中用于获取成员变量的API为:
l getFields(); 获取所有公有的成员变量
l getField("成员变量的名称"); 通过成员变量的名字获取公有的成员变量
l getDeclaredFields (); 获取所有的成员变量(包括private的)
l getDeclaredFields("成员变量的名称"); 通过成员变量的名字获取成员变量

Field的常见API:
l getName(); 获取成员变量的名称
l getType(); 返回成员变量的类类型

需求:给定一个对象,打印出这个对象身上所有的成员变量名称和类类型

/**
    * 打印类中成员变量的信息
    */
   public static void printFieldMessage(Object obj){
      //1.获取字节码文件
      Class c1=obj.getClass();

      //2.拿到字节码文件中所有的变量
      // 获取到所有的公有的成员变量
      Field[] fields = c1.getFields();

        // 获取到指定公有的成员变量
//    c1.getField("成员变量的名称");

        // 获取到指定的成员变量
//    c1.getDeclaredField("成员变量的名称");

        //可以获取到所有的成员变量
//    c1.getDeclaredFields();

      for (Field field : fields) {
         //3.获取成员变量的名称
         String name=field.getName();
         //4.获取成员变量的类型
         Class typeClass=field.getType();
         System.out.println(typeClass+"  "+name+";");
      }
   }

调用此方法打印int身上所有的公有的成员变量的信息,运行结果:

5.png

打印类中的构造方法的信息

可变参数

l 可变参数的出现解决了一个方法接受的参数个数不固定的问题;
l 可变参数只能出现在参数列表的最后;
l ...位于变量类型和变量名之间,前后有无空格都可以;
l 调用可变参数的方法时,编译器为该可变参数隐含创建了一个数组;
l 在方法体中,可以以数组的形式访问可变参数;

可变参数使用范例:

/**
 * 可变参数
 */
public class ChangeArgs {
    public static void main(String[] args) {
        System.out.println(add(2,5));
        System.out.println(add(2,5,9,7));
    }

    /**
     * 可变参数的使用(可变参数实质就是数组的形式)
     */
    public static int add(int... args){
        int sum=0;
        for (int i=0;i<args.length;i++) {
            sum+=args[i];
        }
        return sum;
    }
}

打印构造方法的信息
Class中用于获取构造方法的API为:
l getConstructor("","",""); 获取指定公有的构造方法,参数是可变参数
l getConstructors(); 获取所有的公有构造方法
l getDeclaredConstructor(parameterTypes); 获取指定的构造方法
l getDeclaredConstructors(); 获取所有的构造方法

Constructor的常见API:
l getName(); 获取构造方法的名字
l getParameterTypes(); 获取所有参数的类类型

需求:给定一个对象,打印出这个对象身上所有的构造方法的名称和参数类型

/**
    * 打印构造方法信息
    */
   public static void printConstructorMessage(Object obj){
      String temp="";
  
      //1.获取字节码文件
      Class c1=obj.getClass();
  
      //2.获取构造方法
  
      //参数是可变参数,可以理解为数组,代表的是构造方法的参数的类类型
      //获取指定的构造方法
//    c1.getConstructor("","","");
  
      //获取所有公有的构造方法
//    c1.getConstructors();
  
      //获取指定的构造方法
//    c1.getDeclaredConstructor(parameterTypes);
  
      //获取所有构造方法
      Constructor[] constructors = c1.getDeclaredConstructors();
  
      for (Constructor constructor : constructors) {
         //获取构造方法的名称
         String name=constructor.getName();
         temp+=name+"(";
            
         //获取构造的参数的类类型
         Class[] parameterTypes = constructor.getParameterTypes();
         for (Class class1 : parameterTypes) {
            //获取构造方法的参数的类类型的名字
            String paramName=class1.getName();
            temp+=paramName+",";
         }
         temp+=")";
         System.out.println(temp);
         temp="";
      }
   }

调用此方法打印String身上所有的公有的成员变量的信息,运行结果:

4.png

方法和成员变量的反射
测试用Number类:Number.java

public class Number {
  
   private static String num="number start...";
  
   public void add(int a,int b){
      System.out.println(a+b);
   }
  
   public static void printNum(){
      System.out.println(num);
   }
}

通过反射调用某个类的方法
通过反射调用某个类的方法的步骤:
获取Class字节码
获取构造方法
通过构造方法创建对象
获取方法(可以是私有的)
方法设置Accessible为true(暴力反射)
调用invoke方法

/**
 *  通过反射调用方法
 */
  public static void reflectionMethod() throws Exception {
    // 1. 获取字节码文件
    Class clazz = Number.class;
  
    // 2. 获取类的对象
    Number number = (Number) clazz.newInstance();
  
    // 3. 获取方法
    Method method = clazz.getDeclaredMethod("add", int.class, int.class);
  
    // 4. 设置方法访问权限(暴力反射)
    method.setAccessible(true);
  
    // 5. 执行方法
    method.invoke(number, 2, 6);
  }

通过反射给某个类的成员变量赋值
通过反射给某个类的成员变量赋值的步骤:
获取Class字节码
获取构造方法
通过构造方法创建对象
获取成员变量(可以是私有的)
方法设置Accessible为true(暴力反射)
调用Field.set(对象,参数值) 方法赋值

/**
 *  通过反射为成员变量赋值
 */
  public static void reflectionField() throws Exception {
    // 1. 获取字节码文件
    Class clazz = Number.class;
  
    // 2. 获取类的对象
    Number number = (Number) clazz.newInstance();
  
    // 调用方法打印赋值前的成员变量的值
    System.out.print("修改前:");
    number.printNum();
  
    // 3. 获取成员变量
    Field field = clazz.getDeclaredField("num");
  
    // 4. 设置成员变量的访问权限(暴力反射)
    field.setAccessible(true);
  
    // 5. 为成员变量赋值
    field.set(number, "我是被修改后的Number值");
  
    // 调用方法打印赋值后的成员变量的值,看是否修改成功
    System.out.print("修改后:");
    number.printNum();
  }

方法执行结果:

3.png

注解

Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。
Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,Annotation就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。
Annotation的行为十分类似public、final这样的修饰符。每个Annotation具有一个名字和成员(成员的个数>=0)。每个Annotation的成员具有被称为name=value对的名字和值(就像javabean一样),name=value装载了Annotation的信息。也就是说注解中可以不存在成员。

使用注解的基本规则:
Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。
  
Annotation类型:
Annotation类型定义了Annotation的名字、类型、成员默认值。一个Annotation类型可以说是一个特殊的java接口,它的成员变量是受限制的,而声明Annotation类型时需要使用新语法。当我们通过java反射api访问Annotation时,返回值将是一个实现了该 annotation类型接口的对象,通过访问这个对象我们能方便的访问到其Annotation成员。

简而言之:
l 一个注解就是一个类,使用注解,就相当于创建了一个类的实例对象。
l 注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。
l Java编译器、开发工具或者其他程序可以用反射来了解你的类及各种元素上有无何种标记,通过不同的标记,就去干相应的事。
l 标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

注解是一个特殊的类,他的格式同接口,只是在接口前加了”@“

7.png

注解的分类
根据注解参数的个数,我们可以将注解分为三类:
1.标记注解:一个没有成员定义的Annotation类型被称为标记注解,@Override;
2.单值注解
3.完整注解

根据注解使用方法和用途,我们可以将Annotation分为三类:
1.系统注解
2.元注解
3.自定义注解

系统注解

@Override
@Override 用来表示实现或者重写接口或父类中的方法。
@Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时发挥威力。使用方法极其简单:在使用此annotation时只要在被修饰的方法前面加上@Override即可。
@Deprecated
@Deprecated用来标记已过时的类型或者类型成员。
@Deprecated也是一个标记注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
@SuppressWarnings
@SuppressWarnings是用来警告用户的,它用于通知java编译器禁止特定的编译警告。
@SuppressWarnings被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。

元注解

元注解就是注解的注解,它的作用就是负责注解其他注解。
Java5.0定义了4个标准的元注解类型,它们被用来提供对其它注解类型作说明。

@Target
作用:
用于描述注解的使用范围,即被描述的注解可以用在什么地方

取值(ElementType):
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

使用范例:

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * @Target用于表示注解应用的范围
 * ElementType中包含方法、接口、包、注解、类、构造方法等等
 *
 * 这里指定自定义注解TestAnnotation只能被声明在方法上和成员变量上
 */
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface TestTargetAnnotation {

}

@Retention
作用:
@Retention 用于说明注解的保留期,表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy):
l SOURCE:在源文件中有效(即源文件保留)
l CLASS:在class文件中有效(即class保留,在加载到JVM虚拟机时丢弃)
l RUNTIME:在运行时有效(即运行时保留,此时可以通过反射获得定义在某个类上的所有注解)

默认值在Class阶段
@Override在SOURCE阶段(给编译器看的)
@SuppressWarning在SOURCE阶段(给编译器看)
@Deprecated在RUNTIME阶段

使用范例:

/**
 * Created by Fuyuan on 2016/6/15.
 *
 *  注解的生命周期分为三个阶段:
 *        java源文件、class文件、内存中的字节码
 * 对应的@Retention元注解:
 *        RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
 *
 * 默认值在Class阶段
 * @Override在SOURCE阶段(给编译器看的)
 * @SuppressWarning在SOURCE阶段(给编译器看)
 * @Deprecated在RUNTIME阶段(调进内存后扫描二进制码来查看方法,所以是RUNTIME)
 *
 * 这里指定自定义注解的声明生命为运行时保留
 */
@Retention(RetentionPolicy.RUNTIME)
public @interface TestRetentionAnnotation {

}

@Documented

8.png

@Documented 注解用于生成文档的时候,带有@Documented的注解会被显示在文档中。

@Inherited
@Inherited 表示父类的注解可以被子类继承, 前提是Retention必须是RUNTIME的。

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。
如果一个使用了@Inherited修饰的注解类型被用于一个class,则这个注解将被用于该class的子类。

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

简而言之:
@Inherited表示父类的注解可以被子类继承,但是这个可被继承的前提是注解的生命周期是运行时注解,且@Inherited代表的是子类可以继承父类类级别的注解,父类的方法如果被子类重写,子类不继承父类方法上的注解。

范例:
定义一个注解,指明直接是@Inherited的:TestInheritedAnnotation.java

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * @Inherited 表示父类的注解可以被子类继承,
 */
@Target({ElementType.METHOD, ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TestInheritedAnnotation {
    String value();
}

创建一个父类,在父类的方法、抽象方法、类上都使用了自定义的@Inherited的注解:Parent.java

@TestInheritedAnnotation("I'm parent")
public abstract class Parent {

    @TestInheritedAnnotation("I'm parent method1")
    public void method1() {
        System.out.println(" Parent method1......");
    }

    @TestInheritedAnnotation("I'm parent method2")
    public void method2() {
        System.out.println("Parent method2......");
    }

    @TestInheritedAnnotation("I'm parent absMethod")
    public abstract void absMethod();
}

定义一个子类,继承Parent,实现其抽象方法absMethod,重写其普通方法method1:Child.java

public class Child extends Parent{
    @Override
    public void method1() {
        super.method1();
    }

    @Override
    public void absMethod() {
        System.out.println("子类实现抽象父类的抽象方法absMethod");
    }
}

写一个测试方法,判断Child上面的各个方法和类上是否有我们的自定义注解@ TestInheritedAnnotation:

/**
 *  测试@Inherited注解
 */
private static void testInheritedAnnotation() throws NoSuchMethodException {
    // 1. 获取子类的class
    Class clazz=Child.class;

    //2. 获取子类重写的父类抽象方法
    Method method = clazz.getMethod("absMethod");
    // 3. 判断子类实现的父类的抽象方法是否继承了注解
    if(method.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation inheritedAnnotation = method.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子类实现的抽象方法继承到父类抽象方法中的Annotation,其信息如下:");
        System.out.println(inheritedAnnotation.value());
    }else{
        System.out.println("子类实现的抽象方法没有继承到父类抽象方法中的Annotation");
    }

    // 4. 判断子类重写父类的方法
    Method methodOverride = clazz.getMethod("method1");
    if(methodOverride.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation inheritedAnnotation = methodOverride.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子类method1方法继承到父类method1方法中的Annotation,其信息如下:");
        System.out.println(inheritedAnnotation.value());
    }else{
        System.out.println("子类method1方法没有继承到父类method1方法中的Annotation");
    }

    // 5. 判断子类不重写父类的方法
    Method methodParent = clazz.getMethod("method2");
    if(methodParent.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation inheritedAnnotation = methodParent.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子类method2方法继承到父类method2方法中的Annotation,其信息如下:");
        System.out.println(inheritedAnnotation.value());
    }else{
        System.out.println("子类method2方法没有继承到父类method2方法中的Annotation");
    }

    // 6. 判断子类继承自父类的类上的注解
    if(Child.class.isAnnotationPresent(TestInheritedAnnotation.class)){
        TestInheritedAnnotation annotation = (TestInheritedAnnotation) clazz.getAnnotation(TestInheritedAnnotation.class);
        System.out.println("子类继承到父类类上Annotation,其信息如下:");
        System.out.println(annotation.value());
    }else{
        System.out.println("子类没有继承到父类类上Annotation");
    }
}

打印日志:

1.png

去掉@ TestInheritedAnnotation中的@Inherited标记,打印日志:

2.png

结论:
对于方法上的注解,加不加@Inherited没有影响;但是对于类上的注解,加了@Inherited的,父类的注解会被子类继承。
自定义注解
注解是一个特殊的类,他的格式同接口,只是在接口前加了”@“

9.png
定义注解格式:

public @interface 注解名 {定义体}

注解参数的可支持数据类型:
l 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
l String类型
l Class类型
l enum类型
l Annotation类型
l 以上所有类型的数组

注意:
l 只能用public或默认(default)这两个访问权修饰。
例如:String value();这里把方法设为defaul默认类型;
l 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组。
例如:String value();这里的参数成员就为String;  
l 如果只有一个参数成员, 方法名需要定义成value,后加小括号。

简而言之:
l 当只有一个变量的时候,方法名最好定义成value。
原因:
假如方法名定义成value,那么用的时候可以直接@注解(“value值”);
假如方法名定义的其他的,比如name(),那么用的时候必须写成key-value形式:@注解(name = “value值”)
l 当多个的时候可以随便定义。
l 注解可以有默认值。

自定义注解范例:@CustomAnnotation

**
 * 自定义注解
 *
 * 假如方法名定义成value,那么用的时候可以直接@注解(“value值”);
 * 假如方法名定义的其他的,比如name(),那么用的时候必须写成key -value形式:@注解(name = “value值”)
 */
public @interface CustomAnnotation {
    /** 假如注解中只有一个属性,建议名字定义成value */
//    String value();

    /** 为注解的属性定义一个默认值 */
    String name() default "zhangsan";

    int age();
}

使用范例:

@CustomAnnotation(age = 25)
private String custom = "hello";

注解的处理器

l <T extends Annotation> T getAnnotation(Class<T>annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。
l Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
l boolean is AnnotationPresent(Class<?extends Annotation>annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
l Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)。该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

使用注解定义网络框架
我们在发送网络请求的时候,需要知道请求是GET请求还是POST请求,请求的URL是什么。这里我们通过一个简单的自定义网络框架来看一下注解是如何被应用的。

定义注解,用于标识网络访问的请求方式:RequestMethod.java

/**
 * 网络请求方法: GET or POST
 */
@Target(ElementType.METHOD) // 限制使用位置:方法体
@Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时
@Documented // 显示在文档中
public @interface RequestMethod {

    /** 定义枚举类,限制网络请求的方法仅POST和GET这2种 */
    enum Method{GET, POST}

    /** 默认请求方式为GET请求 */
    Method value() default Method.GET;
}

定义注解,用于标识网络访问的URL地址:RequestURL.java

/**
 * 网络请求要访问的URL地址
 */
@Target(ElementType.METHOD) // 限制使用位置:方法体
@Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时
@Documented // 显示在文档中
public @interface RequestURL {

    /** 默认的访问网络的URL地址为"" */
    String value() default "";
}

写一个简单的网络框架,用于解析注解,并发送网络请求,并返回请求的结果:HttpUtil.java

/**
 * 网络请求的工具类
 */
public class HttpUtil {
    /**
     * 解析并执行网络请求
     * @param object 发起网络请求的类
     */
    public static String parseRequest(Object object) throws IOException {
        // 网络请求返回的结果
        String result = "";

        // 1. 获取Class
        Class clazz = object.getClass();

        // 2. 获取全部的公有方法
        Method[] methods = clazz.getMethods();

        // 3. 遍历所有的方法,寻找哪个方法身上有@RequestURL和@RequestMethod
        for (Method method : methods) {
            // 4. 先判断是否有@RequestURL,获取要访问的URL地址;
            // 若获取不到URL地址,不需要再往后解析了
            if (method.isAnnotationPresent(RequestURL.class)) {
                // 获取URL地址
                RequestURL annotationUrl = method.getAnnotation(RequestURL.class);
                String url = annotationUrl.value();
                // 如果URL地址为空,那么也不需要再往后解析了
                if (!TextUtils.isEmpty(url)) {
                   // 5. 判断是否有@RequestMethod,获取请求方法
                    if (method.isAnnotationPresent(RequestMethod.class)) {
                        // 获取访问的方法
                        RequestMethod annotationMethod = method.getAnnotation(RequestMethod.class);
                        RequestMethod.Method requestMethod = annotationMethod.value();
                        if (requestMethod.equals(RequestMethod.Method.GET)) {
                            // 发送GET请求
                            result = get(url);
                        } else {
                            // 发送POST请求
                            result = post(url);
                        }
                    }
                }
            }
        }
        return result;
    }

    /**
     *  发送GET请求
     * @param url 要请求的URL地址
     */
    public static String get(String url) throws IOException {
        // 借助okHttp发送网络请求
        Request request = new Request.Builder().url(url).build();
        Call call = new OkHttpClient().newCall(request);
        // 发送同步请求
        Response response = call.execute();
        return  response.body().string();
    }

    /**
     *  发送POST请求
     * @param url 要请求的URL地址
     */
    public static String post(String url) throws IOException {
        RequestBody requestBody = new FormEncodingBuilder().add("key", "value").build();
        // 借助okHttp发送网络请求
        Request request = new Request.Builder().post(requestBody).url(url).build();
        Call call = new OkHttpClient().newCall(request);
        // 发送同步请求
        Response response = call.execute();
        return  response.body().string();
    }
}

写一个测试Activity测试我们的框架是否好用:MainActivity.java

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        loadNet();
    }

    @RequestMethod(RequestMethod.Method.GET)
    @RequestURL("http://192.168.191.1:8080/testjson.json")
    public void loadNet() {
        // 开启线程访问网络
        new Thread(){
            @Override
            public void run() {
                try {
                    String request = HttpUtil.parseRequest(MainActivity.this);
                    Log.e("MainActivity", "========网络请求返回结果========:" + request);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

执行结果:

10.png

Xutils injectView实现原理
使用场景:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @ViewInject(id = R.id.button1, clickable = true)
    private Button button1;
    @ViewInject(id = R.id.button2)
    private Button button2;
    ……
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnnotateUtils.injectView(this);
    }
……
}

public static void injectViews(Object object, View sourceView){
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields){
        ViewInject viewInject = field.getAnnotation(ViewInject.class);
        if(viewInject != null){
            int viewId = viewInject.id();
            boolean clickable = viewInject.clickable();
            if(viewId != -1){
                try {
                    field.setAccessible(true);
                    field.set(object, sourceView.findViewById(viewId));
                    if(clickable == true){
                        sourceView.findViewById(viewId).setOnClickListener((View.OnClickListener) (object));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

动态代理

代理根据运行和编译时期,分为静态代理和动态代理。

如果编译时存在的话是静态代理,所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

如果运行时存在的则是动态代理,动态代理之所以称为动态,是因为代理类是在运行时由Proxy类产生的,这就大大减少了需要我们手工设计代理类的数量。
动态代理的应用场景
AOP即Aspect orientedprogram,面向切面的编程

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面
例如:
StudentService类用于处理学生信息
CourseService类用于处理课程信息
MiscService类用于处理教室信息
这三个类都有安全、事务、日志的功能,这三个功能贯穿到好多个模块中,所以,它们就是交叉业务。

交叉业务的编程问题即为面向方面的编程,AOP的目标就是要使交叉业务模块化。

所谓模块化,就是将这些交叉业务只写一份,应用到所有需要的地方,而不是每个需要的地方都写一份。这就需要使用代理技术,代理技术是实现AOP功能的核心和关键。

11.png

要为系统中的各种接口的类增加代理功能,如果全部采用静态代理方式,写成百上千的代理类,不符合实际。
JVM可以在运行期动态的生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现了一个或者多个接口,以便让JVM知道他都实现了什么方法,需要为这些方法来生成代理。所以,JVM生成的动态类只能用于具有相同接口的目标类的代理。

12.png

如何实现动态代理

步骤:

  1.  定义接口
    
  2.  定义委托类,委托类需要实现接口
    
  3.  实现InvocationHandler接口,定义委托类和代理类的桥梁
    
  4.  生成代理类Proxy.newProxyInstance();
    
  5.  调用代理类的方法
    

定义接口StudentInterface.java

public interface StudentInterface {
    public void study();
    public void play();
    public void sleep();
}

定义委托类Student,委托类需要实现接口StudentInterface:

/**
 * 委托类
 */
public class Student implements StudentInterface {

    @Override
    public void study() {
        System.out.println("==== Student ==== study ====");
    }

    @Override
    public void play() {
        System.out.println("==== Student ==== play ====");
    }

    @Override
    public void sleep() {
        System.out.println("==== Student ==== sleep ====");
    }
}

实现InvocationHandler接口,定义委托类和代理类的桥梁:StudentProxyHandler.java

/**
 * 委托类Student和代理类的桥梁
 */
public class StudentProxyHandler implements InvocationHandler {

    /** 委托类对象 */
    private Object targetObj;

    public StudentProxyHandler(Object targetObj) {
        this.targetObj = targetObj;
    }

    /**
     * @param proxy  代理类的对象
     * @param method 要执行的方法
     * @param args 要执行的方法的参数
     * @return 方法执行后的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理类可以过滤方法,必须我们控制play方法不被执行
        if (method.getName().equals("play")) {
            System.out.println("====StudentProxy 拦截了 play方法 ==========");
            return null;
        }

        // 代理类可以增强方法,必须在执行方法前打印Log信息
        System.out.println("====打印了一条log,代理类有方法被执行了 ==========");

        // 执行委托类的对应方法
        Object result = method.invoke(targetObj, args);
        return result;
    }
}

通过Proxy.newProxyInstance();生成代理类对象:

// 创建委托类对象
Student student = new Student();
// 生成代理类对象
StudentInterface proxyInstance = (StudentInterface) Proxy.newProxyInstance(StudentInterface.class.getClassLoader(),
        new Class[]{StudentInterface.class}, new StudentProxyHandler(student));

调用代理类的方法:

// 调用代理类的方法
proxyInstance.study();
proxyInstance.sleep();
proxyInstance.play();


执行结果:

13.png

线程池

线程池的优点
l 避免线程的创建和销毁带来的性能开销
l 避免大量的线程间因互相抢占系统资源导致的阻塞现象
l 能够对线程进行简单的管理并提供定时执行、间隔执行等功能

注意:
线程池在使用的时候最好搞成静态的,因为线程池比较消耗资源。

线程池的概念

Java里面线程池的顶级接口是Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。

Executors提供四种线程池:
l newCachedThreadPool
newCachedThreadPool 得到的是一个可根据需要创建新线程的线程池。
特点:
Ø 如果有缓存的线程可用,优先用缓存的;如果没现有线程可用,则创建一个新的线程加入到池中。
Ø 终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

l newSingleThreadExecutor
newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

l newFixedThreadPool
newFixedThreadPool创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

l newScheduledThreadPool
newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

线程池相关构造参数含义
/**
 * corePoolSize:
 *      线程池的核心线程数,一般情况下不管有没有任务都会一直在线程池中一直存活,
 *      只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 设置为 true 时,
 *      闲置的核心线程会存在超时机制,如果在指定时间没有新任务来时,核心线程也会被终止,
 *      而这个时间间隔由 keepAliveTime 属性指定。
 *
 * maximumPoolSize:
 *      线程池所能容纳的最大线程数,当活动的线程数达到这个值后,后续的新任务将会被阻塞。
 *
 * keepAliveTime:
 *      控制线程闲置时的超时时长,超过则终止该线程。
 *
 * unit:
 *      用于指定 keepAliveTime 参数的时间单位。
 *
 * workQueue:
 *      线程池的任务队列,通过线程池的 execute(Runnable command) 方法会将任务 Runnable 存储在队列中。
 *
 * threadFactory:
 *      线程工厂,它是一个接口,用来为线程池创建新线程的
 */
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,//核心线程数
        5, //最大线程数
        5, //线程空闲时间存活时间
        TimeUnit.SECONDS, //存活时间的单位
        new LinkedBlockingQueue<Runnable>(),   //任务队列
        Executors.defaultThreadFactory());//线程产生的工厂

线程池关闭的API

ThreadPoolExecutor 提供了两个方法,用于线程池的关闭:
l shutdown()
不会立即的终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
l shutdownNow()
立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

线程池的工作原理

当任务来了之后,如果核心线程数没有满,那么就使用核心线程数,如果核心线程数满了,那么就将任务放入任务队列中,如果任务队列也满了,就开始使用最大线程数,如果最大线程数也使用满了,会抛出异常,拒绝任务。

依赖注入

什么是依赖
如果在 Class A 中,有Class B 的实例,则称 Class A 对 ClassB 有一个依赖。

存在依赖的例子:

/**
 * Human对Father有一个依赖
 */
public class Human {
    // Human中使用了Father的实例,产生了对Father依赖
    private Father father;
    /**
     *  主动初始化依赖,耦合严重
     */
    public Human() {
        father = new Father();
    }
}

存在的问题:
l 如果现在要改变 father 生成方式,如需要用new Father(String name)初始化 father,需要修改 Human 代码;
l 如果想测试不同 Father 对象对 Human 的影响会变得很困难,因为 father 的初始化被写死在了 Human 的构造函数中;
l 如果new Father()过程非常缓慢,单测时我们希望用已经初始化好的 father 对象也很困难。

问题产生原因:
两个类不够独立,耦合严重。

解决方案:由外界来提供依赖的实例

/**
 * Human对Father有一个依赖
 */
public class Human {

    private Father father;

    /**
     * 由外界来注入来传入依赖,解耦
     */
    public Human(Father father) {
        this.father = father;
    }
}

类似这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。

什么是依赖注入

依赖注入的目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性。
Java中一般都是通过注解 + 反射的方式来实现依赖注入的。

依赖注入示例:
通过依赖注入,为类的成员变量赋值。

定义注解类,用于生命成员变量的值:StringAnnotation.java

/**
 * 自定义注解,用于为成员变量赋值
 */
@Target(ElementType.FIELD) // 作用范围:成员变量
@Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时
@Documented // 在文档中显示
public @interface StringAnnotation {
    String value();

定义解析注解并为成员变量赋值的注入工具类:ParseAnnocation.java

/**
 *  注入工具类
 */
public class ParseAnnocation {
   public static void parseAnnocation(Object object) throws Exception{
      //1.获取字节码文件
      Class c=object.getClass();
      //2.获取成员变量
      Field[] fields = c.getFields();
      //3.遍历成员变量
      for (Field field : fields) {
            // 4. 找到带有StringAnnotation注解的成员变量
         if(field.isAnnotationPresent(StringAnnotation.class)){
                StringAnnotation annotation = field.getAnnotation(StringAnnotation.class);
            //5. 获取注解中的值
            String value=annotation.value();
                // 6. 为成员变量赋值
            field.set(object, value);
         }
      }
   }
}

使用注解 + 注入工具类,测试注入结果:

/**
 * 测试依赖注入
 */
public class Dependency {

    @StringAnnotation("张三")
    public String name = "猜我是谁";

    public static void main(String[] args) throws Exception {
        Dependency dependency = new Dependency();
        // 解析注解,为成员变量赋值
        ParseAnnocation.parseAnnocation(dependency);
        // 打印注入后的值
        System.out.println("======Dependency name =======" + dependency.name);
    }
}

运行结果:


上一篇下一篇

猜你喜欢

热点阅读