day045 注解 & 动态代理

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

//#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();
        
    }
    
}

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<T> 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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值