聊聊Java静态代理与动态代理的那些事儿

一、什么是代理模式以及为什么需要代理模式?

代理模式就是,个人去买房子,可以直接去买房子,如果直接去买房子的话就得办很多的手续,然后跑很多地方,这时候房产中介就出现了,我们可以把买房子的事交给中介,让中介做我们的代理,这样我们会省力很多,不需要关心房子是怎么买下来的。如果还不是很理解的话,请接着往下看.

package com.cld.demo.proxy;
/**
 *
 * @author Limingwu
 * @create 2018年07月25日
 */
public class BuyHouse {
	public static void main(String[] args) {
		BuyHouse buyHouse = new BuyHouse();
		buyHouse.buy();
                //输出结果如下:
                //买房之前需要办的手续
                //买房!
                //买房之后需要办的手续
	}
	
        /**
	 * 买房
	 */
	public void buy(){
		System.out.println("买房之前需要办的手续");
		System.out.println("买房!");
		System.out.println("买房之后需要办的手续");
	}
}

从上面这段代码你发现了什么?

1、违背了设计原则:单一职责(SRP),buy方法除了要完成自己本身买房的功能,还要完成买房之前需要办的手续的功能以及买房之后需要办的手续的功能。

2、违背了设计原则:开闭原则(OCP),对扩展开放,对修改关闭,如果需要修改买房之前需要办的手续,那么整个buy方法都会受到影响,且业务与操作太多也不便于维护。

实际上在软件开发中,我们只需要关注那些核心业务与操作,对于并不是很核心的业务与操作,我们完全可以交给代理去做.

用代理模式的方式来做:

package com.cld.demo.proxy;

public interface IHouseService {
	/**
	 * 个人买房
	 */
	public void buy() ;
}
package com.cld.demo.proxy;

public class IHouseServiceImpl implements IHouseService{

	@Override
	public void buy() {
	    System.out.println("买房!");
	}

}
package com.cld.demo.proxy;

/**
* 买房代理类
*
* @author Limingwu
* @create 2018年07月26日
*/
public class HouseProxy implements IHouseService{
	
	private IHouseService houseService = new IHouseServiceImpl();

	@Override
	public void buy() {
		//这里可以做非核心业务与操作的
		System.out.println("买房之前需要办的手续");
		houseService.buy();
		//这里可以做非核心业务与操作的
		System.out.println("买房之后需要办的手续");
	}

}
package com.cld.demo.proxy;
/**
 *
 * @author Limingwu
 * @create 2018年07月25日
 */
public class BuyHouse {
	//代理对象
	public static IHouseService houseService = new HouseProxy();
	
	public static void main(String[] args) {
		//买房
		houseService.buy();
                //输出结果如下:
                //买房之前需要办的手续
                //买房!
                //买房之后需要办的手续
	}
}

在整个过程中IHouseService的buy方法始终没有发生改变,它只需要关心买房的操作,而不需要关心买房之前需要办的手续与买房之后需要办的手续,从而使得IHouseService的buy方法业务变得简单明了,便于维护.尽管不使用代理模式与使用代理模式的结果是一样的,但是使用代理模式比不使用代理模式的好处是很明显的。优点一:可以使得核心业务变得简单明了,便于维护,解决了单一原则。优点二:可以实现被代理类与代理类间的解耦,在不修改被代理类的代码的情况下能够做一些额外的处理。简单说即是在不改变被代理对象源码的情况下,实现对被代理对象的功能扩展,解决了开闭原则。(这也就是为什么要使用代理模式的原因)

二、什么是静态代理、JDK动态代理、CGLib动态代理?

静态代理一般是对某一个接口进行代理

/**
 * UserService接口
 */
interface UserService{

    void login();
    void logout();
}

/**
 * UseService实现类
 */
class UserServiceImpl implements UserService{

    @Override
    public void login() {
        System.out.println("someone login....");
    }

    @Override
    public void logout() {
        System.out.println("someone logout....");
    }
}


/**
 * 实现UserService接口,用于代理
 */
class UserServiceProxy implements UserService{

    private UserService userService;

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

    @Override
    public void login() {
        LogService.info();
        userService.login();
    }

    @Override
    public void logout() {
        LogService.info();
        userService.logout();
    }
}

基于JDK的动态代理技术,其主要特点就是目标类,也就是需要被代理的类,必须有接口,并且代理类必须实现跟它一样的接口,从而来起到代理其事务的功能,具体使用如下代码所示,假设有一个UserService类,主要用于负责用户的登录和退出,同时,有个日志类,负责记录用户的操作信息,直接将信息日志写在对应的UserService实现类中,可以达到目的,但显然这种方式不是很合理,特别是在UserService有很多个方法需要做日志记录的时候,就会使得日志记录代码遍布整个UserService,不仅使得代码的冗余很大,而且当需要进行修改的时候,也需要逐个修改,非常麻烦,这个时候,采用动态代理技术就是一种非常好的方法了。

/**
 * UserService接口
 */
interface UserService{

    void login();
    void logout();
}

/**
 * UseService实现类
 */
class UserServiceImpl implements UserService{

    @Override
    public void login() {
        System.out.println("someone login....");
    }

    @Override
    public void logout() {
        System.out.println("someone logout....");
    }
}


/**
 * 实现InvocationHandle接口,用于织入所要增强的代码
 */
class UserServiceHandle implements InvocationHandler{

    private UserService userService;

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

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

        LogService.info();
        Object object = method.invoke(userService, args);
        LogService.info();
        return object;
    }
}

/**
 * 代理类工厂,用于产生UseService类的代理类
 */
class ProxyFactory{

    public static UserService getProxyObject(UserService userService){

        // 使用JDK动态代理技术来创建对应的代理类
        return (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new UserServiceHandle(userService)
        );
    }
}

这样,当我们需要使用UseService类的时候,只需要从ProxyFactory中获取即可,而且获取的对象是UserService对象的代理类,也就是说,获得的对象是UserService对象的增强版

从上面的ProxyFactory工厂中可以看到,在使用JDK进行创建动态代理对象的时候,需要为其提供接口,或者说,如果所要增强的目标类没有实现任何接口,则JDK动态代理技术是无法为其创建对应的代理对象的,这是JDK动态代理技术的一种缺点,而基于CGLib的动态代理技术则恰好弥补了这个缺点,CGLib动态代理技术使用的是继承该类的方式,从而避免了需要接口的缺陷,具体使用如下所示,注意,需要导入对应的依赖文件

/**
 * 基于CGLib的动态代理技术
 * 注意这里需要实现MethodInterceptor接口
 */
class ProxyFactory implements MethodInterceptor{

    // 提供对应的增强操作类
    private  Enhancer enhancer = new Enhancer();

    public UserService getProxyObject(Class clazz){
        // 设置所要增强的类的父类
        enhancer.setSuperclass(clazz);
        // 设置回调对象
        enhancer.setCallback(this);
        // 创建对应的对象
        return (UserService) enhancer.create();
    }

    // 实现拦截方法,用于拦截对应的方法,并且对对应的方法进行增强
    // 参数含义:传入的对象, Method对象,方法的参数,进行代理后的Method对象
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        LogService.info();
        // 这里需要注意,由于methodProxy对象是增强后的Method对象,所以这里需要调用的
        // 是methodProxy父类的方法,也就是所以增强的类的方法,以实现原来的功能
        Object object = methodProxy.invokeSuper(o, objects);
        LogService.info();
        return object;
    }
}

可以看到,使用CGLib动态代理技术可以在不需要实现接口的情况下动态为对象创建代理对象,在很大程度上弥补了JDK动态代理技术的缺点,不过由于CGLib动态代理技术是采用继承目标类的方式,所以也存在一些问题,比如说,对于final以及private修饰的方法是无法为其增强的,这里还需要注意一下。

三、静态代理、动态代理之间的区别?

静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理类:在程序运行时,运用反射机制动态创建而成。
静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
 静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过Proxy里的newProxyInstance得到代理对象。
还有一种动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行时,动态修改字节码达到修改类的目的。

四、JDK动态代理、CGLib动态代理之间的区别?

1)JDK动态代理:

具体实现原理:

1、通过实现InvocationHandlet接口创建自己的调用处理器

2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理

3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型

4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,

Spring通过java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

2)CGLib动态代理:

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

两者对比:

JDK动态代理是面向接口,在创建代理实现类时比CGLib要快,创建代理速度快。

CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败),在创建代理这一块没有JDK动态代理快,但是运行速度比JDK动态代理要快。

使用注意:

如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制)

如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。

那么如何选择的使用代理机制了?

通过配置Spring的中<aop:config>标签来显示的指定使用动态代理机制 proxy-target-class=true表示使用CGLib代理,如果为false就是默认使用JDK动态代理

四、代理模式应用场景?

日志打印、系统参数安全检查、某些实体类更新时间、更新人的、登录时间设置值.(简单说即是在不改变被代理对象源码的情况下,实现对被代理对象的功能扩展

参考博客:https://blog.csdn.net/qq1723205668/article/details/56481476

参考博客:http://www.jfox.info/spring%E5%AD%A6%E4%B9%A0%E4%B9%8B%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86.html

猜你喜欢

转载自blog.csdn.net/qq_36090463/article/details/81219793