继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

第二篇:从Java基础学习Spring AOP——JDK动态代理(重点内容)

杨开振
关注TA
已关注
手记 7
粉丝 156
获赞 649

在Spring中,严格的来说不单单使用JDK的动态代理,还会使用CGLIB动态代理,但是它们大同小异,我们这里讲JDK的动态代理,有了它的理解CGLIB也不难掌握,读者可以阅读其他的文档。

这篇文章是揭示Spring AOP原理的基础,是所有章节中的重点内容之一,初学者需要认真学习和掌握它,否则后面讲解会很难理解的,一定多多敲代码体会它,你的进步就会很大

首先,让我们先了解什么是动态代理,假设读者是一个软件工程师,而你的公司是家软件公司,我是一个客户,我需要你公司提供软件的专业服务。显然我是找到你们公司的商务,而不是你去讨论我的要求。那么商务就等同于你的一个代理,我也会认为商务等于你们公司,而不会去管你这个软件工程师如何工作的。

代理的实际就是在真实服务对象(软件工程师)之前加多一个占位或者叫做代理对象(商务),这个占位(商务)可以根据调用者(客户)的要求去控制真实服务对象的访问(软件工程师)。有了这个比喻是不是好理解很多??

那么代理模式的好处在于什么?首先占位(商务)可以在真实服务对象之前之后做一些服务,同时根据需要选择是否需要启用真实服务对象,也会加入一些规则,比如商务也可以根据客户和公司的规则来提供额外的服务,这时可能就连真实服务对象(软件工程师)都不会启用。

好,上面的东西貌似很神奇,我们用一张图来表达代理的含义。

图片描述

好,这里的代理对象就是我们之前谈到的占位,它代理了我们看到的真实对象,可以在真实对象之前之后,甚至是代替真实对象提供服务。

动态代理有好几种,Spring使用了CGLIG和JDK动态代理。在JDK动态代理中,要求必须提供接口,而CGLIB是不需要的,我们这里只谈论JDK动态代理,在大部分的情况下,笔者建议你使用JDK动态代理,因为JDK动态代理的速度要比CGLIB要快,在Spring中一个有切面的Bean如果有接口声明,Spring就会用JDK动态代理代理它,否者启用CGLIB。

好了论述讲了一大截,我们开始讲JDK动态代理,首先我们需要提供一个简单的接口:

package com.learn.chapter1.proxy;
/**
 *
 * @author ykzhen2015
 */
public interface HelloService {
    public void sayHello(String name);
}

跟着是实现类:

package com.learn.chapter1.proxy;

/**
 *
 * @author ykzhen2015
 */
public class HelloServiceImpl implements HelloService {

    @Override
    public void sayHello(String name) {
        System.err.println("hello " + name);
    }

}

好都很简单,不需要笔者做任何解释。我们跟着就是要生成代理对象(proxy),分成两步:

  • 生成代理对象要建立代理对象(proxy)和真实对象(HelloServiceImpl)的代理关系。
  • 实现代理方法
    在JDK动态代理中需要实现接口:java.lang.reflect.InvocationHandler。让我们先来看看它:
package com.learn.chapter1.proxy;

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

/**
 *
 * @author ykzhen2015.
 */
public class HelloProxy implements InvocationHandler {

    private Object target;

    /**
     * 生成代理对象,并和真实服务对象绑定.
     * @param target 真实服务对线下 
     * @return 代理对象
     */
    public Object bind(Object target) {
        this.target = target;
        //生成代理对象,并绑定.
        Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), //类的加载器
                target.getClass().getInterfaces(), //对象的接口,明确代理对象挂在哪些接口下
                this);//指明代理类,this代表用当前类对象,那么就要求其实现InvocationHandler接口
        return proxy;
    }

    /**
     * 当生成代理对象时,第三个指定使用HelloProxy进行代理时,代理对象调用的方法就会进入这个方法。
     * @param proxy ——代理对象
     * @param method -- 被调用的方法
     * @param args -- 方法参数
     * @return  代理方法返回。
     * @throws Throwable  -- 异常处理
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.err.println("反射真实对象方法前");
        Object obj = method.invoke(target, args);//相当于sayHello方法调用.
        System.err.println("反射真实对象方法后");
        return obj;
    }

}

好了,有了上面的代码我们论述一下(实际注释也很清晰),

  • 首先声明了一个类的属性target,它的作用是保存真实服务对象(软件工程师)。
  • 然后用bind方法绑定代理对象(proxy 商务)和真实对象(软件工程师),是通过这样去绑定的:

Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), //类的加载器
target.getClass().getInterfaces(), //对象的接口,明确代理对象挂在哪些接口下
this);//指明代理类,this代表用当前类对象,那么就要求其实现InvocationHandler接口的invoke方法,而代理逻辑就放入invoke方法里。
用之前的比喻就是proxy就是商务,它代理了target这个软件工程师,而商务代理的逻辑方法放在this这个对象的invoke方法里,只是this这个对象需要实现InvocationHandler接口而已。

  • 这样声明就会进入当前类invoke方法,它实现的是代理逻辑(按比喻就是商务逻辑)它有三个参数:

Object proxy ——当前代理对象(商务)
Method method —— 当前调度的方法
Object[] args -- 方法参数

然后我们通过反射调度真实对象的方法,不懂的可以看到第一篇的论述(链接描述
Object obj = method.invoke(target, args);//相当于sayHello方法调用.

让我们测试一下这段代码:

package com.learn.chapter1.main;

import com.learn.chapter1.pojo.Role;
import com.learn.chapter1.proxy.HelloProxy;
import com.learn.chapter1.proxy.HelloService;
import com.learn.chapter1.proxy.HelloServiceImpl;
import com.learn.chapter1.utils.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;

/**
 *
 * @author ykzhen2015
 */
public class Chapter1Main {

    public static void main(String[] args) {
       HelloProxy helloProxy = new HelloProxy();
       //因为使用了接口HelloService绑定了代理对象,所以可以用HelloService作为代理对象的声明.
       HelloService proxy = (HelloService) helloProxy.bind(new HelloServiceImpl());
       proxy.sayHello("张三");//此时使用代理对象运行方法进入HelloProxy的invoke方法里
    }
}

这和时候,我们运行一下可以得到下面的打印:

反射真实对象方法前
hello 张三
反射真实对象方法后

好了,我们看到的HelloService实际已经是一个代理对象了,而不是我们普通人看到的HelloServiceImpl这个真实对象,在Spring中不要被它迷糊了哦。

这篇很重要,是我们以后的基础,所以笔者也论述得比较详细,希望大家好好学习,一定要多敲代码,好好体会哦。

打开App,阅读手记
38人推荐
发表评论
随时随地看视频慕课网APP

热门评论

讲的很详细,生动形象,期待作者更新

这写的多好,虽然我看不太懂

查看全部评论