Java静态代理和动态代理

原创
2014/12/22 23:08
阅读数 436

###代理概念 代理是什么呢?举个例子,一个公司是卖摄像头的,但公司不直接跟用户打交道,而是通过代理商跟用户打交道。如果:公司接口中有一个卖产品的方法,那么公司需要实现这个方法,而代理商也必须实现这个方法。如果公司卖多少钱,代理商也卖多少钱,那么代理商就赚不了钱。所以代理商在调用公司的卖方法后,加上自己的利润然后再把产品卖给客户。而客户部直接跟公司打交道,或者客户根本不知道公司的存在,然而客户最终却买到了产品。

专业点说:代理模式是对象的结构型模式,代码模式给某一个对象提供代理,并由代理对象控制原对象(目标对象,被代理对象)的引用。简单点说,就是通过一个工厂生成一个类的代理对象,当客户端使用的时候不直接使用目标对象,而是直接使用代理对象。

###JDK静态代理 JDK静态代理要求,目标对象和代理对象都要实现相同的接口。然后提供给客户端使用。这个代理对客户端是可见的,其结果图如下: JDK静态代理类图 下面给出一个例子: 首先建立1个接口:UserService.java定义如下方法:

package com.xie.service;
public interface UserService {  
    public void addUser(String userId,String userName);  
    public void delUser(String userId);  
    public void modfiyUser(String userId,String userName);  
    public String findUser(String userId);  
}

然后实现这个接口的目标对象:UserServiceImpl.java

package com.xie.service.impl;
import com.xie.service.UserService;

public class UserServiceImpl implements UserService {

    @Override
    public void addUser(String userId, String userName) {
       System.out.println("UserServiceImpl addUser userId->>"+userId);
    }

    @Override
    public void delUser(String userId) {
       System.out.println("UserServiceImpl delUser userId->>"+userId);
    }

    @Override
    public void modfiyUser(String userId, String userName) {
       System.out.println("UserServiceImpl modfiyUser userId->>"+userId);
    }

    @Override
    public String findUser(String userId) {
       System.out.println("UserServiceImpl findUser userId->>"+userId);
       return "张山";
    }
}

为目标对象创建代理对象:UserServiceImplProxy.java代理对象持有目标对象的引用。

package com.xie.service.proxy;
import com.xie.service.UserService;

public class UserServiceImplProxy implements UserService {
    private UserService userService;

    public UserServiceImplProxy(UserService userService){
       this.userService = userService;
    }

    @Override
    public void addUser(String userId, String userName) {
       try {
           System.out.println("开始执行:addUser");
           userService.addUser(userId, userName);
           System.out.println("addUser执行成功。");
       } catch (Exception e) {
           System.out.println("addUser执行失败。");
       }
    }

    @Override
    public void delUser(String userId) {

    }

    @Override
    public void modfiyUser(String userId, String userName) {

    }

    @Override
    public String findUser(String userId) {
       return null;
    }
}

最后调用代理对象完成功能:Client.java

package com.xie.client;
import com.xie.service.UserService;
import com.xie.service.impl.UserServiceImpl;
import com.xie.service.proxy.UserServiceImplProxy;

public class Client {
    public static void main(String[] args) {
        UserService userService = new UserServiceImplProxy(new UserServiceImpl());
        userService.addUser("001", "centre");
    }
}

###JDK动态代理 静态代理要为每个目标类创建一个代理类,当需要代理的对象太多,那么代理类也变得很多。同时代理类违背了可重复代理只写一次的原则。JDK给我们提供了动态代理。其原理图如下: JDK动态代理类图 JDK动态要求目标对象必须实现接口,因为它创建代理对象的时候是根据接口创建的。如果不实现接口,JDK无法给目标对象创建代理对象。被代理对象可以实现多个接口,创建代理时指定创建某个接口的代理对象就可以调用该接口定义的方法了。

首先定义2个接口:Service接口和UserService接口(上面的接口) Service.java

package com.xie.service;

public interface Service {
      public void sayHello(String name);
      public int addOperter(int num,int num2);
}

然后定义实现这2个接口的目标对象:UserServiceImpl.java

package com.xie.serviceimpl;
import com.xie.service.Service;
import com.xie.service.UserService;

public class UserServiceImpl implements UserService ,Service{

    @Override
    public void addUser(String userId, String userName) {
        System.out.println("UserServiceImpl addUser userId->>"+userId);
    }

    @Override
    public void delUser(String userId) {
        System.out.println("UserServiceImpl delUser userId->>"+userId);
    }

    @Override
    public void modfiyUser(String userId, String userName) {
        System.out.println("UserServiceImpl modfiyUser userId->>"+userId);
    }

    @Override
    public String findUser(String userId) {
        System.out.println("UserServiceImpl findUser userId->>"+userId);
        return "张山";
    }

    @Override
    public void sayHello(String name) {
        System.out.println("你好:"+name);
    }

    @Override
    public int addOperter(int num, int num2) {
        return num+num2;
    }
}

提供一个生成代理对象的的类:LogHandler.java

package com.xie.service.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class LogHandler implements InvocationHandler {

    private Object targertObject;

    public Object newInstance(Object targertObject){
        this.targertObject = targertObject;
        Class targertClass = targertObject.getClass();
        return Proxy.newProxyInstance(targertClass.getClassLoader(), targertClass.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用方法"+method.getName());
        Object ret = null;
        try {
            ret = method.invoke(targertObject, args);
            System.out.print("成功调用方法:"+method.getName()+";参数为:");
            for (int i = 0; i < args.length; i++) {
                System.out.println(args[i]);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.print("调用方法:"+method.getName()+"失败;参数为:");
            for (int i = 0; i < args.length; i++) {
                System.out.print(args[i]);
            }
        }
        return ret;
    }
}

编写一个客户端类来使用工厂类生成目标类的代理:Client.java

package com.xie.client;

import com.xie.service.Service;
import com.xie.service.UserService;
import com.xie.service.impl.UserServiceImpl;
import com.xie.service.proxy.LogHandler;

public class Client {
    public static void main(String[] args) {
        Service Service = (Service)new LogHandler().newInstance(new UserServiceImpl());
        UserService userService = (UserService)new LogHandler().newInstance(new UserServiceImpl());
        userService.addUser("001", "centre");
        String name = userService.findUser("002");
        System.out.println(name);
        int num = Service.addOperter(12, 23);
        System.out.println(num);
        Service.sayHello("centre");
    }
}

###CGLIB动态代理 JDK给目标类提供动态要求目标类必须实现接口,当一个目标类不实现接口时,JDK是无法为其提供动态代理的。CGLIB却能给这样的类提供动态代理。Spring在给某个类提供动态代理时会自动在JDK动态代理和CGLIB动态代理中动态的选择。

使用CGLIB为目标类提供动态代理:需要导入cglib.jar和asm.jar

如果出现asm中的类无法找到的异常,在java工程中是真的缺少asm.jar,而在web工程中很可能是asm.jar和spring提供的org.springframework.asm-3.0.4.RELEASE.jar包冲突。

注意:如果一个类继承了某个类,在子类中没有一个方法,用CGLIB生成该子类的动态代理类中将没有这个方法。

首先编写一个目标类:UserServiceImpl.java(上面的类),然后为其创建一个代理工厂,用于生成目标类的代理对象:CglibProxy.java

package com.xie.service.proxy;

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor{

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("调用的方法是:" + method.getName());
        Object ret = null;
        try {
            ret = proxy.invokeSuper(obj, args);
            System.out.print("成功调用方法:"+method.getName()+";参数为:");
    
            for (int i = 0; i < args.length; i++) {
                System.out.print(args[i]);
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.out.print("调用方法:"+method.getName()+"失败;参数为:");
            for (int i = 0; i < args.length; i++) {
                System.out.print(args[i]);
            }
        }
        return ret;
    }
}

编写一个客户端使用代理工厂生成代理对象:CglibClient.java

package com.xie.client;

import net.sf.cglib.proxy.Enhancer;
import com.xie.service.impl.UserServiceImpl;
import com.xie.service.proxy.CglibProxy;

public class CglibClient {
    public static void main(String[] args) {
        cglibUse1();
    }

    public static void cglibUse1(){
        Enhancer enhancer = new Enhancer();

        // 设置被代理的类(目标类)
        enhancer.setSuperclass(UserServiceImpl.class);

        // 使用回调
        enhancer.setCallback(new CglibProxy());

        // 创造 代理 (动态扩展了UserServiceImpl类)
        UserServiceImpl my = (UserServiceImpl) enhancer.create();
        int ret = my.addOperter(15, 22);

        System.out.println("返回的结果是:"+ret);
    }
}

###JDK动态代理和CGLIB动态代理比较 JDK动态代理要求被代理的类要实现接口,而CGLIB不需要,CGLIB能根据内存中类对象为其创建子类(代理对象)。那么它们的效率是怎么样的呢?可以做如下测试: 用JDK和CGLIB动态代理分别为同一个代理创建10000个代理对象

public static void testFlexiable() {
    UserService test = new UserServiceImpl();
    long nums = 1000;

    Date start = new Date();
    for (int i = 0; i < nums; i++) {
        UserService userService = (UserService)new LogHandler().newInstance(test);
    }
    Date end = new Date();

    System.out.println("创建"+nums+"个代理代理对象用时:"+(end.getTime()-start.getTime())+"毫秒。");
    }

测试结果: 创建1000个代理代理对象用时:32毫秒。 创建1000个代理代理对象用时:31毫秒。 创建1000个代理代理对象用时:31毫秒。 创建1000个代理代理对象用时:31毫秒。 创建1000个代理代理对象用时:94毫秒。 创建1000个代理代理对象用时:78毫秒。 创建1000个代理代理对象用时:78毫秒。 创建1000个代理代理对象用时:78毫秒。

public static void testFlexiable() {
    Enhancer enhancer = new Enhancer();
    
    // 设置被代理的类(目标类)
    enhancer.setSuperclass(UserServiceImpl.class);

    //使用回调
    enhancer.setCallback(new CglibProxy());

    long nums = 1000;
    Date start = new Date();
    for (int i = 0; i < nums; i++) {
        UserServiceImpl my = (UserServiceImpl) enhancer.create();
    }
    Date end = new Date();
    System.out.println("创建"+nums+"个代理代理对象用时:"+(end.getTime()-start.getTime())+"毫秒。");
}

测试结果: 创建1000个代理代理对象用时:47毫秒。 创建1000个代理代理对象用时:62毫秒。 创建1000个代理代理对象用时:62毫秒。 创建1000个代理代理对象用时:47毫秒。 创建1000个代理代理对象用时:47毫秒。 创建1000个代理代理对象用时:47毫秒。

创建10000个代理对象会抛异常,CGLIB运行速度明显比JDK动态代理慢,由于是通过类创建的子类,比JDK通过接口创建代理更耗内存。因此在SSH框架中,Spring通过为类提供代理采用JDK比CGLIB应该要好一些吧。

###面向切面编程 面向切面编程是继面向对象后,又一种重要的思维方式。面向对象比较重要的是通过继承实现代码重用。而面向切面编程,则注重纵向编程,他能将2个不同的功能分开,实现最大程度的解耦,比如我们现在有业务逻辑层和日志层,如果不分开,那么在每个业务逻辑方法中除了要实现业务外还要加上日志代码,如果某一天我不需要日志了,而有很多这样的类的,很多方法都加上日志代码,那改动的工作量是可想而知的。有没有一种方法让我们只写一次日志代码,而把他应用在需要写日志的方法前面,当不需要的时候直接删除呢?面向切面编程给我们提供了办法。

Struts2的拦截器就是采用这种思想编写的。下面模拟实现一个拦截器,设计图如下: Struts2拦截器时序图 首先定义一个拦截器接口:Interceptor.java

package com.xie.interceptor;

public interface Interceptor {
    public void intercept(ActionInvocation invocation);
}

然后实现拦截器接口,实现3个拦截器: FirstInterceptor.java

package com.xie.interceptor;

public class FirstInterceptor implements Interceptor {

    @Override
    public void intercept(ActionInvocation invocation) {
        System.out.println(1);
        invocation.invoke();
        System.out.println(-1);
    }
}

SecondInterceptor.java

package com.xie.interceptor;

public class SecondInterceptor implements Interceptor {

    @Override
    public void intercept(ActionInvocation invocation) {
        System.out.println(2);
        invocation.invoke();
        System.out.println(-2);
    }
}

ThirdInterceptor.java

package com.xie.interceptor;

public class ThirdInterceptor implements Interceptor{

    @Override
    public void intercept(ActionInvocation invocation) {
       System.out.println(3);
       invocation.invoke();
       System.out.println(-3);
    }    
}

接下来定义一个ActionInvocation.java

package com.xie.interceptor;

import java.util.ArrayList;
import java.util.List;

public class ActionInvocation {

    List<Interceptor> interceptors=new ArrayList<Interceptor>();
    int index=-1;
    Action action=new Action();

    public ActionInvocation(){
        this.interceptors.add(new FirstInterceptor());
        this.interceptors.add(new SecondInterceptor());
        this.interceptors.add(new ThirdInterceptor());
    }

    public void invoke(){
        if (index+1>=this.interceptors.size()) {
            action.execute();
        } else {
            index++;
            this.interceptors.get(index).intercept(this);
        }
    }
}

定义一个action:Action.java

package com.xie.interceptor;

public class Action {
    public void execute(){
        System.out.println("execute action.");
    }
}

最后定义Main类来调用action的execute方法:

package com.xie.interceptor;

public class Main {
    public static void main(String[] args) {
        new ActionInvocation().invoke();
    }
}

运行结果如下: 1 2 3 execute action. -3 -2 -1

在JavaEE中,像Filter(过滤器),Intercepetor(拦截器),Spring的事务管理都采用了面向切面的编程思想。

###面向切面概念 aspect(切面):实现了cross-cutting功能,是针对切面的模块。最常见的是logging模块,这样,程序按功能被分为好几层,如果按传统的继承的话,商业模型继承日志模块的话根本没有什么意义,而通过创建一个logging切面就可以使用AOP来实现相同的功能了。

将横切多个业务对象的程序独立出来,模块化,该模块可以无侵入式的集成到业务对象中,如:事务,日志,权限等。

jointpoint(连接点):连接点是切面插入应用程序的地方,该点能被方法调用,而且也会被抛出意外。连接点是应用程序提供给切面插入的地方,可以添加新的方法。比如以上我们的切点可以认为是findInfo(String)方法。

通知执行的时机,如方法调用,抛出异常时。

advice(处理逻辑):advice是我们切面功能的实现,它通知程序新的行为。如在logging里,logging advice包括logging的实现代码,比如像写日志到一个文件中。advice在jointpoint处插入到应用程序中。以上我们在MyHandler.java中实现了advice的功能 。

Advice(通知):指切面的具体实现,如记录日志,验证权限。通知有各种类型,其中包括“before”,“after”,“around”和“throw”等通知

pointcut(切入点):pointcut可以控制你把哪些advice应用于jointpoint上去,通常你使用pointcuts通过正则表达式来把明显的名字和模式进行匹配应用。决定了那个jointpoint会获得通知。

切入点:是感兴趣的连接点。通知和一个切入点表达式关联,并在满足这个切入点上运行,(如:执行某个特定名称的方法时。)切入点表达式如何和连接点匹配时AOP的核心:Spring缺省使用AspectJ切入点语法。

introduction:允许添加新的方法和属性到类中。

target(目标类):是指那些将使用advice的类,一般是指独立的那些商务模型。比如以上的StudentInfoServiceImpl. 是一个被代理对象,被通知对象,被一个或者多个切面所通知的对象。

proxy(代理类):使用了proxy的模式。是指应用了advice的对象,看起来和target对象很相似。AOP代理的对象,用来实现切面的功能,在Spring中,AOP代理可以使用JDK动态代理和CGLIB动态代理。

weaving(插入):是指应用aspects到一个target对象创建proxy对象的过程:complie time,classload time,runtime。把切面连接到其它应用程序类型或者对象上,并创建一个被通知的对象,在运行时完成织入。

Aspectj是一个使用面向切面,底层采用动态代理的框架。AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。Spring的AOP实现使用了Aspectj框架。

展开阅读全文
打赏
0
3 收藏
分享
加载中
更多评论
打赏
0 评论
3 收藏
0
分享
OSCHINA
登录后可查看更多优质内容
返回顶部
顶部