Java进阶学习第24天——动态代理与类加载器

文档版本 开发工具 测试平台 工程名字 日期 作者 备注
V1.0 2016.06.17 lutianfei none

动态代理

  • 代理对象存在的价值:主要用于拦截对真实业务对象的访问。
  • 代理对象有什么方法?
    • 现在要生成某一个对象的代理对象,这个代理对象通常也要编写一个来生成,所以首先要编写用于生成代理对象的类。
  • 如何编写生成代理对象的类,两个要素:
    • 代理谁
    • 如何生成代理对象
  • 代理谁?
    • 设计一个类变量,以及一个构造函数,记住代理类 代理哪个对象。
  • 如何生成代理对象?
    • 设计一个方法生成代理对象(在方法内编写代码生成代理对象是此处编程的难点)
  • Java提供了一个Proxy类,调用它的newInstance方法可以生成某个对象的代理对象,使用该方法生成代理对象时,需要三个参数:
    • 1.生成代理对象使用哪个类装载器
    • 2.生成哪个对象的代理对象,通过接口指定
    • 3.生成的代理对象的方法里干什么事,由开发人员编写handler接口的实现来指定。
  • 初学者必须必须记住的2件事情:
    • Proxy类负责创建代理对象时,如果指定了handler(处理器),那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法。
    • 由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。

代理模式

  • 代理模式作用:

    • 屏蔽真实行为的访问,让程序更加安全。
    • 可以对真实行为的调用进行控制。
  • 通过一个案例:来说明代理的实现以及代理的作用
  • 代理模式实现:
  • 1.代理类与被代理类要实现同一个接口.
//潘金莲 ---被代理
public class Pjl implements KindWoman{

    public void throwEye(){
        System.out.println("潘金莲抛媚眼");

    }

    public void doSomething(){
        System.out.println("潘金莲。。。。。。。。");
    }
}

//王婆 ---代理
public class Wp implements KindWoman {

    private KindWoman woman;

    public Wp(KindWoman woman) {
        this.woman = woman;
    }

    public void throwEye() {
        woman.throwEye();

    }

    public void doSomething() {
        woman.doSomething();
    }

}
  • 2.在代理类中持有被代理对象.
public class Xmq {

    public static void main(String[] args) {

        KindWoman woman = new Pjl();

        Wp wp = new Wp(woman);

        wp.throwEye();//真实执行的是潘金莲,但是我们看不到,所以屏蔽了真实行为。
    }
}
  • 3.在代理类中调用被代理的行为。
    public void throwEye() {
        //在这里做操作,可以控制是否调用真实行为。

        woman.throwEye();

        //在这个位置,可以在真实行为调用完成后,在做操作。
    }

    public void doSomething() {
        woman.doSomething();
    }
}

AOP:面向切面的编程

  • AOP的底层实现就是通过动态代理来做到的。
  • 动态代理
    • 在代理模式基础上发展的,它不在是对单一的类型进行代理,而是可以对任意的一个实现了接口的类的对象做代理。

动态代理实现

  • 两种方式:

    • 1.通过jdk中提供的Proxy类来实现

      • 这种方式要求,被代理类必须实现接口,即只能为接口做代理.
    • 2.通过cglib来实现,它不要求实现接口。
  • 第一种方式的代码实现:
    • Proxy类中有一个方法newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h);
    • 参数:
      • loader: 要求,传递的是被代理类类加载器ClassLoader

        • 类加载器怎样获取:

          • 得到其Class对象,在Class类中提供一个方法getClassLoader();
      • interfaces: 要求:得到被代理对象所实现的接口的所有Class对象
        • 怎样获取所有实现接口的Class对象?

          • 得到其Class对象,在Class类中提供一个方法 getInterfaces();它返回的是Class[],就代表所实现接口的所有Class对象。
      • h: 它的类型是InvocationHandler,这是一个接口
        • InvocationHandler 是代理实例的调用处理程序 实现的接口。
  • InvocationHandler**接口中有一个方法**invoke;
    • 参数 proxy 就是代理对象
    • 参数 method 就是调用方法
    • 参数 args 就是调用的方法的参数
    • 返回值,就是真实行为执行后返回的结果,会传递给代理对象调用的方法.
      • public Object invoke(Object proxy, Method method, Object[] args);

  • eg:
public class Student implements Person {

    public String say(String message) {
        return "hello " + message;
    }

}

public class StudentProxyTest {

    public static void main(String[] args) {
        // 做Person接口实现类Student的动态代理。

        // 1.创建一个Student 被代理
        final Person s = new Student();

        // 2.得到s的代理对象.
        Person sproxy = (Person) Proxy.newProxyInstance(s.getClass()
                .getClassLoader(), s.getClass().getInterfaces(),
                new InvocationHandler() {
                    // 参数 proxy就是代理对象
                    // 参数method就是调用方法
                    // 参数args就是调用的方法的参数
                    // 返回值,就是真实行为执行后返回的结果,会传递给代理对象调用的方法.
                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {

                        // proxy,就是代理对象,我们一般不使用。
                        // method,就是要访问的方法。
                        // args 就是要访问的方法的参数
                        return method.invoke(s, args); // s.say("james");

                    }
                });
        String message = sproxy.say("james"); // 这个是代理对象调用say方法.

        System.out.println(message);
    }
}

动态代理案例1—实现编码过滤

public class LoginServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String username = request.getParameter("username");
        System.out.println(username);

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}
  • 2.操作
public class EncodingFilter implements Filter {

    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {

        // 1.强转
        final HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 2.操作
        // 创建一个req对象的代理对象reqProxy
        HttpServletRequest reqProxy = (HttpServletRequest) Proxy
                .newProxyInstance(req.getClass().getClassLoader(), req
                        .getClass().getInterfaces(), new InvocationHandler() {

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

                        // 1.得到方法名称
                        String methodName = method.getName();
                        if ("getParameter".equals(methodName)) {
                            String param = req.getParameter((String) (args[0]));

                            return new String(param.getBytes("iso8859-1"),
                                    "utf-8");

                        } else {
                            // 不是getParameter方法,就执行其原来操作.
                            return method.invoke(req, args);
                        }
                    }
                });
        // 3.放行
        chain.doFilter(reqProxy, resp);
    }

    public void destroy() {

    }

}

动态代理案例2–细粒度的权限控制

  • 数据库
create table users(
 id int primary key auto_increment,
 username varchar(40),
 password varchar(40)
);

insert into users values(null,‘aaa‘,‘111‘);
insert into users values(null,‘bbb‘,‘111‘);
insert into users values(null,‘ccc‘,‘111‘);

create table privileges(
  id int primary key auto_increment,
  name varchar(40)
);

insert into privileges values(null,‘添加图书‘);
insert into privileges values(null,‘修改图书‘);
insert into privileges values(null,‘查看图书‘);
insert into privileges values(null,‘删除图书‘);

多对多表关系
create table userprivilege(
  user_id int ,
  privilege_id int,
  foreign key(user_id) references users(id),
  foreign key(privilege_id) references privileges(id),
  primary key(user_id,privilege_id)
);

insert into userprivilege values(1,1);
  • 代码实现:
  • 1.完成登录操作,将user存储到session中.
    • login.jsp LoginServlet UserService UserDao.
  • 2.登录成功,跳转到book.jsp页面。
    • 在这个页面上有四个超连接,访问的是同一个servlet(BookServlet)
    • 问题:怎样让一个servlet处理多个请求?
      • 可以通过在请求,携带参数来判断要做什么操作
<a href="${pageContext.request.contextPath}/book?method=add">book add</a>
<br>
<a href="${pageContext.request.contextPath}/book?method=update">book update</a>
<br>
<a href="${pageContext.request.contextPath}/book?method=delete">book delete</a>
<br>
<a href="${pageContext.request.contextPath}/book?method=search">book search</a>
  • 在servlet中判断method值是什么,调用不同的请求处理方法.

    • 这种方式下,在做权限控制时,如果使用url级别权限控制,就不能通过判断请求的资源路径来处理。
  • 可以使用细粒度权限控制:
    • 实现原理:使用注解+动态代理来完成。
    • 注解:它用于定义当前行为的访问需要什么权限。
    • 动态代理帮助我们完成控制拦截。简单说,就是在代理中,会判断当前用户是否具有访问该行为的权限,如果有会调用被代理的行为,如果没有,不调用行为,直接抛出权限不足。
  • 3.实现权限控制

    • 1.创建一个BookInfo注解,它是用于描述行为访问时,需要什么权限的.
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Inherited
    public @interface BookInfo {

    String value(); //这就是权限名称
    }
  • 2.在BookServiceFactory中进行权限控制

    • 1.得到当前行为访问需要的权限名称

      • BookInfo bif = method.getAnnotation(BookInfo.class);
      • String pname = bif.value();
    • 2.得到当前登录的用户
      • 我们在所有的service的方法上添加了一个User参数。
      • 那么我们获取时,就可以直接通过invoke方法的args参数获取.
        • User user = (User) args[0];
    • 1.首先判断用户是否存在,也就是判断它是否登录了。
    User user = (User) args[0];

    if (user == null) {
        throw new RuntimeException("没有登录,请登录后操作");
    }
* 2.如果登录了,根据用户查询数据库,得到这个用户所具有的所有权限名称
    * 使用ColumnListHandler进行封装查询结果。
SELECT
    privileges.name
FROM
    users,PRIVILEGES,userprivilege
WHERE
    users.id=userprivilege.user_id
AND
    privileges.id=userprivilege.privilege_id
AND
    users.id=?";
QueryRunner runner = new QueryRunner(DataSourceUtils
        .getDataSource());

List<Object> pnames = runner.query(sql,
        new ColumnListHandler(), user.getId());
  1. 判断用户是否具有权限
                // 真实行为访问前--判断用户是否有权限执行当前行为
                boolean flag = method.isAnnotationPresent(BookInfo.class);

                if (!flag) {
                    // 不需要权限
                    return method.invoke(service, args);

                }

if (pnames.contains(pname)) {

                    Object obj = method.invoke(service, args);

                    // 真实行为访问 后
                    return obj;
                } else {
                    throw new RuntimeException("权限不足");

类加载器

  • 什么是类加载器,有什么作用?

    • 类加载器的作用就是将java中的字节码文件(.class文件)转换成Class对象。
    • 当 JVM 启动时,会形成由三个类加载器组成的初始类加载器层次结构:
      • 1.引导类加载器 BootStrap jre/lib/rt.jar
      • 2.扩展类加载器 ExtClassLoader JRE/lib/ext/*.jar
      • 3.应用类加载器(系统类加载器) AppClassLoader SystemClassLoader CLASSPATH指定的所有jar或目录
    • 在java中ClassLoader代表类加载器,所有的类加载器都是ClassLoader的子.
  • bootstrap classloader:引导(也称为原始)类加载器,它负责加载Java的核心类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int  i = 0; i < urls.length; i++) {
  System.out.println(urls[i].toExternalForm());
}
  • 因为JVM在启动的时候就自动加载它们,所以不需要在系统属性CLASSPATH中指定这些类库

  • 演示类加载器

    • 问题:类加载器如果获取?

      • 在Class类中有一个方法 getClassLoader()它返回的就是一个类加载器.
  • 1.获取引导类加载器
    • ClassLoader cl = String.class.getClassLoader();
    • System.out.println(cl);
    • 结果是null.
    • 原因:引导类加载器特殊,它根本就不是java实现。所有在得到引导类回载器是结果就是null.
  • 2.扩展类加载器
    • ClassLoader cl = AccessBridge.class.getClassLoader();
    • System.out.println(cl); //[email protected]
  • 3.应用类加载器
    • ClassLoader cl = this.getClass().getClassLoader();
    • System.out.println(cl); //[email protected]

全盘负责委托机制

  • 全盘负责:即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的其它Class通常也由这个classloader负责载入。
  • 委托机制:先让parent(父)类加载器 寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
  • 类加载还采用了cache机制:如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么修改了Class但是必须重新启动JVM才能生效,并且类只加载一次的原因。

自定义类加载器

  • 创建了一个类 javax.activation.MimeType,在这个类中有一个方法show();当jvm加载这个类时,因为在rt.jar包下也存在一个MimeType类,并且包名都一样,这时jvm就会使用引导类加载器加载这个类,而我们想得到的其实是应该由应用类加载器加载的Class.
  • 解决方案:自定义类加载器.
    • 1.创建一个类,去继承自ClassLoader
    • 2.重写findClass方法,在这个方法中通过defineClass将一个.class文件转换成Class对象.

泛型反射

  • 问题:在BaseDaoImpl类中需要得到当前这个类上的泛型的Class对象,而直接通过T.class这是不对的.
  • 怎样得到当前这个类上的泛型的Class?
    • Type type = this.getClass().getGenericSuperclass(); // 得到当前类上的泛型–父类型
    • Type[] params = ((ParameterizedType) type).getActualTypeArguments(); // 得到当前类上所有的泛型类型Class
    • clazz = (Class) params[0];

时间: 2024-08-26 05:31:20

Java进阶学习第24天——动态代理与类加载器的相关文章

Java反射学习总结四(动态代理使用实例和内部原理解析)

通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加相应的代理类.解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成. 动态代理知识点: Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类: 1.InvocationHandler接口:    

day19_java基础加强_动态代理+注解+类加载器

一.动态代理 1.1.代理模式 ? ? 什么是代理模式及其作用? ? ? ? ? Proxy Pattern(即:代理模式),23种常用的面向对象软件的设计模式之一.? ? ? ? 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.? ? ? ? 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用.? ? 优点:? ? ? ? (1) 职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件

java JDK8 学习笔记——第17章 反射与类加载器

第十七章 反射与类加载器 17.1 运用反射 反射:.class文档反映了类基本信息,从Class等API取得类信息的方式称为反射. 17.1.1 Class与.class文档 1.java.lang.Class的实例代表Java应用程序运行时加载的.class文档,类.接口.Enum等编译过后,都会生成.class文档.Class类没有公开构造函数,实例时候JVM自动产生,每个.class文档加载时,JVM会自动生成对应的Class对象. 2.取得Class对象的方式: (1)通过Object

Java反射—运用反射生成jdk动态代理

1.  核心类&接口 在Java的java.lang.reflect包下提供一个Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成jdk动态代理类或动态代理对象. Proxy是所有动态代理类的父类,它提供了两个静态方法来创建动态代理类和动态代理对象,如下: ?  static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) ?  static Objec

java 反射提取类信息, 动态代理 和过滤某些方法演示

package org.rui.classts.reflects; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.regex.Pattern; /** * 提取 类的 方法 构造器 * @author lenovo * */ //{args:ShowMethods} //查看一个类的所有方法和构造器 public class ShowMethods { private

Java进阶学习(2)——log4j的学习和使用

Java进阶学习(2)--log4j的学习和使用 简介Loj4j Log4j的组成 Log4j主要由三大组组件构成: Logger: 负责生成日志,并能够对日志信息进行分类筛选,通俗的讲就是决定什么日志信息应该被输出,什么日志信息应该被忽略. Appender: 定义了日志信息输出的目的地,指定日志信息应该被输出到什么地方,这些地方可以是控制台.文件或网络设备等. Layout: 指定日志信息的输出格式. 说明: 一个Logger可以有多个Appender,这意味着日志信息可以被输出到多个设备上

java进阶学习计划

断断续续使用java也已经有两年了,算是最熟悉的开发工具了.但写的代码都是以项目为导向,追求work around,还需要打好基础才能长远发展. 大致的进阶学习计划, 阶段1:深究java语法,阅读常用库的jdk源码,了解jvm机制; 阶段2:阅读基于java的开源框架源码,各种framework,container. 希望可以坚持下来,经常更新技术博客. java进阶学习计划

Java:进阶学习(1)——网络编程

Java:进阶学习(1)--网络编程 基础 Socket与ServerSocket Socket又称"套接字",网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket. Socket方法 getInetAddress();    远程服务端的IP地址 getPort();    远程服务端的端口 getLocalAddress()    本地客户端的IP地址 getLocalPort()    本地客户端的端口 getInputStream();   

由浅入深分析mybatis通过动态代理实现拦截器(插件)的原理

最近在用mybatis做项目,需要用到mybatis的拦截器功能,就顺便把mybatis的拦截器源码大致的看了一遍,为了温故而知新,在此就按照自己的理解由浅入深的理解一下它的设计. 和大家分享一下,不足和谬误之处欢迎交流.直接入正题. 首先,先不管mybatis的源码是怎么设计的,先假设一下自己要做一个拦截器应该怎么做.拦截器的实现都是基于代理的设计模式设计的,简单的说就是要创造一个目标类的代理类,在代理类中执行目标类的方法并拦截执行拦截器代码. 那么我们就用JDK的动态代理设计一个简单的拦截器