Spring 学习 (四)三种代理模式(动态代理AOP底层实现的两种方式)

目录

Java代理模式

一、静态代理

二、AOP底层原理的两种实现方式

(一)动态代理(又叫JDK代理(实现接口))

JDK代理底层原理:

(二)CGlib动态代理(继承类)

我们用CGLib代理就需要知道


动态代理技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会动态代理技术,那么在学习Spring这些框架时是学不明白的。

Java代理模式

代理(Proxy)是Java常用的设计模式中的之一,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能(划重点,只为扩展功能,不为改变功能)。

这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法举个例子来说明代理的作用:假设我们想邀请一位歌星,那么并不是直接连接歌星,而是联系歌星的经纪人,来达到同样的目的.歌星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子。
用图表示如下:

代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象,简单说即是在不改变源码的情况下,实现对目标对象功能扩展。

下面我们通过代码来讲解:

比如有个歌手对象叫Singer,这个对象有一个唱歌方法叫sing()

public class Singer{
   public void sing(){
        System.out.println("唱一首歌");
   }  
}

假如你希望,通过你的某种方式生产出来的歌手对象,在唱歌前后还要想观众问好和答谢,也即对目标对象Singer的sing方法进行功能扩展。

public void sing(){
    System.out.println("向观众问好");
    System.out.println("唱一首歌");
    System.out.println("谢谢大家");
} 

但是往往你又不能直接对源代码进行修改,可能是你希望原来的对象还保持原来的样子,又或许你提供的只是一个可插拔的插件,甚至你有可能都不知道你要对哪个目标对象进行扩展。这时就需要用到java的代理模式了。网上好多用生活中的经理人的例子来解释“代理”,看似通俗易懂,但我觉得不适合程序员去理解。程序员应该从代码的本质入手。

想要实现以上的需求有三种方式,这一部分我们只看三种模式的代码怎么写,先不涉及实现原理的部分。

一、静态代理

public interface ISinger {
    void sing();
}

/**
 *  目标对象实现了某一接口
 */
public class Singer implements ISinger{
    public void sing(){
        System.out.println("唱一首歌");
    }  
}

/**
 *  代理对象和目标对象实现相同的接口
 */
public class SingerProxy implements ISinger{
    // 接收目标对象,以便调用sing方法
    private ISinger target;
    public UserDaoProxy(ISinger target){
        this.target=target;
    }
    // 对目标对象的sing方法进行功能扩展
    public void sing() {
    ----System.out.println("向观众问好");
        target.sing();
    ----System.out.println("谢谢大家");
    }
}

测试类

/**
 * 测试类
 */
public class Test {
    public static void main(String[] args) {
        //目标对象
        ISinger target = new Singer();
        //代理对象
        ISinger proxy = new SingerProxy(target);
        //执行的是代理的方法
        proxy.sing();
    }
}

总结:其实这里做的事情无非就是,创建一个代理类SingerProxy,继承了ISinger接口并实现了其中的方法。只不过这种实现特意包含了目标对象的方法,正是这种特征使得看起来像是“扩展”了目标对象的方法。假使代理对象中只是简单地对sing方法做了另一种实现而没有包含目标对象的方法,也就不能算作代理模式了。所以这里的包含是关键。

缺点:这种实现方式很直观也很简单,但其缺点是代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是接下来的动态代理。

二、AOP底层原理的两种实现方式

(一)动态代理(又叫JDK代理(实现接口))

跟静态代理的前提一样,依然是对Singer对象进行扩展

public interface ISinger {
    void sing();
}

/**
 *  目标对象实现了某一接口
 */
public class Singer implements ISinger{
    public void sing(){
        System.out.println("唱一首歌");
    }  
}

动态代理有以下特点:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理

这回直接上测试,由于java底层封装了实现细节,所以代码非常简单,格式也基本上固定。调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象接收的三个参数依次为:

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
  • ClassLoader loader:指定当前目标对象使用类加载器,写法固定
  • Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
  • InvocationHandler h事件处理,需传入一个实现类,一般直接使用匿名内部类,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

测试类

public class Test{
    public static void main(String[] args) {
----Singer target = new Singer();
        ISinger proxy  = (ISinger) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    ----System.out.println("向观众问好");
                        //执行目标对象方法
                        Object returnValue = method.invoke(target, args);
                    ----System.out.println("谢谢大家");
                        return returnValue;
                    }
                });
    ----proxy.sing();
    }
}

当然如果觉的代码长想要封装成代理类也是可以的

public class JdkProxy implements InvocationHandler {
	//将被增强的对象传入到代理类中
	private ISinger traget;
	
	public JdkProxy(ISinger traget) {
		this.traget = traget;
	}

	/**
	 * -产生Singer代理的方法
	 * @return
	 */
	public ISinger createProxy() {
		ISinger singerProxy = (ISinger) Proxy.newProxyInstance(traget.getClass().getClassLoader(), traget.getClass().getInterfaces(), this);
        //调用Proxy的静态方法newProxyInstance方法会返回一个代理对象
		return singerProxy;
	}

    /**
     * 在invoke方法编码指定返回的代理对象干的工作
     * proxy : 把代理对象自己传递进来 
     * method:把代理对象当前调用的方法传递进来 
     * args:把方法参数传递进来

     * 实际上执行的都是invoke方法里面的代码,
     * 因此我们可以在invoke方法中使用method.getName()就可以知道当前调用的是代理对象的哪个方法 
     * @return
     */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 如果传入的被增强对象有多个方法需要判断是不是sing方法
		if("sing".equals(method.getName())) {
            //在转调具体目标对象之前,可以执行一些功能处理

			System.out.println("向观众问好");
			Object returnValue = method.invoke(traget, args);
			System.out.println("谢谢大家");

            //转调具体目标对象的方法
			return returnValue;

            //在转调具体目标对象之后,可以执行一些功能处理
		}
		return method.invoke(traget, args);
	}
}

JDK代理底层原理:

这里要讲一个我在学习时候的难点,可能很多朋友都不知道我在createProxy方法中创建了代理类对象,并没有调用invoke方法为什么会执行它里面的代码呢?

其实Proxy类负责创建代理对象时,如果指定了handler(处理器)也就是newProxyInstance方法中的第三个参数InvocationHandler h,那么不管用户调用代理对象的什么方法,该方法都是调用处理器的invoke方法(这相当于invoke方法拦截到了代理对象的方法调用)。因为我们代理类继承了 InvocationHandler 接口,所以可以重写里面唯一的方法 invoke
  由于invoke方法被调用需要三个参数:代理对象、方法、方法的参数,因此不管代理对象哪个方法调用处理器的invoke方法,都必须把自己所在的对象、自己(调用invoke方法的方法)、方法的参数传递进来。

并且,开发人员通过invoke方法的参数,还可以在拦截的同时,知道用户调用的是什么方法,因此利用这两个特性,就可以实现一些特殊需求,例如:拦截用户的访问请求,以检查用户是否有访问权限、动态为某个对象添加额外的功能。

测试类

public class SpringDemo1 {
	@Test
	//JDK动态代理
	public void demo1() {
		//创建被增强对象
		ISinger iSinger = new Singer();
		
		ISinger proxy = new JdkProxy(iSinger).createProxy();
		proxy.sing();
	}
}

总结:以上代码只有横线的部分是需要自己写出,其余部分全都是固定代码。由于java封装了newProxyInstance这个方法的实现细节,所以使用起来才能这么方便(这个方法底层代码我还没有研究,研究好后会更新到这里)。

缺点:可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,加入没有,则可以使用Cglib代理。

(二)CGlib动态代理(继承类)

上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
  • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
  • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.

前提条件:

  • 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入  spring-core-3.2.5.jar
  • 引入功能包后,就可以在内存中动态构建子类
  • 目标类不能为final
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法

目标类

/**
 * 目标对象,没有实现任何接口
 */
public class Singer{

    public void sing() {
        System.out.println("唱一首歌");
    }
}

Cglib子类

/**
 * Cglib子类代理工厂
 */
public class ProxyFactory implements MethodInterceptor{
     维护目标对象
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    给目标对象创建一个代理对象
    public Object getProxyInstance(){
        1.这是CGlib的核心类对象
        Enhancer en = new Enhancer();
        2.设置父类,那我们已经说了CGlib采用了继承目标类生成子类的方式来代理,所以
          我们需要在这里设置CGLib的父类是谁
        en.setSuperclass(target.getClass());
        3.设置回调函数(什么是回调,类似于InvocationHandler对象,因为我们这里不是继承了        
          MethodInterceptor接口嘛,那这里的intercept就类似于invoke方法),建立关联关系
        en.setCallback(this);
        4.创建子类(代理对象)
        Object proxy = en.create();
        return proxy;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    ----System.out.println("向观众问好");
        执行目标对象的方法
        Object returnValue = method.invoke(target, args);
    ----System.out.println("谢谢大家");
        return returnValue;
    }
}

这里的代码也非常固定,只有横线部分是需要自己写出

测试

/**
 * 测试类
 */
public class Test{
    public static void main(String[] args){
        //目标对象
        Singer target = new Singer();
        //代理对象
        Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
        //执行代理对象的方法
        proxy.sing();
    }
}

我们用CGLib代理就需要知道

必须知道一个类和一个接口
 1.MethodInterceptor接口
           public Object intercept(Object obj,  Method method, Object[] args, MethodProxy proxy) throws Throwable; intercept是所有拦截器执行的方法,类似于jdk动态代理中的invoke,它比invoke就多最后一个参数 MethodProxy proxy 方法代理:生成的代理类对方法的代理引用
 2. Enhancer类 (这两个接口和类就自己百度深入学一下把= =,我只想静静)

总结:三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值