动态代理&类加载器&注解

1. 动态代理【掌握】

1.1 简介

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用

优点:

(1).职责清晰 
真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,
通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。

(2).代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的作用。

(3).高扩展性
结构
一个是真正的你要访问的对象(目标类),另一个是代理对象,
真正对象与代理对象实现同一个接口,由代理控制目标对象的一切方法。

1.2 创建动态代理的步骤

  • 目标对象必须要实现一个接口,代理对象实现这个接口从而能够实现这些方法。

  • 创建动态代理对象,传入三个参数。

      1. 参数1:加载目标类的类加载器。web工程发布后由类加载器web-inf的classes中,从而能够找到该目标类 。
    
      2. 参数2:目标对象实现的接口。
    
      3. 参数3:代理类对目标对象方法的控制器。new InvocationHandler(proxy,method,args);
    
  • new InvocationHandler(Object proxy,Method method,Parameter args)对目标对象方法的控制。在这里可以进行方法增强等操作。

      1. 参数1: proxy,当前代理对象的引用。一般不使用它
    
      2. 参数2: method,调用方法时该方法的字节码对象。
    
      3. 参数3: args,调用方法时传递进来的实际参数  
    

例:

1. 要实现的共同接口:

public interface TargetInterface {
    public abstract void method1();
    public abstract String method2();
    public abstract int method3(int num);
}

2. 被代理的目标类

//代理对象和目标对象必须实现同一个接口,即动态代理对象必须成为共同接口的实现类对象,这样就具备了共同的方法
//在jdk中,动态代理和目标对象必须共同实现一个接口,才能实现动态代理
public class Target implements TargetInterface {

    public void method1() {
        System.out.println("method1");
    }

    public String method2() {
        System.out.println("method2");
        return "method2";
    }

    public int method3(int num) {
        System.out.println("method3");
        return num;
    }

}

3. 代理类并测试

public class ProxyTest {
    public static void main(String[] args) {

        final Target target = new Target();

        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(//
                target.getClass().getClassLoader(), // 要代理的目标对象的类加载器
                target.getClass().getInterfaces(), // 和目标对象实现共同的接口
                new InvocationHandler() { // 调用目标对象的某个方法
                    // 参数1:proxy : 动态代理对象
                    // 参数2:method : 要调用的目标对象的方法的字节码对象
                    // 参数3:args : 调用目标对象的方法时传递的参数
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 动态代理调用目标函数的方法
                        Object invoke = method.invoke(target, args);
                        return invoke;// 返回调用目标函数方法后的返回值
                    }
                });

        // 使用动态代理对象调用方法
        proxy.method1();// 使用目标函数对象,需要将动态代理的类型从Object强转为接口对象的实现子类(多态)
        String method2 = proxy.method2();
        int method3 = proxy.method3(100);

        System.out.println(method2);
        System.out.println(method3);
    }
}

1.3 使用动态代理解决全站编码问题

1. 分析

1. 首先创建一个表单页面和一个servlet小程序用于测试。

2. 要对全站编码进行控制,需要经过Filter过滤器进行控制,过滤路径url-pattern设为/*即对所有请求进行拦截。

3.需求:我们解决的是servlet中获取参数的getParameter()方法时及servlet写回数据到客户端时的乱码问题。

4. 使用动态代理解决,首先找到需要被代理的对象是HttpServletRequest的实现类对象.
    1. 创建一个动态代理。
    2. 获取目标对象的类加载器,与目标对象实现同一个接口HttpServletRequest。
    3. 在new InvocationHandler()方法控制器中找到getParameter()方法进行增强,在这里面解决乱码逻辑。
    4. 对于其他无需改变的方法,不进行改变,按原来的逻辑返回。

5. 放行(这里的参数request要改为动态代理对象proxy了,这样servlet调用getParameter方法才是增强后的)。

2. 代码实现

这里省略jsp页面和servlet程序的代码,就是一个简单的jsp表单提交和回写数据

Filter的动态代理代码
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
        throws IOException, ServletException {
    // 需求: 使用动态代理解决全站编码问题

    // 1. 强制转换
    final HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) resp;
    response.setContentType("text/html;charset=UTF-8");// 设置服务器写回客户端的编码格式

    // 2. 业务逻辑 : 使用动态代理解决全站编码问题
    // 2.1 创建动态代理对象,返回的是和目标对象实现了同一接口的动态代理对象
    HttpServletRequest proxy = (HttpServletRequest) Proxy.newProxyInstance( //
            request.getClass().getClassLoader(), // 参数1:加载目标对象的类加载器
            request.getClass().getInterfaces(), // 参数2:与目标对象实现相同的接口
            new InvocationHandler() {// 参数3:目标对象的方法控制器,即如何代理?

                // 参数1:proxy:就是当前的动态代理对象
                // 参数2:method:代理对象调用目标对象的某个方法的字节码对象
                // 参数3:args:调用某个方法时传递的参数
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 判断找出getParameter()方法进行方法曾倩
                    if ("getParameter".equals(method.getName())) {
                        // 解决编码集的代码
                        // 获取表单提交的方式
                        String requestMethod = request.getMethod();
                        if ("GET".equalsIgnoreCase(requestMethod)) { // 如果是get方式提交数据
                            // 获取提交的数据
                            String parameter = (String) method.invoke(request, args);
                            // 对获取的数据重新编解码
                            parameter = new String(parameter.getBytes("ISO-8859-1"), "UTF-8");
                            return parameter;
                        } else if ("POST".equalsIgnoreCase(requestMethod)) {
                            request.setCharacterEncoding("UTF-8");
                        }
                    }
                    // 对于不需增强的方法,按原来的逻辑执行后返回,不做任何修改
                    return method.invoke(request, args);
                }
            });

    // 3. 放行(将与request实现了同一接口的动态代理作为参数传递放行)
    chain.doFilter(proxy, response);
}

2. 注解【了解】

2.1. 简介

  • 定义 : Annotation. 也叫元数据.一种代码级别的说明. 它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次. 它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释
  • 比喻 : 注释是给成员看的.注解是给java编译器看的
  • 作用
    • 编译检查:通过代码里标识的元数据让编译器能够实现基本的编译检查,例如Override
    • 代码分析:通过代码里标识的元数据对代码进行分析,可以用来替代xml.例如3.0版本的Web工程就不需要使用web.xml对Servlet进行配置
    • 编写文档:通过代码里标识的元数据生成文档, 例如JDK文档

2.2. 常见的注解

  • @SuppressWarnings : 忽略警告
  • @Override : 编译器在检查这段代码时,检查方法名和父类是否一致
  • @Deprecated : 方法是否过期

2.3. 自定义注解

  • 注解和类、接口是一样级别的,只不过它的作用更多的是对代码进行说明
  • <font color='red'>实现步骤
    • 创建一个接口,并在interface之前添加一个@

        // 自定义注解
        public @interface MyAnno {
        
        }
        // 使用注解
        public class Test1 {
            @MyAnno
            public static void main(String[] args) {
                String aString;
            }
        }   
      
    • 添加属性

        // 自定义注解
        public @interface MyAnno {
            // 声明一个String类型的属性
            String name();
            // 声明一个int类型的属性
            int age();
        }
        // 使用注解
        public class Test1 {
            @MyAnno(name = "zhangsan", age = 18)
            public static void main(String[] args) {
                String aString;
            }
        }   
      
    </font>
  • 常用的注解属性类型
    • 基本数据类型
    • String类型
    • 以上类型的数组
  • 如果属性值只有一个,并且恰好为value,那么使用的时候,可以不写属性名
    • 示例

        // 自定义注解
        public @interface MyAnno {
            // 声明一个String类型的属性
            String value();
        }
        // 使用注解
        public class Test1 {
            @MyAnno("zhangsan")
            public static void main(String[] args) {
                String aString;
            }
        }   
      

2.4. 元注解

  • 定义 : 说明注解的注解

  • @Retention()

    • 作用 : 指定注解的保留阶段(生命周期)

    • 常量值

      • RetentionPolicy.SOURCE : 只存在于源码中
      • RetentionPolicy.CLASS : 默认值.保留到字节码阶段.
      • RetentionPolicy.RUNTIME : 保留到运行阶段

      源码阶段--->>字节码阶段--->>运行阶段

  • @Target()

    • 作用 : 指定注解的使用位置
    • 常量值
      • ElementType.CONSTRUCTOR : 用于描述构造器
      • ElementType.FIELD : 用于描述字段
      • ElementType.LOCAL_VARIABLE : 用于描述局部变量
      • ElementType.METHOD : 用于描述方法
      • ElementType.PACKAGE : 用于描述包
      • ElementType.PARAMETER : 用于描述参数
      • ElementType.TYPE : 用于描述类、接口(包括注解类型) 或enum声明

2.5. 注解解析

  • API

    • 判断一个类是否使用了一个注解 : 类名.class.isAnnotationPresent(注解类名.class)
    • 解析注解 : 类名.class.getAnnotation(注解类名.class)
  • 示例代码

      // 判断某一个类是否使用了一个注解
      // 格式: 类名.class.isAnnotationPresent(注解类名.class)
      // 如下所示,判断Person类是否使用了MyAnno注解
      boolean isPresent = Person.class.isAnnotationPresent(MyAnno.class);
      if (isPresent) {
          // 解析注解
          // 格式 : 类名.class.getAnnotation(注解类名.class)
          // 如下所示: 解析Person使用的MyAnno注解
          MyAnno anno = Person.class.getAnnotation(MyAnno.class);
          // 获取注解具体字段的值
          String value = anno.address();
          System.out.println(value);
      }
    

2.6. 案例的实现

自定义注解必须定义@Retention和@Target的元注解,否则注解到不了运行阶段


// 1.自定义注解类
@Target(value = { ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface MTest {
}

// 2.使用自定义注解的类
public class DAOTest {
    public void aa() {
        System.out.println("aa...");
    }
    // 在方法bb上使用自定义注解.模拟@Test注解
    @MTest
    public void bb() {
        System.out.println("bb...");
    }
}

// 3.模拟Junit的运行
public class Test {
    // 测试类, 模拟@Test注解的运行
    // @Test注解其实是插件+反射执行实现的
    // 我们只能模拟反射执行的过程
    public static void main(String[] args)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
        // 获取类的字节码
        Class clazz = DAOTest.class;
        // 获取所有本类自己声明的方法
        Method[] methods = clazz.getDeclaredMethods();
        // 遍历方法
        for (Method method : methods) {
            // 判断方法是否使用了MTest注解
            if (method.isAnnotationPresent(MTest.class)) {
                // 执行方法
                method.invoke(clazz.newInstance());
            }
        }
    }
}

3.类加载器【了解】

  • 定义 :
    • Java类的加载是由虚拟机来完成的
    • 虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制
    • JVM中用来完成上述功能的具体实现就是类加载器.
    • 类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象.
  • 分类
    • Bootstrap ClassLoader : 将jre\lib目录下的类库加载到虚拟机内存中,用来加载java的核心库,不能被java程序直接调用,代码是使用C++编写的.是虚拟机自身的一部分

    • Extension ClassLoader : 加载jre\lib\ext目录下的类库,用来加载java的扩展库,开发者可以直接使用这个类加载器.

    • App ClassLoader : 加载我们编写的java类,以及第三方的jar

    • Custom ClassLoader : 自定义的类加载器,以满足特殊的需求,如tomcat就根据J2EE规范自行实现ClassLoader

    • 测试代码

        public static void main(String[] args) {
            // 当前当前类的类加载器 : AppClassLoader
            System.out.println(MyLoader.class.getClassLoader().getClass().getName());
            // 获取当前类的类加载器/的父类 : ExtClassLoader
            System.out.println(MyLoader.class.getClassLoader().getParent().getClass().getName());
            // 获取当前类的类加载器/的父类/的父类 : Bootstrap ClassLoader
            System.out.println(MyLoader.class.getClassLoader().getParent().getParent());
            // List在rt.jar中,由Bootstrap ClassLoader进行加载
            System.out.println(List.class.getClassLoader().getClass().getName());
        }
      
  • 双亲委托机制
    • 定义 : 是一种组织类加载器之间关系的一种规范
    • 作用 : 规定类如何被加载
    • 工作原理 :
      • 如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成
      • 这样层层递进,最终所有的加载请求都被传到最顶层的启动类加载器中,只有当父类加载器无法完成这个加载请求(它的搜索范围内没有找到所需的类)时,才会交给子类加载器去尝试加载.
    • 优点 :
      • Java类随着它的类加载器一起具备了带有优先级的层次关系.这是十分必要的
      • 比如java.langObject,它存放在\jre\lib\rt.jar中,它是所有java类的父类
      • 因此无论哪个类加载都要加载这个类,最终所有的加载请求都汇总到顶层的启动类加载器中,这样Object类会由启动类加载器来加载,所以加载的都是同一个类
      • 如果不使用双亲委派机制,由各个类加载器自行去加载的话,系统中就会出现不止一个Object类,应用程序就会全乱了
类加载器.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 作者:成 富, 软件工程师, IBM 中国软件开发中心 类加载器(class loader)是 Java™中的一个...
    Android技术研究阅读 3,898评论 0 74
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,494评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,493评论 18 399
  • 也许你不相信,白皙的皮肤也能吃出来! 所谓“一白遮百丑”,美白是女人求美的永恒话题,特别是中国女人。长久以来,中国...
    绛河清浅阅读 547评论 0 3
  • 最近很忙,很长时间都没动过笔了。夜深人静的时候,辗转反侧,突然觉得自己好像丢了什么,拼命想啊想,又感觉自己什么...
    长街冷阅读 169评论 0 0