30、Java基础增强2(JavaEE笔记)

主要内容:

  • 泛型
  • 注解
  • 动态代理
  • 类加载器

一、泛型

1.1 泛型(Generic)的作用

  • jdk5以前,对象保存到集合中就会时区其特性,取出时通常要程序员手工进行类型的强制转换,这样不可避免就会引发程序的一些安全性问题。

  • jdk5中的泛型允许程序员在编写结合代码时,就限制集合的处理类型,从而把原来程序运行时可能发生的问题,转变为编译时的问题,以此提高程序的可读性和稳定性。

注意:泛型是提供给javac编译器使用的,它用于限定集合的输出类型,让编译器在源代码级别上挡住向集合中插入非法数据。但编译器编译完带有泛型的java程序后,生成的class文件中将不再带有泛型信息,以此使程序运行效率不受到影响,这个过程称之为“擦除”。

  • 泛型的基本术语,以ArrayList<E>为例,<>typeof
    ArrayList<E>中的E称为类型参数变量
    ArrayList<Integer>中的Integer称为实际类型参数
    •整个称为ArrayList<E>泛型类型
    •整个ArrayList<Integer>称为参数化的类型ParameterizedType

1.2 泛型的典型应用

  • 使用迭代器迭代泛型集合中的元素
  • 使用增强for循环迭代泛型集合中的元素
  • 存取HashMap中的元素
  • 使用泛型时的几个常见问题:
    1.使用泛型时,泛型类型必须为引用类型,不能是基本数据类型
    2.ArrayList<String> list = new ArrayList<Object>();
    3.ArrayList<Object> list = new ArrayList<String>();
    4.ArrayList<String> list = new ArrayList ();
    5.rayList list = new ArrayList<String>();

1.3 自定义泛型-泛型方法

1.3.1 回顾(工程day22

Demo1.java

package cn.itcast.generic;

public class Demo1 {
    
    public <T> void aa(T t){
        
    }
    public <T> void bb(T t){
        
    }
}

说明:

  • 1.一般我们定义泛型方法如上,如果我们先想使用泛型参数,那么我们需要先定义再使用,即在方法中使用<T>来定义泛型。
  • 2.当多个方法都需要使用相同的泛型时,我们可以将泛型定义在类上,这样方法中就不需要再次定义了:
package cn.itcast.generic;

public class Demo1<T> {
    
    public void aa(T t){
        
    }
    public void bb(T t){
        
    }
}

但是注意:在类上定义泛型只对类中的普通方法(非静态成员)有效,对静态方法是无效的,也就是说如果我们想在静态方法中使用泛型,那么我们需要单独定义之后再使用。如:
public static <T> void cc(T t){}

  • 3.注意:只有对象类型才能作为泛型方法的实际参数类型,基本数据类型则不能。

1.3.2 一个小题目:编写一个泛型方法,接收一个任意数组,并颠倒数组中的所有元素

Demo2.java

package cn.itcast.generic;
import java.util.Arrays;

public class Demo2 {

    public static void main(String[] args) {
        Integer arr[] = {1,2,3,4};
        reverse(arr);
        System.out.println(Arrays.asList(arr));

    }

    private static <T> void reverse(T[] arr) {
        int start = 0;
        int end = arr.length - 1;
        
        T tmp = arr[0];
        while(start < end){
            tmp = arr[start];
            arr[start] = arr[end];
            arr[end] = tmp;
            start++;
            end--;
        }
    }
}

1.4 自定义泛型-泛型类和反射泛型

  • 泛型的典型应用:BaseDao和反射泛型
    BaseDao.java
package cn.itcast.generic;
import org.hibernate.Session;
public abstract class BaseDao<T> {
    
    private Session session;
    private Class clazz;
    
    public BaseDao(Class clazz){
        this.clazz = clazz;
    }
    
    public void add(T t){
        session.save(t);
    }
    public T find(String id){
        return (T) session.get(clazz, id);
    }
    public void update(T t){
        session.update(t);
    }
    public void delete(String id){
        T t = (T) session.get(clazz, id);//先得到再进行删除
        session.delete(t);
    }
}

CategoryDao.java

package cn.itcast.generic;
public class CategoryDao extends BaseDao<Category> {

    public CategoryDao(Class clazz) {
        super(Category.class);
    }
}

说明:

  • 1.以后我们的dao层实现中对于增删改查有很多类似的代码,这里我们希望只需要定义一个dao层实现,然后其他需要相关方法的dao层实现只需要继承此公共dao层即可,如CategoryDao.java

  • 2.当然这里我们要用到hibernate,而find方法中我们需要相关类的类型,我们可以使用构造函数传递进来,但是这种方式在继承的时候还需要显式调用构造方法,比较麻烦。还可以使用反射的方法进行传递:

public BaseDao(){
        //拿到的是子类,因为哪个类调用这个方法,那么this就表示哪个类,
        //而我们new一个CategoryDao时就会i调用其默认的构造方法,而这个
        //构造方法会调用父类的构造方法,所以这个方法相当于是CategoryDao调用的,于是this就表示CategoryDao。
        Class clazz = this.getClass();  
        
        //得到父类,clazz表示CategoryDao,其父类就是BaseDao <CategoryDao>
        ParameterizedType  pt = (ParameterizedType)clazz.getGenericSuperclass();
        //一个参数化泛型中可能有多个参数,我们这里取第一个参数,即得到具体的实体类型。
        clazz = (Class) pt.getActualTypeArguments()[0];
        System.out.println(clazz);
        
    }

此时我们只需要继承即可,无需再显式调用构造方法。此方法中第一步是拿到具体dao层的类型,然后通过此类型拿到具体的实体类型。

  • 3.在实际开发中,我们一般是通过方法参数将具体的类的类型传递进来:
    public T get(Class<T> c, Serializable id)

1.5泛型的高级应用-通配符

Demo4.java

package cn.itcast.generic;
import java.util.ArrayList;
import java.util.Collection;

public class Demo4 {
    public static void main(String[] args) {
        print(new ArrayList<Integer>());
        print(new ArrayList<String>());
    }
    public static void print(Collection<?> c){
        for(Object obj : c){
            System.out.println(obj);
        }
    }
    //当使用?通配符时,就不能再调用与类型相关的方法,只能调与类型无关的方法
    public static void save(Collection<?> c){
        //c.add("1");
        c.size();
    }
}

注意:记住,通配符主要用于引用对象,使用了通配符之后就只能调用对象与类型无关的方法,不能调用对象与类型有关的方法(不管什么情况下都是这样)。比如上面的save方法中我们就不能使用add这种与类型相关的方法,但是size方法与类型无关的方法。

1.6泛型的高级应用-有限制的通配符

  • 限定通配符的上边界
    正确:Vector<? extends Number> x = new Vector<Integer>();
    错误:Vector<? extends Number> x = new Vector<String>();

  • 限定通配符的下边界
    正确:Vector<? super Integer> x = new Vector<Number>();
    错误:Vector<? super Integer> x = new Vector<Byte>();

  • 问题:以下代码行不行?

public void add(List<? extends String> list){
    list.add("abc");
}

答:显然是不行的,因为使用了通配符,所以不能使用和类型相关的方法。

二、Annotation(注解)

2.1概述

  • 从jdk5开始,java增加了对元数据(MetaData)的支持,也就是Annotation(注解)。

  • 什么是Annotation,以及注解的作用?三个基本的Annotation:
    @Override: 限定重写父类方法, 该注解只能用于方法
    @Deprecated: 用于表示某个程序元素(类, 方法等)已过时
    @SuppressWarnings: 抑制编译器警告.

  • Annotation其实就是代码里的特殊标记,它用于替代配置文件,也就是说,传统方式通过配置文件告诉类如何运行,有了注解技术后,开发人员可以通过注解告诉类如何运行。在java技术里注解的典型应用是:可以通过反射技术去得到类里面的注解,以决定怎么去运行类。这个技术一般在框架中应用的比较多。

  • 例1:
    在servlet3.0中引入了注解,我们可以不使用xml文件进行配置而直接使用注解即可。

package cn.itcast.annotation;
//@WebServlet(name="servlet1",patternUrl={"/servlet/Servlet1","/servlet/MyServlet"})
public class MyServlet {
}
  • 例2:

Demo1.java

package cn.itcast.annotation;
import java.util.ArrayList;
import java.util.List;

@SuppressWarnings("unchecked")//对类使用
public class Demo1 {
    @SuppressWarnings("unchecked") private List list;
    //对字段使用
    
    @SuppressWarnings("unchecked")//对方法和参数使用
    public Demo1(@SuppressWarnings("unchecked") List list) {
        super();
        this.list = list;
    }

    @Override//表示覆盖父类的方法
    public boolean equals(Object obj) {
        return super.equals(obj);
    }
    
    @Deprecated//表示过时了
    public void doxx(){
        
    }
    public void doyy(){
        
    }
    @SuppressWarnings("unchecked")//表示跳过检查,这样便不会有警告
    public void dozz(){
        List list = new ArrayList();
        System.out.println(list);
    }
}

注意:注解只接收基本数据类型、String、Class、Annotation、枚举类型和以上所属类型的一维数组类型。

2.2 自定义Annotation

  • 定义新的Annotation类型使用@interface关键字

  • 声明注解的属性

    • 注解属性的作用:原来写在配置文件中的信息,可以通过注解的属性进行描述。
    • Annotation的属性声明方式:String name();
    • 属性默认值的申明方式:String name() default “xxx”;
    • 特殊属性value:如果注解中有一个名称value的属性,那么使用注解时可以省略value=部分,如@MyAnnotation(“xxx”)
    • 特殊属性value[]
  • 例1:
    MyAnnotation.java

package cn.itcast.annotation;
public @interface MyAnnotation {

    //注解可以使用如下类型配置注解包含的信息
    String name();//注意其字段的定义格式,这是定义一个属性
    
    String password() default "123";
    double age() default 12;
    Gender gender() default Gender.FEMALE;//一个枚举值
    Class clazz();
    MyAnnotation2 my2();//把另一个注解当作字段,对应于xml文档中的嵌套配置
    int[] arr() default {1,2,3};//注意不能把[]放在后面
    Gender[] gs();
}

Gender.java

package cn.itcast.annotation;

public enum Gender {
    MALE,FEMALE;
}

MyAnnotation2.java

package cn.itcast.annotation;
public @interface MyAnnotation2 {
    String name();
}

我们在使用时就可以这样:

@MyAnnotation(name="老张",age=37,gender=Gender.MALE,clazz=String.class,my2=@MyAnnotation2(name="xxx"),arr={2,3,4},gs={Gender.FEMALE,Gender.MALE})
public void doaa(){
}

注意:当一个注解中如果只有一个名为value的字段,那么在使用时可以直接使用(即直接给出其值),不需要”value=”
如:

package cn.itcast.annotation;
public @interface MyAnnotation3 {
    String[] value();//名称为value的属性可以直接赋值
}

使用时可以这样:

//名称为value的属性可以直接赋值
    @MyAnnotation3({"bb"})
    public void dobb(){
        
    }

但是如果此注解中还有其他的字段或是属性名不是value,则不能省略”value=”。注意如果是数组则不能少了花括号。

注意:我们在使用自定义注解时,如果自定义注解中没有给出默认值则必须在使用时给出相关字段的值。

2.3 反射注解

我们直接通过相关例子进行说明:
DbInfo.java

package cn.itcast.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//java--->class--->jvm()
@Retention(RetentionPolicy.RUNTIME)//注明此注解的作用域,这里是运行时,这种用的最多
@Target({ElementType.METHOD})//指定注解用于修饰类的哪个成员,这里指修饰方法
@Inherited//表示此注解具有继承性,就是子类可以继承父类的注解
public @interface DbInfo {
    String url() default "jdbc:mysql://localhost:3306/test";
    String username() default "root";
    String password() default "root";
}

Inject.java

package cn.itcast.annotation;

public @interface Inject {
    String name();
    int age();
}

这里我们首先定义了两个注解。下面我们使用这两个注解之后再反射相关方法上的注解。
JdbcUtils.java

package cn.itcast.annotation;
import com.sun.corba.se.pept.transport.Connection;


public class JdbcUtils {
    @DbInfo(url="jdbc:mysql://localhost:3306/test",username="root",password="walp1314")
    public static Connection getConnection(String url,String username,String password){
        System.out.println(url);
        System.out.println(username);
        System.out.println(password);
        return null;
    }
    
    @DbInfo(url="jdbc:mysql://localhost:3306/test",username="root",password="walp1314")
    @Inject(name="flx",age=23)
    public void aa(Person p){
        //其中Person类有两个字段name和age,这里我们对这个方法注入一个类。
    }
}

Demo2.java

package cn.itcast.annotation;
import java.lang.reflect.Method;


public class Demo2 {
    public static void main(String[] args) throws SecurityException, Exception {
        Class clazz = JdbcUtils.class;
        Method method = clazz.getMethod("getConnection", String.class,String.class,String.class);
        
        DbInfo di = method.getAnnotation(DbInfo.class);//得到方法的注解,
                            //一个方法可以有多个注解,这里我们得到关于DbInfo的注解。
        String url = di.url();//取得相关的字段,但是如果不指定注解
        String username = di.username();//是运行时注解,则是不会取得相
        String password = di.password();//关的类和字段的。
        method.invoke(null, url,username,password);
    }
}

其中Person.java

private String name;
private int age;
......

说明:

  • 1.对于DbInfo注解类中的相关注解我们在后面会详细说明,这些都是一些元注解信息,用来修饰注解。

  • 2.在类Demo.java中我们使用反射对相关方法上的注解进行反射,之后可以得到方法上的一些注解信息。但是注意:我们知道java类有3种状态,一种是源代码,还有class和在jvm中,如果我们将注解的作用域修饰为前两种(默认为class),那么反射是不会取到任何信息的,因为如果作用域为java源代码级别,那么编译的时候就会抛弃注解,如果级别为class,那么在运行时也会抛弃注解,此时反射不到任何信息。

  • 3.在开发中我们还经常使用注解将一个类通过容器注入到某个方法中,如上面的aa方法,其实也是通过反射实现的。

2.4 JDK的元注解

2.4.1 元注解

元Annotation指修饰Annotation的Annotation。Jdk中定义了如下元Annotation:

  • @Retention只能用于修饰一个Annotation的定义,用于指定该Annotation可以保留的域,包含一个RetentionPolicy类型的成员变量,通过这个变量指定域。

    • RetentionPolicy.CLASS 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解. 这是默认值
    • RetentionPolicy.RUNTIME编译器将把注释记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注释。这个值用的最多,一定不要忘记。
    • RetentionPolicy.SOURCE 编译器直接丢弃这种策略的注释
      我们可以看到在DbInfo.java中就使用了这个注解。我们在反射一个注解时如果使用的不是RetentionPolicy.RUNTIME,那么我们是拿不到相关的注解信息的。
  • @Target指定注解用于修饰类的哪个成员。@Target包含了一个名为value,类型为ElementType的成员变量。

  • @Documented用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档。

  • @Inherited被它修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该Annotation具有的注解。在DbInfo.java中就使用了这个注解。

2.4.2 模拟注解的实现

通过注解注入一个对象,前面例子中已经使用过,现在我们模拟其实现。而这一实现有两种方式:
首先我们看看使用注解的相关类
InjectPerson.java

package cn.itcast.annotation2;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)//一定不要忘记
public @interface InjectPerson {
    String name();
    int age();
}

PersonDao.java

package cn.itcast.annotation2;//使用注解注入一个类有两种方式
public class PersonDao {
    @InjectPerson(name="老王",age=23) private Person person;//2

    public Person getPerson() {
        return person;
    }
    @InjectPerson(name="老张",age=23)//1
    public void setPerson(Person person) {
        this.person = person;
    }
}

方式一:
test.java

package cn.itcast.annotation2;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
    public static void main(String[] args) throws Exception {
        
        //1.得到要注入的属性,这里使用的是内省
        PropertyDescriptor pd = new PropertyDescriptor("person",PersonDao.class);
        //name=person
        //propertyType=cn.itcast.annotation2.Person
        //readMethod=getPerson()
        //writeMethod=setPerson()
        
        //2.得到要注入的属性需要的类型
        Class clazz = pd.getPropertyType();  //Person
        
        //3.创健属性需要的对象,实例化一个对象
        Object person = clazz.newInstance();
        
        //4.得到属性的写方法writeMethod=setPerson()
        Method setPerosn = pd.getWriteMethod();
        
        //5.反射出方法上声明的注解
        InjectPerson inject = setPerosn.getAnnotation(InjectPerson.class);
        
        //6.得到注解上声明的信息,填充person对象
        //得到注解类的方法name(),age(),注意:注解类中的方法也可以叫其属性
        Method[] methods = inject.getClass().getMethods();

        for(Method m : methods){
            String methodName = m.getName();//方法的名字name or age
            try{
                //通过属性名得到Person类的属性
                Field f = Person.class.getDeclaredField(methodName);
                
                Object value = m.invoke(inject, null);  //得到注解上配置的属性的值,即”老张”和23
                f.setAccessible(true);
                f.set(person, value);//对person实例对象使用value进行填充
            }catch (Exception e) {
                continue;
            }
            
        }
        
        //7.把填充了数据的person通过setPerson方法整到personDao对象上
        PersonDao dao = new PersonDao();
        setPerosn.invoke(dao, person);
        System.out.println(dao.getPerson().getName());
    }
}

说明:以上就是模拟容器通过注解注入的过程,基本过程就是先得到要注入的属性,这里是一个类,然后得到属性的写方法(即PersonDao.java中),然后反射出其注解上的值,再使用这些值实例化相关对象,然后使用invoke方法将实例化的对象设置到PersonDao.java上。当然有时候注解是配置在属性上而不是在方法上,那么下面我们看这种情况是如何实现的。

方式二:
Test2.java

package cn.itcast.annotation2;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test2 {
    public static void main(String[] args) throws Exception {
        

        //1.得到需要注入的属性cn.itcast.annotation2.PersonDao.person
        Field f = PersonDao.class.getDeclaredField("person");
        
        //2.得到属性需要的类型cn.itcast.Person
        Class clazz = f.getType();
        
        //3.创建person
        Person person = (Person) clazz.newInstance();
        
        //4.反射属性的注解,
        InjectPerson inject = f.getAnnotation(InjectPerson.class);
        
        //5.并用注解的信息填充person

        //得到注解的方法name()和age()
        Method ms [] = inject.getClass().getMethods();
        for(Method m : ms){
            String methodName = m.getName();  //name age    看person对象上有没有注解与之对应的属性
            try{
                PropertyDescriptor pd = new PropertyDescriptor(methodName,Person.class);
                Method set = pd.getWriteMethod();  //setName setAge
                set.invoke(person, m.invoke(inject, null));//注解的方法是没有参数的,所以为null
            }catch (Exception e) {
                continue;
            }
        }
        
        //6.把person赋给dao
        PersonDao dao = new PersonDao();
        f.setAccessible(true);  //person
        f.set(dao, person);
        System.out.println(dao.getPerson().getAge());
        System.out.println(dao.getPerson().getName());
    }
}

说明:相关实现过程基本一样。

三、动态代理

3.1基本概念

其实前面我们已经讲过了,这里再详细说明一下。

  • java提供了一个proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:

    • 1.生成代理对象使用哪个类装载器
    • 2.生成哪个对象的代理对象,通过接口指定
    • 3.生成的代理对象的方法里是干什么事情的,由开发人员进行编写handler接口的实现来指定。
  • 初学者必须理解并记住:

    • 1.proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
    • 2.由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。

下面看一个简单的例子:
Person.java

package cn.itcast.proxy;
public interface Person {
    String sing(String name);
    String dance(String name);
}

Liyuchun.java

package cn.itcast.proxy;
public class Liyuchun implements Person {
    
    public String sing(String name){
        System.out.println("春哥唱" + name + "歌!!");
        return "飞吻!!";
    }
    
    public String dance(String name){
        System.out.println("春哥跳" + name + "舞!!");
        return "多谢多谢老板!!";
    }
}

LiyuchunProxy.java

package cn.itcast.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LiyuchunProxy {
    private Person chunchun = new Liyuchun();

    // Person person = LiyuechunProxy.getProxy();
    // person.sing("山歌") person.dance();
    public Person getProxy() {
        return (Person) Proxy.newProxyInstance(
                LiyuchunProxy.class.getClassLoader(), // 类装载器
                chunchun.getClass().getInterfaces(),// 代理的对象的接口
                new InvocationHandler() {// 需要干什么事情

                    /**
                     * proxy : 把代理对象自己传递进来 method:把代理对象当前调用的方法传递进来
                     * args:把方法参数传递进来
                     */
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        // 编码指定返回的代理对象干的工作
                        if (method.getName().equals("sing")) {
                            System.out.println("拿一万块钱来!!");
                            // 调用相关方法,把类和参数传递进去
                            return method.invoke(chunchun, args); // 找春哥唱歌
                        }
                        if (method.getName().equals("dance")) {
                            System.out.println("拿2万块钱来!!");
                            return method.invoke(chunchun, args); // 找春哥跳舞
                        }
                        return null;
                    }
                });
    }
}

测试:Test.java

package cn.itcast.proxy;

public class Test {
    public static void main(String[] args) {
        
        LiyuchunProxy proxy = new LiyuchunProxy();//new一个代理对象
        Person p = proxy.getProxy();//得到一个接口对象
        
        /*String value = p.sing("我爱你");
        System.out.println(value);*/
        
        String value = p.dance("跳舞");//使用接口去调用相关的方法
        System.out.println(value);
    }
}

3.2 应用

  • 在动态代理技术里,由于不管用户调用代理对象的什么方法,都是调用开发人员编写的处理器的invoke方法。
  • 并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这个两个特性,就可以实现一些特殊的需求。例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。同时,这种拦截手段较之前的拦截方式更为精细,因为它是在方法级别上的拦截。下面看几个例子(这些例子我们在过滤器中已经讲过,这里当作复习):

例1:对整个web的乱码过滤器CharacterEncodingFilter.java

public class CharacterEncodingFilter implements Filter {
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        
        final HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) resp;
        request.setCharacterEncoding("UTF-8");  //post
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        
        //request.getParamter()  requestProxy.getParameter()
       //我们不能直接将响应的数据显示在页面,所以需要拦截Request
        chain.doFilter((ServletRequest) Proxy.newProxyInstance
            (CharacterEncodingFilter.class.getClassLoader(),
              request.getClass().getInterfaces(), 
              new InvocationHandler(){

              public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                //若不是getParameter方法,则不需要处理
                if(!method.getName().equals("getParameter")){
                    return method.invoke(request, args);
                }
                
              //若不是get方式,则也不需要处理
                if(!request.getMethod().equalsIgnoreCase("get")){
                    return method.invoke(request, args);
                }
                
                //是get方式下的getParameter方法,需要处理
                String value = (String) method.invoke(request, args);
                if(value==null){
                    return null;
                }
                return new String(value.getBytes("iso8859-1"),"UTF-8");
            }
        }), response);  
    }

例2:压缩过滤器GzipFilter.java

public class GzipFilter implements Filter {
    public void destroy() {}

    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) resp;
        

        final ByteArrayOutputStream bout = new ByteArrayOutputStream();
        final PrintWriter pw = new PrintWriter(new OutputStreamWriter(bout,"UTF-8"));
        
        //response.getWriter().write("aaa");  responseProxy
        chain.doFilter(request, (ServletResponse)Proxy.newProxyInstance
        (GzipFilter.class.getClassLoader(), 
        response.getClass().getInterfaces(), 
        new InvocationHandler(){

            public Object invoke(Object proxy, Method method, Object[] args)
                    throws Throwable {
                if(method.getName().equals("getWriter")){
                    return pw; 
                }else if(method.getName().equals("getOutputStream")){
                    return new MyServletOutputStream(bout);
                }else{
                    return method.invoke(response, args);
                }
            }
        }));
        
        pw.close();
        byte result[] = bout.toByteArray();  //拿到目标资源的输出
        System.out.println("原始大小:" + result.length);
        
        ByteArrayOutputStream bout2 = new ByteArrayOutputStream();
        GZIPOutputStream gout = new GZIPOutputStream(bout2);
        gout.write(result);
        gout.close();
        
        byte gzip[] = bout2.toByteArray(); //拿到目标资源输出的压缩数据
        System.out.println("压缩大小:" + gzip.length);
        
        response.setHeader("content-encoding", "gzip");
        response.setContentLength(gzip.length);
        response.getOutputStream().write(gzip);
    }

    public void init(FilterConfig filterConfig) throws ServletException {}
}

class MyServletOutputStream extends ServletOutputStream{
    private ByteArrayOutputStream  bout = null;
    public MyServletOutputStream(ByteArrayOutputStream  bout){
        this.bout = bout;
    }
    @Override
    public void write(int b) throws IOException {
        bout.write(b);
    }
}

注意:要产生某个类的动态代理对象,那此类必须有一个接口,这也叫aop编程;如果一个类没有接口,那一般的方式是不能实现动态代理的,但是我们使用开源框架cglib则可以实现动态代理。但是如果一个类是final类型,则这种方式也不能完成动态代理,因为其是通过产生一个需要代理对象的子类来实现动态代理的,所以如果是final类型的,显然是不能实现的。那此时我们只能使用静态代理(包装设计模式)。

四、类加载器

  • 类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.class.Class对象。
  • 当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:
类加载器.png

其中BootStrap是对JDK中的核心类进行加载的加载器,ExtClassLoader是扩展包中的类进行加载的加载器,而AppClassLoader是对我们自己写的类进行加载的加载器。

4.1 BootStrap

  • bootstrap classloader是使用c语言实现的。引导(原始)类加载器,它负责加载java的核心类。这个加载器是非常特殊的,它实际上不是java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得此加载器加载了哪些核心类库:
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs(); 
for (int i = 0; i < urls.length; i++) { 
    System.out.println(urls[i].toExternalForm()); 
} 
  • 因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库。

4.2 ExtClassLoader

  • extension classloader扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中的jar包。这为引入除java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个jre中启动的JVM都是通用的,所以放入这个目录的jar类包对所有的JVM和system classloader都是可见的。

4.3 AppClassLoader

  • system classloader系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的jar类包和类路径。

  • 可以通过静态方法
    ClassLoader.getSystemClassLoader()找到该类的加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它父加载器。

4.4 全盘负责委托机制

  • classloader加载类用到是全盘负责委托机制。

  • 全盘负责:即是当一个classloader加载一个class的时候,这个class所依赖的和引用的其他class通常也由这个classloader负责载入。

  • 委托机制:先让父类加载器进行加载,只有在父类找不到的时候才从自己的类路径下寻找。

  • 类加载还采用了cache机制:如果cache中保存了这个class就直接返回它,如果没有才从文件中读取和转换成class,并存入cache,这就是为什么修改了class但是必须重新启动JVM才能生效,并且类只加载一次的原因。

  • 如果不实用委托机制,有时候会出现classCastException异常,如:
    Demo1 d1 = new Demo1();
    因为有可能定义Demo1这个属性和实例化Demo1这个类不是由同一个类加载器加载的,这样就会出现转换异常,比如tomcat中就没有使用此机制。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 本文章涉及代码已放到github上annotation-study 1.Annotation为何而来 What:A...
    zlcook阅读 29,081评论 15 116
  • 什么是注解(Annotation):Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和...
    九尾喵的薛定谔阅读 3,123评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,049评论 25 707
  • 上午八点钟,我和孩子出门去往艺术学校学习古筝。最近一周放暑假,她没有主动练习古筝而我也疏于管她。路上简单地和她谈谈...
    爱阅沈阳阅读 181评论 0 0
  • 早晨起来就一直下雨,下了一上午,雨停了,也一直阴沉着天… 下午放学回来,先让女儿吃饭,做了她爱吃青椒土豆...
    俩千金的妈阅读 243评论 0 0