一 代理设计模式
本文讲解内容的源码下载链接 : http://onl5wa4sd.bkt.clouddn.com/SpringAopPro.rar
1 代理模式的定义
为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
是是个是真正的你要访问的对象(目标类),一个是代理对象,真正对象与代理对象实现同一个接口,先访问代理类再访问真正要访问的对象。
主要解决:直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
关键代码: 实现与被代理类组合。
使用场景:
租房子的时候去找中介
类图 :
2 为什么需要代理设计模式
假设需实现一个计算的类Math、完成加、减、乘、除功能,如下所示:
Math.java
package com.xinping;
public class Math {
//加
public int add(int param1, int param2){
int result = param1 + param2;
System.out.println(param1 + "+" + param2 +"=" + result);
return result;
}
//减
public int sub(int param1, int param2){
int result = param1 - param2;
System.out.println(param1 + "-" + param2 +"=" + result);
return result;
}
//乘
public int mul(int param1, int param2){
int result = param1 * param2;
System.out.println(param1 + "*" + param2 +"=" + result);
return result;
}
//除
public int div(int param1, int param2){
int result = param1 / param2;
System.out.println(param1 + "/" + param2 +"=" + result);
return result;
}
}
现在需求发生了变化,要求项目中所有的类在执行方法时输出执行耗时。最直接的办法是修改源代码,如下所示:
package com.xinping;
import java.util.Random;
public class Math {
public Math(){
}
//加
public int add(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 + param2;
System.out.println(param1 + "+" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
//减
public int sub(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 - param2;
System.out.println(param1 + "-" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
//乘
public int mul(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 * param2;
System.out.println(param1 + "*" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
//除
public int div(int param1, int param2){
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = param1 / param2;
System.out.println(param1 + "/" + param2 +"=" + result);
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
public void lazy(){
Random random = new Random();
int n = random.nextInt(500);
try {
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试运行:
package com.xinping.test;
import junit.framework.TestCase;
import com.xinping.Math;
public class TestRunMath extends TestCase {
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testRunMath(){
Math math = new Math();
math.add(5, 3);
math.sub(10, 3);
math.mul(3, 6);
math.div(4, 2);
}
}
运行结果:
5+3=8
共耗时: 251 毫秒
10-3=7
共耗时: 144 毫秒
3*6=18
共耗时: 153 毫秒
4/2=2
共耗时: 271 毫秒
注意消耗时间会随着 每次的运行而不同,这是模拟的加载时间,是个随机值。
缺点:
1、工作量特别大,如果项目中有多个类,多个方法,则要修改多次。
2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,而为了增加功能把每个方法都修改了,也不便于维护。
3、违背了设计原则:单一职责(SRP),每个方法除了要完成自己本身的功能,还要计算耗时、延时;每一个方法引起它变化的原因就有多种。
4、违背了设计原则:依赖倒转(DIP),抽象不应该依赖细节,两者都应该依赖抽象。而在Test类中,Test与Math都是细节。
使用静态代理可以解决部分问题。
二、静态代理
1、定义抽象主题接口。
IMath.java
package com.xinping.staticproxy;
/***
* 接口
* 抽象主题
*
* */
public interface IMath {
//加
public int add(int param1, int param2);
//减
public int sub(int param1, int param2);
//乘
public int mul(int param1, int param2);
//除
public int div(int param1, int param2);
}
2、主题类,算术类,实现抽象接口。
Math.java
package com.xinping.staticproxy;
public class Math implements IMath{
//加
public int add(int param1, int param2){
int result = param1 + param2;
System.out.println(param1 + "+" + param2 +"=" + result);
return result;
}
//减
public int sub(int param1, int param2){
int result = param1 - param2;
System.out.println(param1 + "-" + param2 +"=" + result);
return result;
}
//乘
public int mul(int param1, int param2){
int result = param1 * param2;
System.out.println(param1 + "*" + param2 +"=" + result);
return result;
}
//除
public int div(int param1, int param2){
int result = param1 / param2;
System.out.println(param1 + "/" + param2 +"=" + result);
return result;
}
}
3、代理类
package com.xinping.staticproxy;
import java.util.Random;
import com.xinping.staticproxy.IMath;
import com.xinping.staticproxy.Math;
/***
* 静态代理类
*
* */
public class StaticMathProxy implements IMath {
//被代理的对象
IMath math = new Math();
@Override
public int add(int param1, int param2) {
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = math.add(param1, param2);
System.out.println(param1 + "+" + param2 +"=" + result);
//结束时间
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
@Override
public int sub(int param1, int param2) {
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = math.sub(param1, param2);
System.out.println(param1 + "+" + param2 +"=" + result);
//结束时间
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
@Override
public int mul(int param1, int param2) {
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = math.mul(param1, param2);
System.out.println(param1 + "+" + param2 +"=" + result);
//结束时间
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
@Override
public int div(int param1, int param2) {
//开始时间
long start = System.currentTimeMillis();
this.lazy();
int result = math.div(param1, param2);
System.out.println(param1 + "+" + param2 +"=" + result);
//结束时间
long end = System.currentTimeMillis();
long consumeTime = end - start;
System.out.println("共耗时: "+ consumeTime + " 毫秒");
return result;
}
public void lazy(){
Random random = new Random();
int n = random.nextInt(500);
try {
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4. 测试运行
package com.xinping.test01;
import com.xinping.staticproxy.IMath;
import com.xinping.staticproxy.StaticMathProxy;
import junit.framework.TestCase;
public class TestRunStaticMathProxy extends TestCase {
protected void setUp() throws Exception {
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testRunMath(){
IMath math = new StaticMathProxy();
math.add(5, 3);
math.sub(10, 3);
math.mul(3, 6);
math.div(4, 2);
}
}
测试结果:
5+3=8
共耗时: 296 毫秒
10-3=7
共耗时: 491 毫秒
3*6=18
共耗时: 68 毫秒
4/2=2
共耗时: 195 毫秒
5、小结
通过静态代理,是否完全解决了上述的4个问题:
已解决:
5.1、解决了“开闭原则(OCP)”的问题,因为并没有修改Math类,而扩展出了MathProxy类。
5.2、解决了“依赖倒转(DIP)”的问题,通过引入接口。
5.3、解决了“单一职责(SRP)”的问题,Math类不再需要去计算耗时与延时操作,但从某些方面讲MathProxy还是存在该问题。
未解决:
5.4、如果项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。
如果要解决上面的问题,可以使用动态代理。
三、动态代理,使用JDK内置的Proxy实现
只需要一个代理类,而不是针对每个类编写代理类。
package com.xinping.test02;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
* 动态代理类
*
* **/
public class DynamicMathProxy implements InvocationHandler {
//被代理的对象,也叫目标类。
private Object targetObject;
/**
* 获得被代理后的对象
*
* @p object 被代理的对象
* @return 代理后的对象
*
* */
public Object getProxyObject(Object object){
this.targetObject = object;
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), //类加载器
targetObject.getClass().getInterfaces(),//获得被代理对象的所有接口
this);//InvaocationHandler对象
}
/**
* 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口
proxy 被代理后的对象
method 将要被执行的方法信息(反射)
args 执行方法时需要的参数
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被织入的内容,开始时间
long start=System.currentTimeMillis();
lazy();
//使用反射在目标对象上调用方法并传入参数
Object result=method.invoke(targetObject, args);
//被织入的内容,结束时间
Long span= System.currentTimeMillis()-start;
System.out.println("共用时:"+span);
return result;
}
//模拟延时
public void lazy(){
Random random = new Random();
int n = random.nextInt(500);
try {
Thread.sleep(n);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
getProxyObject使用的三个参数:
loader: 一个ClassLoader对象,定义了由那个ClassLoader对象来生成代理对象进行加载
interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
h:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上,间接通过invoke来执行
测试运行:
package com.bank.aop;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.bank.aop.dyna.DynamicMathProxy;
import com.bank.aop.staticproxy.IMath;
import com.bank.aop.staticproxy.Math;
public class TestRunDynamicMathProxy
{
@Before
public void setUp() throws Exception
{
}
@After
public void tearDown() throws Exception
{
}
@Test
public void test()
{
Math mathTarget = new Math();
DynamicMathProxy dynaProxy = new DynamicMathProxy();
IMath math = (IMath) dynaProxy.getProxyObject(mathTarget);
math.add(5, 3);
math.sub(10, 3);
math.mul(3, 6);
math.div(4, 2);
}
}
四 CGLIB
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
1 新建业务类Math,但是没有接口
Math.java
package com.bank.aop.cglib;
public class Math
{
//加
public int add(int param1, int param2){
int result = param1 + param2;
System.out.println(param1 + "+" + param2 +"=" + result);
return result;
}
//减
public int sub(int param1, int param2){
int result = param1 - param2;
System.out.println(param1 + "-" + param2 +"=" + result);
return result;
}
//乘
public int mul(int param1, int param2){
int result = param1 * param2;
System.out.println(param1 + "*" + param2 +"=" + result);
return result;
}
//除
public int div(int param1, int param2){
int result = param1 / param2;
System.out.println(param1 + "/" + param2 +"=" + result);
return result;
}
}
创建Cglib代理类
package com.bank.aop.cglib;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* 使用cglib动态代理
*
*
*/
public class MathCglib implements MethodInterceptor
{
private Object target;
/**
* 创建代理对象
*
* @param target
* @return
*/
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
// 回调方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("事务开始");
Object object = proxy.invokeSuper(obj, args);
System.out.println("事务结束");
return object;
}
}
测试方法:
package com.bank.aop;
import org.junit.Before;
import org.junit.Test;
import com.bank.aop.cglib.MathCglib;
import com.bank.aop.cglib.Math;
public class TestRunCglibProxy
{
@Before
public void setUp() throws Exception
{
}
@Test
public void test()
{
Math math = new Math();
MathCglib mathCglib = new MathCglib();
Math mathProxy = (Math) mathCglib.getInstance(math);
mathProxy.add(5, 3);
mathProxy.sub(10, 3);
mathProxy.mul(3, 6);
mathProxy.div(4, 2);
}
}
小结:
JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。中间主要使用到了一个接口InvocationHandler与Proxy.newProxyInstance静态方法,参数说明如下:
使用内置的Proxy实现动态代理有一个问题:被代理的类必须实现接口,未实现接口则没办法完成动态代理。
如果项目中有些类没有实现接口,则不应该为了实现动态代理而刻意去抽出一些没有实例意义的接口,通过cglib可以解决该问题。
一个知识点,你自己看懂了,那是一个层次;你会用,是另外一个层次;你写出来,你写出来让别人懂,那又是更高的一个层次;你用最通俗的言语,把知识点讲出来,让别人一下子明白,又是更高的一个层次