1 Junit测试

Junit测试单元是由第三方提供的测试工具,可以使用main方法,让指定的方法直接执行起来;
在junit测试单元中有3个注解:
1: @Test 表示要测试这个方法
2: @Before 表示在执行被测试的方法(含有@Test的方法)之前,需要执行含有@Before的方法
3: @After 表示在执行被测试的方法(含有@Test的方法)之后,需要执行含有@After的方法
junit的使用步骤:
1:导入junit.jar包(在eclipse中已经内置了,直接引入即可)
2:直接在被测试的方法上面添加相应的注解即可;
使用前提:
被测试的方法要求:
1:必须是public权限
2:必须是void返回值
3:必须是空参数的列表

1 注解

1.1 概述

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

1.2 JDK提供的注解

  1. @Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。(以后使用被这个修饰的方法时候会多一条线)
     一般被标记位过时的方法都存在不同的缺陷:1安全问题;2新的API取代
    2. @Override JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
    3. @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略(里面可以传字符串的值)
    可传的值为:
    deprecation ,或略过时
    rawtypes ,忽略类型安全
    unused , 忽略不使用
    unchecked ,忽略安全检查
    null,忽略空指针
    all,忽略所有
     @Deprecated ```java //#1 方法过期 class Parent1_1{ @Deprecated public void init(){

    } }

  1. @Override 复写父类方法
  2. ```java
  3. //#2.1 JDK5.0 复写父类方法
  4. class Parent1_2{
  5. public void init(){
  6. }
  7. }
  8. class Son1_2 extends Parent1_2{
  9. @Override
  10. public void init() {
  11. }
  12. }

 @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();

    }

}

image.png

1.3 自定义注解:定义—基本语法

一般抽取工具或者优化的时候会使用。
关键字: @interface
只能写属性:
属性的格式:
public abstract 数据类型 属性名() default 属性值;
支持的数据类型:
基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组(注意:不支持基本数据类型的包装类型)
 定义注解使用关键字: @interface
1. 定义类: class
2. 定义接口:interface
3. 定义枚举:enum

// #1 定义注解
@interface MyAnno1{

}

 定义带有属性的注解

//#2 定义含有属性的注解
@interface MyAnno2{
    public String username() default "jack";
}

 属性格式:修饰符 返回值类型 属性名() [default 默认值]
1. 修饰符:默认值 public abstract ,且只能是public abstract。
image.png
2. 返回值类型:基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组
image.png
3. 属性名:自定义
4. default 默认值:可以省略
 完整案例

//#3 完整含属性注解
@interface MyAnno3{
    int age() default 1;
    String password();
    Class clazz();
    MyAnno2 myAnno(); // 注解
    Color color(); // 枚举
    String[] arrs();
}
enum Color{
    BLUE,RED;
}

1.4 自定义注解:使用

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

@MyAnno1
@MyAnno2(username="rose")
@MyAnno3(
        age=18 , 
        password="1234" ,
        clazz=String.class , 
        myAnno=@MyAnno2 , 
        color = Color.RED , 
        arrs = {"itcast","itheima"} 
)
public class TestAnno2 {

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

关键字: enum
数据类型:属于引用数据类型;
作用:相当于定义了一个常量数组,数组中的每一个元素都是常量,常量的值的数据类型就是这个枚举类型;
常量定义格式:直接写常量名即可,多个常量名之间使用逗号分隔
* 常量名使用格式:类名.常量名
举例:

public enum Color {
    //定义常量  
    RED,GREEN;
}
public @interface MyAn2 {
    //定义属性
    public abstract Color yanSe() default Color.GREEN;
}

1.5 自定义注解:定义—元注解

注解和反射一样,默认只在编译时期存在(在.java中存在,当编译成.class时会编译擦除)
元注解:用于修饰注解的注解。(用于修饰自定义注解的JDK提供的注解)
JDK提供4种元注解:
@Retention 用于确定被修饰的自定义注解生命周期(retention的英文是保留的意思)
RetentionPolicy.SOURCE 被修饰的注解只能存在源码中,字节码class没有。用途:提供给编译器使用。
RetentionPolicy.CLASS 被修饰的注解只能存在源码和字节码中,运行时内存中没有。用途:JVM java虚拟机使用
RetentionPolicy.RUNTIME 被修饰的注解存在源码、字节码、内存(运行时)(常用)。用途:取代xml配置
@Target 用于确定被修饰的自定义注解 使用位置
ElementType.TYPE 修饰 类、接口
ElementType.CONSTRUCTOR 修饰构造
ElementType.METHOD 修饰方法
ElementType.FIELD 修饰字段
@Documented 使用javaDoc生成 api文档时,是否包含此注解 (了解)
@Inherited 如果父类使用被修饰的注解,子类是否继承。(了解)
image.png
 修改注解类,在运行测试实例,输出结果为:true。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno1{

}

举例:
MyAn3.java


/*
 * 使用元注解,让MyAn3在代码运行的时候,也存活
 */

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(value=RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAn3 {
    public abstract String abc() default "abc的默认值";
}

MyAn3Test.java



import java.lang.reflect.Method;

import org.junit.Test;

@SuppressWarnings("all")
public class MyAn3Test {
    @MyAn3
    @Test
    public void m1() throws Exception{
        //1:解析m1这个方法头上的MyAn3注解的abc属性值
        //1:判断m1的上面是否有MyAn3这个注解
        Class c1 = MyAn3Test.class;
        Method m = c1.getMethod("m1");
        boolean b = m.isAnnotationPresent(MyAn3.class);
        System.out.println(b);
        //2:从m对象上面,获取一个MyAn3注解类型的对象
        MyAn3 a3 = m.getAnnotation(MyAn3.class);
        //3:调用属性名()即可获取属性值
        String abc = a3.abc();
        System.out.println(abc);
    }
}

1.6 自定义注解:解析

 如果给类、方法等添加注解,如果需要获得注解上设置的数据,那么我们就必须对注解进行解析,JDK提供java.lang.reflect.AnnotatedElement接口允许在运行时通过反射获得注解。
image.png
 常用方法:
 boolean isAnnotationPresent(Class annotationClass) 当前对象是否有注解
 T getAnnotation(Class annotationClass) 获得当前对象上指定的注解
 Annotation[] getAnnotations() 获得当前对象及其从父类上继承的,所有的注解
 Annotation[] getDeclaredAnnotations() 获得当前对象上所有的注解
image.png
 测试

@MyAnno1
public class TestAnno2 {
    public static void main(String[] args) {
        boolean b = TestAnno2.class.isAnnotationPresent(MyAnno1.class);
        System.out.println(b);    //false
    }
}

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

1.7 模拟@Test功能练习

MyTest.java


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {

}

Demo.java


import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Demo {
    @MyTest
    public void m1(){
        System.out.println("m1...");
    }
    public void m2(){
        System.out.println("m2...");
    }
    public void m3(){
        System.out.println("m3...");
    }
    @MyTest
    public void m4(){
        System.out.println("m4...");
    }

    //编写main方法,识别上面的m1,m2,m3,m4哪些方法上面有MyTest,可以直接,否则不能执行
    public static void main(String[] args) throws Exception{
        //1:反射获取m1,m2,m3,m4方法对象
        Class c=Demo.class;
        Method[] ms = c.getMethods();
        //2:迭代所有的方法对象,判断,如果方法上面有MyTest,可以直接,否则不能执行
        for (Method m : ms) {
            boolean b = m.isAnnotationPresent(MyTest.class);
            if(b){
                m.invoke(c.newInstance());
            }
        }
    }
}

2 JDK的动态代理

jdk的动态代理要求目标类必须有接口。

2.1 动态代理流程分析

动态代理的流程分析.png

2.1 java.lang.reflect.Proxy

用于创建动态代理对象的工具类,将来使用者直接面向这个工具类,即可获取一个动态代理对象,面向动态代理对象即可操作刘德华对象的方法。

创建动态代理对象的步骤:
面向JDK的Proxy工具类,直接调用静态方法即可获取一个动态代理对象。
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
参数说明:
(1):loader,是目标类的类加载器。
(2):interfaces,是目标类实现的所有接口。
(3):h,调用处理程序,是专门用于告诉生成的动态代理对象如何处理目标类中的方法。
返回值说明:
Proxy的newProxyInstance方法返回的是一个动态代理对象,,这个动态代理对象由Proxy工具类负责创建出来,是目标类的兄弟对象,需要使用目标类的接口类型来接收。

InvocationHandler接口中的方法:
Object invoke(Object proxy, Method method, Object[] args)
在代理实例上处理方法调用并返回结果。
参数说明:
1:proxy 就是即将生成的动态代理对象,这里不能使用,否则语法报错。
2:method 代表的是使用者想调用的目标类中的方法对象。
3:args 代表的是method执行时所需要的实际参数。
返回值说明:
InvocationHandler的invoke方法。返回的就是增强后的处理结果。一般会直接返回method方法的invoke执行的结果。

2.3 练习举例

YiRen.java

public interface YiRen {
    public void sing();
    public void dance();
}

LDH.java

public class LDH implements YiRen{
    public void sing(){
        System.out.println("刘德华唱练习...");
    }
    public void dance(){
        System.out.println("刘德华跳太空步...");
    }
}

MyUtils.java


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MyUtils {
    //创建一个刘德华的对象
    private static LDH l = new LDH();
    //静态方法,直接造
    public static Object getDongTaiDaiLi(){
        //面向JDK工具类,直接造动态代理对象即可
        YiRen y=(YiRen)Proxy.newProxyInstance(l.getClass().getClassLoader(),l.getClass().getInterfaces(),new InvocationHandler(){    //y是临时在内存中创建的,和l是兄弟,实现了相同接口
            //invoke方法,程序员只负责编写,由生成的动态代理对象负责调用
            public Object invoke(Object proxy, Method method, Object[] args){
                //在这里就可以编写调用处理流程了........
                System.out.println("刘德华开始执行 "+method.getName()+" 之前,都要执行的流程.........");
                try {
                    //让刘德华的方法执行
                    Object in = method.invoke(l, args);
                    //控制方法执行后
                    System.out.println("刘德华的"+method.getName()+"方法执行后..........");
                    return in;
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException();
                } 
            }
        });
        //2:返回动态代理对象y
        return y;
    }
}

Test.java

/*
 * 歌迷类
 */
public class Test {

    public static void main(String[] args) {
        //1>获取一个经纪人对象
        YiRen y = (YiRen) MyUtils.getDongTaiDaiLi();
        //2:面向经纪人对象,调用具体的方法
        y.sing();
        y.dance();
    }

}

3 类加载器

 类加载器:类加载器是负责加载类的对象。将class文件(硬盘)加载到内存生成Class对象。
所有的类加载器 都是 java.lang.ClassLoader 的子类
image.png
 使用 类.class.getClassLoader() 获得加载自己的类加载器
 类加载器加载机制:全盘负责委托机制
全盘负责:A类如果要使用B类(不存在),A类加载器C必须负责加载B类。
image.png
委托机制:A类加载器如果要加载资源B,必须询问父类加载是否加载。
如果加载,将直接使用。
如果没有机制,自己再加载。
 采用 全盘负责委托机制 保证 一个class文件 只会被加载一次,形成一个Class对象。
 注意:
如果一个class文件,被两个类加载器加载,将是两个对象。
提示 com.itheima.Hello 不能强制成 com.itheima.Hello
h.getClass() —>A h.getClass() —>B
自定义类加载,可以将一个class文件加载多次。
image.png
练习:

import sun.security.ec.SunEC;

/*
 * 类加载器练习:
 * 
 *     
 */
public class Test {

    public static void main(String[] args) {
        //1:获取引导类加载器
        ClassLoader l1 = String.class.getClassLoader();
        System.out.println(l1);
        //2:获取扩展类加载器
        ClassLoader l2 = SunEC.class.getClassLoader();    //SunEC就是ext结尾的一个类
        System.out.println(l2);
        //3:获取应用类加载器
        ClassLoader l3 = Test.class.getClassLoader();
        System.out.println(l3);
        System.out.println("---------------------");
        ClassLoader l4 = l3.getParent();
        System.out.println(l4);
        System.out.println(l4.getParent());
    }

}

输出结果:
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@6d06d69c
——————————-
sun.misc.Launcher$ExtClassLoader@1b6d3586
null

注意:
1:引导类加载器(根类加载器):是用c和c++写的所以在java里获取不到。java.lang包下的东西由他负责加载。
2:扩展类加载器:java包里带ext后缀的是由扩展类加载器加载的。
3:应用类加载器:只加载第三方jar包和程序员自己写的类。
4:父类加载器:读法:父-类加载器,强调是调用关系而不是继承关系。
5:在使用时,应用类加载器使用比较多,获取方法要记住。类.class.getClassLoader()
6:在敲上面代码的时候可能发生eclipse没有给SunEC的导包动作,这是因为eclipse默认情况下,sun公司的包,eclipse是没有权限使用的,不能使用sun开头的这些包的。此时需要的操作是:
(1)右键点击工程,选Properties。
(2)找Java Build Path
(3)找Libraries
(4)找JRE System Library,展开,找Access rules:,然后edit,然后add,
(5)在Resolution中选Accessible,Rule Pattern中添加一个“sun/**”。

4 综合练习:动态代理解决一个参数乱码问题

MyEncodingFilter.java

package 

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 使用动态代理的方式,完成中文编码过滤的问题
 */
public class MyEncodingFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /*
         * 思路:
         *         如果是get请求,则动态的创建一个代理对象,并放行代理对象,
         *         如果是post请求,则直接设置编码,并放行
         */
        //1:转成子接口类型
        final HttpServletRequest r = (HttpServletRequest)request;
        HttpServletResponse res = (HttpServletResponse)response;
        //2:解决响应乱码
        res.setContentType("text/html;charset=utf-8");
        //3:获取请求方式
        String m = r.getMethod();
        if("get".equalsIgnoreCase(m)){
            //4:创建代理对象并放行
            HttpServletRequest req=(HttpServletRequest)Proxy.newProxyInstance(r.getClass().getClassLoader(),r.getClass().getInterfaces(),new InvocationHandler() {
                boolean flag = true;
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //在这里写处理流程
                    //根据method对象,获取方法名
                    String name = method.getName();
                    //让method对象执行
                    Object o=method.invoke(r, args);
                    if(name.equals("getParameter")){
                        //说明此时的method是getParameter方法
                        String s =(String)o;
                        if(s==null){
                            return null;
                        }
                        s=new String(s.getBytes("iso8859-1"),"utf-8");
                        return s;
                    }else if(name.equals("getParameterValues")){//注意:每次调用getParameterValues的时候会得到一个新数组,所以里面不能用增强for(具体见下图debug模式图)
                        //说明此时的method是getParameterValues方法
                        String[] s =(String[])o;
                        if(s!=null){
                            for (int i=0;i<s.length;i++) {
                                s[i] = new String(s[i].getBytes("iso8859-1"),"utf-8");
                            }
                        }
                        return s;
                    }else if(name.equals("getParameterMap")){//注意:每次调用getParameterMap的时候会得到的都是同一个map,所以里面可以用增强for,又因为是同一个map,所以只能转一次,需要定义一个布尔类型变量flag控制一下。(具体见下图debug模式图)
                        //说明此时的method是getParameterValues方法
                        Map<String,String[]> map =(Map<String,String[]>)o;
                        if(flag){
                            Set<String> set = map.keySet();
                            for (String key : set) {
                                String[] s = map.get(key);
                                for (int i=0;i<s.length;i++) {
                                    s[i] = new String(s[i].getBytes("iso8859-1"),"utf-8");
                                }
                            }
                            flag=false;
                        }
                        return map;
                    }
                    //其他方法,直接返回原始值
                    return o;
                }
            });
            //5:放行req对象
            chain.doFilter(req, res);
        }else{
            //设置编码.,并放行
            r.setCharacterEncoding("utf-8");
            chain.doFilter(r, res);
        }
    }
    public void destroy() {
    }
    public void init(FilterConfig fConfig) throws ServletException {
    }

}

Hello.java

package 

import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 验证参数的servlet
 */
public class Hello extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String n = request.getParameter("name");
        String[] values2 = request.getParameterValues("hobby");
        String[] values = request.getParameterValues("hobby");
        Map<String, String[]> map2 = request.getParameterMap();
        Map<String, String[]> map = request.getParameterMap();
        System.out.println("一个参数:"+n);
        System.out.println("一个数组:"+Arrays.toString(values));
        Set<String> set = map.keySet();
        for (String key : set) {
            System.out.println("map:"+key+"====>"+Arrays.toString(map.get(key)));
        }
        response.getWriter().println("你好啊");
    }
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

}

测试:
浏览器输入:
localhost:/day45/Hello?name=张三&hobby=抽烟&hobby=喝酒
无标题.png