第04天 java基础加强

今日内容介绍

u Xml的综合案例

u 注解

u 类的加载

u 动态代理

第1章   注解

1.1  注解概述

l  什么是注解:Annotation注解,是一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次

n  对比注释:注释是给开发人员阅读的,注解是给计算机提供相应信息的。

l  注解的作用:

  1. 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
  2. 代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的。
  3. 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
  4. @Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。

1.2  JDK常见注解

1.2.1 常见注解

n  一般被标记位过时的方法都存在不同的缺陷:1安全问题;2新的API取代

  1. @Override  JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
  2. @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略

       deprecation ,或略过时

       rawtypes ,忽略类型安全

       unused , 忽略不使用

       unchecked ,忽略安全检查

       null,忽略空指针

       all,忽略所有

1.2.2 案例代码一

AnnotationDemo_01

public class AnnotationDemo_01 {

   

    @Deprecated

    public void init(){

    }

}

 

AnnotationDemo_02

public class AnnotationDemo_02 {

}

// 情况1: jdk1.5表示复写父类的方法

class Parent_2_1{

    public void init(){

    }

}

class Son_2_1 extends Parent_2_1{

    @Override

    public void init() {

    }

}

// 情况2:jdk1.6 表示方法实现接口声明的方法

interface Parent_2_2{

    public void init();

}

class Son_2_2 implements Parent_2_2 {

    @Override

    public void init() {

       

    }

}

 

 

 

AnnotationDemo_03

@SuppressWarnings("serial")

public class AnnotationDemo_03 implements java.io.Serializable {

   

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

    public static void main(String[] args) {

       

        List list = new ArrayList();

       

        String str = null;

        str.toString();

       

        new Thread().stop();

    }

}

 

1.3      自定义注解

1.3.1 自定义注解--定义与使用

 

l  定义注解使用关键字: @interface

  1. 定义类: class
  2. 定义接口:interface
  3. 定义枚举:enum

// #1 定义注解

@interface MyAnno1{

      

}

l  定义带有属性的注解

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

@interface MyAnno2{

       public String username() default "jack";

}

l  属性格式:修饰符  返回值类型  属性名()  [default 默认值]

  1. 修饰符:默认值 public abstract ,且只能是public abstract。

 

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

 

  1. 属性名:自定义
  2. default 默认值:可以省略

l  注解使用的注意事项:

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

1.3.2 代码案例二

Anno_01.java

public @interface Anno_01 {

}

Anno_02.java

public @interface Anno_02 {

    /* 格式:修饰符  返回值类型  属性名() default 默认值

     * * 修饰符:public abstract

     * * 返回值类型:基本类型、String、Class、注解、枚举,以及以上类型的一位数组

     *  * 属性名:自定义

     *  * 默认值:可以省略

     */

    public abstract String username() default "默认值";

    public int age() ;

    public Class clazz();

    public Anno_01 anno();

    public Color color();

}

 

//枚举:相当于多例。Color.RED

enum Color{

    RED,BLUE;

}

 

//自定义多例

class Color2{

    private Color2(){

       

    }

    public static final Color2 RED = new Color2();

    public static final Color2 BLUE = new Color2();

}

 

 

Anno_03.java

public @interface Anno_03 {

 

    public String[] hobbies();

}

 

 

Anno_04.java

public @interface Anno_04 {

 

    public String[] value();

}

 

TestAnnotation.java

@Anno_01

@Anno_02(username="jack", age=18,clazz=Date.class , anno=@Anno_01 , color = Color.RED )

//@Anno_03(hobbies={"抽烟","喝酒","烫头"})        //数组表示使用{}括住

@Anno_03(hobbies="抽烟") //如果只有一个值,{}可以省略

//@Anno_04(value={"a","b"})

//@Anno_04(value="a")

@Anno_04("a")               //只有一对属性时,属性名为value,可以省略属性名

                            //同一个对象中,同一个注解只能使用一次。

public class TestAnnotation {

 

}

1.3.3 自定义注解--解析和元注解

1.3.3.1       解析

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

 

常用方法:

boolean isAnnotationPresent(Class annotationClass) 当前对象是否有注解

T getAnnotation(Class<T> annotationClass) 获得当前对象上指定的注解

Annotation[] getAnnotations() 获得当前对象及其从父类上继承的,所有的注解

Annotation[] getDeclaredAnnotations() 获得当前对象上所有的注解

 

测试

public class MyAnnoTest_5 {

       public static void main(String[] args) {

boolean b = MyAnnoTest_4.class.isAnnotationPresent(MyAnno_1.class);

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

       }

}

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

 

1.3.3.2       元注解

元注解:用于修饰注解的注解。(用于修饰自定义注解的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.3.4 案例代码三

AnnoParse_01.java

@MyAnno("测试数据")

public class AnnoParse_01 {

   

    public static void main(String[] args) {

       

        //运行时,获得指定类上面的注解,从而获得对应数据

       

        boolean b = AnnoParse_01.class.isAnnotationPresent(MyAnno.class);

        if(b){

            MyAnno myAnno = AnnoParse_01.class.getAnnotation(MyAnno.class);

            String value = myAnno.value();

            System.out.println(value);

        } else {

            System.out.println("没有注解");

        }

       

    }

   

    @MyAnno

    public AnnoParse_01(){

       

    }

   

    @MyAnno

    public void init(){

       

    }

}

 

MyAnno.java

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

 

/* 元注解:jdk提供一套注解,元注解用于修饰自定义注解。

 * 用于控制自定义注解 使用位置、生命周期等基本信息。

 *

 * @Target 用于限定自定义注解使用位置

 *      @Target(ElementType.CONSTRUCTOR) 只能在构造方法使用

 *      @Target(ElementType.METHOD) 只能在普通方法使用

 *      @Target(ElementType.FIELD) 只能在字段使用

 *      @Target(ElementType.TYPE) 只能在类、接口使用

 * @Retention 用于确定自定义注解生命周期

 *      @Retention(RetentionPolicy.SOURCE)  自定义注解只在源码有效,编译之后将删除(class文件没有)。提供编译器使用

 *      @Retention(RetentionPolicy.CLASS)   自定义注解只在源码和字节码有效,编译之后有,运行时内存没有。提供JVM使用

 *      @Retention(RetentionPolicy.RUNTIME) 自定义注解在源码、字节码和内存都有效。【】提供程序使用,用于取代xml配置文件

 *

 */

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

@Retention(RetentionPolicy.RUNTIME)

public @interface MyAnno {

 

    public String value() default "默认值";

   

}

 

1.4      综合案例

1.4.1 案例分析

l  模拟Junit测试,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修改方法,且在运行时可以获得。

l  其次编写目标类(测试类),然后给目标方法(测试方法)使用@MyTest注解

l  最后编写测试类,使用main方法模拟Junit的右键运行。

1.4.2 案例代码四

MyTest.java

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 {

}

 

Demo.java

public class Demo {

   

    @MyTest

    public void demo01(){

        System.out.println("demo01");

    }

    //@MyTest

    public void demo02(){

        System.out.println("demo02");

    }

    @MyTest

    public void demo03(){

        System.out.println("demo03");

    }

}

 

TestApp.java

import java.lang.reflect.Method;

public class TestApp {

   

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

        //模拟 右键运行

        // 当前指定类,所有的方法,是否有@MyTest注解,如果有将运行

        //1 当前类

        Class clazz = Demo.class;

        // * new 实例

        Object obj = clazz.newInstance();

        //2 获得所有的方法

        Method[] allMethod = clazz.getMethods();

        for(Method method : allMethod){

            //3 判断方法上是否有指定的注解

            boolean b = method.isAnnotationPresent(MyTest.class);

            if(b){

                // 4 如果有,运行类

                method.invoke(obj);

            }

        }

    }

}

第2章   类的加载

2.1  类加载--理论            

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

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

      

l  使用类.class.getClassLoader()  获得加载自己的类加载器

l  类加载器加载机制:全盘负责委托机制

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

             

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

              如果加载,将直接使用。

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

l  采用 全盘负责委托机制 保证 一个class文件 只会被加载一次,形成一个Class对象。

l  注意:

       如果一个class文件,被两个类加载器加载,将是两个对象。

              提示 com.itheima.Hello  不能强制成 com.itheima.Hello

              h.getClass() -->A                 h.getClass()  -->B

       自定义类加载,可以将一个class文件加载多次。

      

 

 

2.2  类加载--演示

2.2.1 案例代码五

ClassLoaderDemo_01

import org.junit.Test;

 

public class ClassLoaderDemo_01 {

   

    @Test

    public void demo01(){

        // 确定引导类加载器,加载内容:rt.jar  (Runtime )

        // * JDK固定配置信息,sun.boot.class.path 用于表示 引导类加载器所加载的内容

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

        String[] allPath = paths.split(";");

        for(String p : allPath){

            System.out.println(p);

        }

    }

   

    @Test

    public void demo02(){

        // 确定引导类加载器,类型:null

        ClassLoader cl = String.class.getClassLoader();

        System.out.println(cl);

    }

}

 

ClassLoaderDemo_02.java

import org.junit.Test;

import sun.net.spi.nameservice.dns.DNSNameService;

 

public class ClassLoaderDemo_02 {

   

    @Test

    public void demo01(){

        // 确定扩展类加载器,加载内容: jre/lib/ext

        // * JDK固定配置信息,java.ext.dirs 用于表示 扩展类加载器所加载的内容

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

        String[] allPath = paths.split(";");

        for(String p : allPath){

            System.out.println(p);

        }

    }

    @Test

    public void demo02(){

        // 确定扩展类加载器,类型:Launcher$ExtClassLoader

        ClassLoader cl = DNSNameService.class.getClassLoader();

        System.out.println(cl);

    }

}

 

ClassLoaderDemo_03.java

import org.junit.Test;

import sun.net.spi.nameservice.dns.DNSNameService;

 

public class ClassLoaderDemo_03 {

   

    @Test

    public void demo01(){

        // 确定应用类加载器,加载内容: 项目/bin (编译后内容) ,自己编写类由应用类加载加载

        // * JDK固定配置信息,java.class.path 用于表示应用类加载器所加载的内容

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

        String[] allPath = paths.split(";");

        for(String p : allPath){

            System.out.println(p);

        }

    }

   

    @Test

    public void demo02(){

        // 确定应用类加载器,类型:Launcher$AppClassLoader

        ClassLoader cl = ClassLoaderDemo_03.class.getClassLoader();

        System.out.println(cl);

    }

}

 

ClassLoaderDemo_04.java

import org.junit.Test;

import sun.net.spi.nameservice.dns.DNSNameService;

 

public class ClassLoaderDemo_04 {

    @Test

    public void demo01(){

        //3个类加载的关系

        ClassLoader c1 = ClassLoaderDemo_04.class.getClassLoader();

        System.out.println(c1); //应用 (AppClassLoader)

       

        ClassLoader c2 = c1.getParent();

        System.out.println(c2); //扩展(ExtClassLoader)

        ClassLoader c3 = c2.getParent();

        System.out.println(c3); //引导(null)

    }

}

 

第3章   动态代理

3.1  动态代理概述

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

3.2      Proxy类

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

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

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

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

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

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

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

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

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

       参数1:代理实例

       参数2:当前执行的方法

       参数3:方法实际参数。

 

3.3 
动态代理原理

 

3.4  动态代理案例

3.4.1 需求分析

自定一个MyCollections,在该类中定义一个unmodifiableList方法实现,使用动态代理对该方法增强,使list对象不能再加入元素。

3.4.2 案例代码六

MyCollections.java

public class MyCollections {

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

    public static List<String> unmodifiableList(final List<String> list) {

        //list 所有功能都有 (目标类)

        //proxyList 希望不能进行增删改,只能查询  (代理类)

        // 参数1:ClassLoader ,动态代理需要一个类加载器

        ClassLoader loader = MyCollections.class.getClassLoader();

        // 参数2:Class[] interfaces 需要与目标类接口保持一致

        Class[] interfaces = list.getClass().getInterfaces();

       

        List<String> proxyList = (List<String>)Proxy.newProxyInstance(loader, interfaces, new InvocationHandler(){

 

            //代理类的每一个方法调用一次,处理类invoke方法都执行一次

            @Override

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

               

                //处理(增强)--不允许添加

                //1 获得方法名

                String methodName = method.getName();

                //2 eq 不同方法的处理

                if("add".equals(methodName)){

                    throw new UnsupportedOperationException("操作不允许");

                }

                //处理类中直接执行目标类对应的方法

                return method.invoke(list, args);

            }

        });

        return proxyList;

    }

}

 

ProxyDemo_01.java

import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

 

import org.junit.Test;

 

public class ProxyDemo_01 {

   

    @Test

    public void demo01(){

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

        list.add("abc");

        String s = list.get(0);

        System.out.println(s);

       

        List<String> list2 = Collections.unmodifiableList(list);

        String s2 = list2.get(0);

        System.out.println(s2);

        list2.add("123");

       

        //不支持操作异常,当前list2不允许修改。

        //java.lang.UnsupportedOperationException

       

    }

   

    @Test

    public void demo02(){

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

        list.add("abc");

        String s = list.get(0);

        System.out.println(s);

       

        List<String> list2 = MyCollections.unmodifiableList(list);

        String s2 = list2.get(0);

        System.out.println(s2);

        list2.add("123");   //不允许

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

       

        //不支持操作异常,当前list2不允许修改。

        //java.lang.UnsupportedOperationException

    }

}