黑马程序员--动态代理

本文详细阐述了动态代理的概念、结构、原理及在Spring AOP框架中的应用实例,包括如何通过代理类增强现有接口的功能,以及如何在Spring中配置并使用动态代理来实现面向切面编程。通过具体的示例代码,展示了如何利用动态代理提高系统的灵活性和可维护性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

               ------------android培训java培训、期待与您交流!--------------

大纲:

    一、代理概述

    二、代理结构图

    三、动态代理

    四、动态创建代理对象的三种方式

    五、InvocationHandler对象的运行原理

    六、动态代理的工作原理图

    七、经典动态代理示例

    八、实现类似spring的可配置的AOP框架


一、代理概述

要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,你准备如何做?这就需要编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。 如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

二、代理结构图


三、动态代理

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,工作量非常大! JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。 JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。 CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后

四、动态创建代理对象的三种方式

第一种方式:

Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
		System.out.println(clazzProxy1.getName());
                //结果是com.sun.proxy.$Proxy0,即代理类的类名
		Constructor constructor = clazzProxy1.getConstructor(InvocationHandler.class);
		class MyInvocationHander1 implements InvocationHandler{
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				// TODO Auto-generated method stub
				return null;
			}
		}
		Collection proxy1 = (Collection)constructor.newInstance(new MyInvocationHander1());
		System.out.println(proxy1);<div align="left"><pre name="code" class="java">                //结果:null
                proxy.clear();
               //执行没有返回值的方法,不会报告异常
                proxy.size();
              //执行有返回值的方法,会报告异常

 System.out.println(proxy1);结果为null,不是proxy1对象为空,而是因为proxy1调用的toString方法返回值为空,从Object对象继承过来的方法有三个是调用的invoke方法,分别是:hashcode、equals、toString方法,由于invoke方法返回是null,所以toString返回null。proxy1.size()返回值是int类型与null不匹配,所以报告异常。 

第二种方式(匿名内部类):

   Class clazzProxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class );
            Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class) ;
            Collection proxy = (Collection)constructor.newInstance(new InvocationHandler(){
                   public Object invoke(Object proxy, Method method, Object[] args)
                               throws Throwable {
                         return null ;
                  }
            });
            System. out.println(proxy);
第三种方式(最常用):

 Collection proxy = (Collection)Proxy.newProxyInstance(
                        Collection.class.getClassLoader(),
                         new Class[]{Collection.class},
                         new InvocationHandler() {
                              ArrayList target = new ArrayList();
                               public Object invoke(Object proxy, Method method, Object[] args)
                                           throws Throwable {
                                    long beginTime = System.currentTimeMillis();
                                    Object retVal = method.invoke(target,args);
                                    long endTime = System.currentTimeMillis();
                                    System. out.println(method.getName() + " running out of " + (endTime - beginTime));
                                    return retVal;
                              }
                        }
            );
            proxy.add("zxx");
            proxy.add("lhm");
            proxy.add("bxd");
            System. out.println(proxy.size());

            //结果:3

注意:
如果作为target的ArrayList对象放置在invoke方法内部定义,那么每次调用代理的某个方法,都会调用invoke方法,这样作为target的ArrayList对象每次都会被创建,这样就导致最后调用proxy.size()的时候,结果为0。

五、InvocationHandler对象的运行原理

动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。
1、动态类构造方法

$Proxy0 implements Collection
{
     InvocationHandler handler;
     public $Proxy0(InvocationHandler handler)
     {
          this.handler = handler;
     }
}

2、动态类里的add方法:

Class Proxy$ {
add(Object object) {
              return handler.invoke(Object proxy, Method method, Object[] args);
}
}

3、动态类里的方法调用示例:

$Proxy0 implements Collection
 {
      InvocationHandler handler;
      public $Proxy0(InvocationHandler handler)
      {
           this.handler = handler;
      }
      //生成的Collection接口中的方法的运行原理
     int size()
      {
           return handler.invoke(this,this.getClass().getMethod("size"),null);
      }
      void clear(){
           handler.invoke(this,this.getClass().getMethod("clear"),null);
      }
      boolean add(Object obj){
           handler.invoke(this,this.getClass().getMethod("add"),obj);
      }
 }

六、动态代理的工作原理图


七、经典动态代理示例

将创建代理的过程改为一种更优雅的方式,eclipse重构出一个getProxy方法绑定接收目标同时返回代理对象,让调用者更懒惰,更方便,调用者甚至不用接触任何代理的API。 将系统功能代码模块化,即将切面代码也改为通过参数形式提供,怎样把要执行的系统功能代码以参数形式提供? 把要执行的代码装到一个对象的某个方法里,然后把这个对象作为参数传递,接收者只要调用这个对象的方法,即等于执行了外界提供的代码!为方法增加一个Advice参数。
public interface Advice {
       void beforeAdvice(Method method);
       void afterAdvice(Method method);
}

public class MyAdvice implements Advice {
       private long beginTime = 0;
       public void afterAdvice(Method method) {
             long endTime = System.currentTimeMillis();
             System. out .println(method.getName() + " running out of " + (endTime - beginTime));
      }
       public void beforeAdvice(Method method) {
             beginTime = System.currentTimeMillis();
      }
}

public class ProxyTest {
    public static void main(String[] args) throws Exception {
       final ArrayList target = new ArrayList();
       Collection collection = (Collection) getProxy(target,new MyAdvice());
       collection.add("zxx" );
       //结果:add running out of 0
    }

    public static Object getProxy(final ArrayList target,final Advice advice) {
          Object proxy = Proxy. newProxyInstance(
                      target.getClass().getClassLoader(),
                      target.getClass().getInterfaces(),
                      new InvocationHandler() {
                            public Object invoke(Object proxy, Method method, Object[] args)
                                        throws Throwable {
                               advice.beforeAdvice(method);
                                 Object retVal = method.invoke(target ,args);
                                 advice.afterAdvice(method);
                                 return retVal;
                           }
                     }
         );
         return proxy;
    }
}

八、实现类似spring的可配置的AOP框架

工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice

ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
目标
通知
编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置
调用BeanFactory获取对象

ProxyFactoryBean

package com.itheima.day3.aopframework;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import cn.itcast.day3.Advice;

public class ProxyFactoryBean {
	private Advice advice;
	private Object target;

	public Advice getAdvice() {
		return advice;
	}

	public void setAdvice(Advice advice) {
		this.advice = advice;
	}

	public Object getTarget() {
		return target;
	}

	public void setTarget(Object target) {
		this.target = target;
	}

	public Object getProxy() {
		Object proxy = Proxy.newProxyInstance(target.getClass()
				.getClassLoader(), target.getClass().getInterfaces(),
				new InvocationHandler() {
					public Object invoke(Object proxy, Method method,
							Object[] args) throws Throwable {
						advice.beforeMethod(method);
						Object retVal = method.invoke(target, args);
						advice.afterMethod(method);
						return retVal;
					}
				});
		return proxy;
	};
}

BeanFactory

package com.itheima.day3.aopframework;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import cn.itcast.day3.Advice;

public class BeanFactory {
	Properties props = new Properties();
	public BeanFactory(InputStream ips) {
		try {
			props.load(ips);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public Object getBean(String name) {
		String className = props.getProperty(name);
		Object bean = null;
		try {
			Class clazz = Class.forName(className);
			bean = clazz.newInstance();
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (bean instanceof ProxyFactoryBean) {
			ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean) bean;
			Object proxy = null;
			try {
				Advice advice = (Advice) Class.forName(
				props.getProperty(name + ".advice")).newInstance();
				Object target = Class.forName(
				props.getProperty(name + ".target")).newInstance();
				proxyFactoryBean.setAdvice(advice);
				proxyFactoryBean.setTarget(target);
				proxy = proxyFactoryBean.getProxy();
			} catch (Exception e) {
				e.printStackTrace();
			}

			return proxy;
		}
		return bean;
	}
}
AopFrameworkTest

package com.itheima.day3.aopframework;
import java.io.InputStream;

public class AopFrameworkTest {
	public static void main(String[] args) throws Exception {
		InputStream ips = AopFrameworkTest.class
				.getResourceAsStream("config.properties");
		Object bean = new BeanFactory(ips).getBean("xxx");
		System.out.println(bean.getClass().getName());
	}
}

config.properties
xxx= java.util.ArrayList
//结果:java.util.ArrayList
config.properties
xxx=com.itheima.day3.aopframework.ProxyFactoryBean
xxx.advice= com.itheima.day3.MyAdvice
xxx.target= java.util.ArrayList
//结果:$Proxy0



               ------------android培训java培训、期待与您交流!--------------

### JavaEE 黑马程序员 学习资料 教程 下载 JavaEE 是企业级应用开发的重要技术栈,而黑马程序员提供了丰富的学习资源和教程,帮助开发者掌握 JavaEE 的核心技术和框架。以下是关于 JavaEE 黑马程序员学习资料的相关信息: #### 1. 黑马程序员官方课程资源 黑马程序员JavaEE 课程通常包括以下内容: - **基础部分**:涵盖 Java 基础、面向对象编程、集合框架等内容[^1]。 - **核心框架**:Spring 框架的学习,包括 Spring AOP、Spring MVC 等模块[^2]。 - **Web 开发**:涉及 Servlet、JSP、Filter 等 Web 技术[^3]。 - **数据库操作**:MyBatis、Hibernate 等 ORM 框架的使用。 - **分布式与微服务**:Dubbo、Spring Cloud 等微服务框架的学习。 这些课程资源通常可以通过黑马程序员的官方网站或其合作平台(如 Bilibili、优快云)获取。 #### 2. JDK 动态代理与 Spring AOP 在 JavaEE 开发中,Spring AOP 是一个重要的知识点。它默认使用 JDK 动态代理来实现代理功能。通过 `java.lang.reflect.Proxy` 类,可以调用 `newProxyInstance()` 方法创建代理对象[^2]。这种代理机制的优点在于可以在不修改原始代码的情况下增强某些方法的功能,实现无侵入式的代码扩展。 #### 3. SpringMVC 请求映射 SpringMVC 是 JavaEE 开发中的一个重要框架,用于处理 HTTP 请求和响应。通过 `@Controller` 和 `@RequestMapping` 注解,可以定义请求的映射路径[^3]。例如,以下代码展示了如何设置请求映射路径: ```java @Controller @RequestMapping("/user") // 类请求映射的路径 public class UserController { @RequestMapping("/commonParam") // 方法请求映射的路径 @ResponseBody public String commonParam(String name, int age) { System.out.println("name参数传递:" + name); System.out.println("age参数传递:" + age); return "success"; } } ``` #### 4. 学习资源下载 如果需要下载 JavaEE 相关的黑马程序员学习资料,可以尝试以下途径: - **官方网站**:访问黑马程序员官网,注册账号后可以下载相关课程资料。 - **第三方平台**:如 Bilibili、优快云、GitHub 等,搜索关键词“黑马程序员 JavaEE”可能找到免费或付费的课程资源。 - **论坛与社区**:加入 Java 开发者论坛或 QQ 群,与其他开发者交流并获取学习资料。 #### 5. 注意事项 在下载和使用学习资料时,请确保遵守版权法规。如果选择购买正版课程,可以获得更系统的学习体验和技术支持。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值