Spring学习04---反射、静态代理、动态代理

写在前面:代理模式可以在不改变原来写好的功能类上,增加新的功能。动态代理的难点在于反射机制的实现,所以有必要在了解反射机制。

目录

1.代理模式简介

1.1 使用代理模式的作用

1.2 实现代理模式的方式

1.3案例模型

2.静态代理实现

2.1创建Student接口

 2.2学生核心业务实现类StudentImpl

2.3学生静态代理类

 2.4测试类

3.反射

3.1反射案例

​ 3.2案例分析

4.动态代理实现

4.1jdk动态代理

4.2代码演示

4.3Cglib动态代理

4.4cglib代码测试

5.小结


1.代理模式简介

1.1 使用代理模式的作用

        1.功能增强:在原有的功能上,增加了额外的功能。

        2.控制增强:代理类不让你访问目标,例如商家不让用户访问厂家。

1.2 实现代理模式的方式

1.静态代理:

 (1)代理类是自己手工实现的,自己创建一个java类,表示代理类。

 (2)同时你要代理的目标类是要确定的。

创建过程:

  1. 创建一个接口(核心功能)
  2. 创建实现类(实现接口的方法)
  3. 创建代理类(实现接口的方法还可添加自己的方法)
  4. 创建客户端类(测试)

优点:代码简单,容易实现。

缺点:当接口中的功能增加了或修改了,会影响到众多的实现类,都需要修改,工作量会增大,影响比较大。

2.动态代理:

当你修改了接口中的方法时,不会影响代理类。

在程序的执行过程中,使用jdk反射机制,创建代理类对象,并动态的指定要代理目标类。

实现方式有两种:

  • 第一种是jdk动态代理:使用java反射包中的类和接口实现动态代理的功能。jdk方法必须有接口才能用,没接口用不了,这种情况应该考虑cglib方法。
  • 第二种是cglib动态代理:cglib是第三方提供的工具库,创建代理对象。cglib的原理是继承,cglib通过继承目标类,创建他的子类,在子类中重写父类中同名的方法,实现功能的修改。

实现动态代理的步骤:

  • 1.创建接口,定义目标类要完成的功能
  • 2.创建目标类实现接口
  • 3.创建InvocationHandler接口实现类,在invoke方法中完成代理类的功能
  • 4.使用Proxy类的静态方法,创建代理对象,并把返回值转为接口类型。

1.3案例模型

解析:核心类的方法:课堂学习(study),做家庭作业(homework),其他方法为非核心业务,属于附加业务。

2.静态代理实现

2.1创建Student接口

学生核心业务:

public interface Student {
    public void study();
    public void homework();
}

 2.2学生核心业务实现类StudentImpl

学生核心业务实现类,实现学生的核心业务。个人理解:所谓学生实现类,就是一种多态,学生是一个抽象概念,而实现它的类就是一个具体的事物。比如:张三和李四都是学生,那他们都要做学生必须做的事(核心业务)。

 
public class StudentImpl implements Student{
    //核心业务:课堂学习(study) 做家庭作业(homework)
    //非核心业务:准备的上课、听老师讲课、上课结束、准备做家庭作业、开始做家庭作业、完成家庭作业
    @Override
    public void study() {
        System.out.println("学生学习----------------------");
    }
    @Override
    public void homework() {
        System.out.println("学生做作业-------------------------");
    }
}

2.3学生静态代理类

要求学生实现类不能改变,为了给实现类增加非核心业务方法,创建一个静态代理类StudentProxy继承学生类(相当于代理某个学生),然后重写核心业务。这里假设张三同学找了个学习代理,这个学习代理本来就拥有学生的核心业务(study和homework),现在代理只需要将张三的study和homeWork拿过来即可(代理调用张三的方法),这样就不改变张三本来要做的事了。代理还有其他附加业务,预习、做笔记、收拾垃圾、做作业、复习、整理书包。此时只需要添加到核心业务中,就可以实现了。

//学生功能代理类
public class StudentProxy implements Student{
    private Student student;
    public void setStudent(Student student) {
        this.student = student;
    }
    @Override
    public void study() {
        student.study();
        openBook();
        lectures();
        classOver();
    }
    @Override
    public void homework() {
        student.homework();
        proDoHomework();
        DoHomework();
        HomeworkOver();
    }
    //添加非核心功能
    //非核心业务:准备的上课、听老师讲课、上课结束、准备做家庭作业、开始做家庭作业、完成家庭作业
    public void openBook(){
        System.out.println("学生打开书本预习。。。。");
    }
    public void lectures(){
        System.out.println("学生听老师讲课,做笔记,完成课堂练习。。。。");
    }
    public void classOver(){
        System.out.println("学生上课结束,收拾书包,带走垃圾。。");
    }
    public void proDoHomework(){
        System.out.println("学生准备做家庭作业,复习今天课堂所学得的知识。。");
    }
    public void DoHomework(){
        System.out.println("学生打开作业本,写作业、检查改错、预习。。");
    }
    public void HomeworkOver(){
        System.out.println("学生完成家庭作业,收拾书本。。。");
    }
}

 2.4测试类

以前是学生自己调用方法,现在是找代理完成学生业务。

public class StudentLiuSir {
    public static void main(String[] args) {
            //静态代理模式
            //静态代理类相当于把核心类重新写了一遍,所以代码复用太高了,开发效率低
            StudentImpl student=new StudentImpl();
            StudentProxy proxy = new StudentProxy();
            proxy.setStudent(student);
            proxy.study();
            proxy.homework();
   }
}

3.反射

3.1反射案例

要了解动态代理就必须了解反射的一些知识,由于反射底层太复杂,所以只要需要了解基本的方法和机制就行。

 

3.2案例分析

jdk提供了反射包中的Method类,这个类可以通过目标类中的方法名获取目标类对象,经过

目标类.class.getMethod(方法名,参数类型);可得到一个Method对象,这个对象有一个invoke()方法,有两个参数 invoke(目标对象,方法执行参数值),返回值是一个Object类型。

这里的目标对象就是提前声明好的实现类对象。运行后得到“你好,李四”的结果。

4.动态代理实现

4.1jdk动态代理

1.反射:Mehtod类,表示方法,类中的方法。通过Method可以执行某个方法。

2.jdk动态代理的实现:

反射包java.lang.reflect,里面有三个类:InvocationHandler,Method,Proxy

(1)InvocationHandler(调用处理器)接口:就一个方法invoke(),表示你要干什么。

        invoke():表示代理对象要执行的功能代码,你的代理类要完成的功能就写在invoke()方法中。

        代理类完成的功能:

  •                 调用目标的方法,执行目标方法的功能。
  •                 功能增强,在目标方法调用时,增强功能。

                方法原型:public Object invoke(Object proxy,Method method,object[] args)

参数:Object proxy:jdk创建的代理对象,无需赋值。

        method:目标类中的方法,jdk提供的method对象的。

        object[] args:目标类中方法的参数,jdk提供的。

怎么用:1.创建一个类实现这个接口invocationHandler

              2.重写invoke方法,把原来静态代理中代理类要完成的功能,写在这里面。

(2)method类:表示目标类中的方法。

        作用:通过method可以执行某个目标类的方法,method.invoke();这里的invoke只是碰巧和invocationHandeler同名,并无关联。

method.invoke(目标对象,方法的参数);

(3)proxy类:核心的对象,创建代理对象,之前创建对象都是new类的构造方法;

现在使用proxy类的方法,代替new的使用。

方法:静态方法 newproxyInstance()

作用是:创建代理对象。

参数:1.ClassLoader loader类加载器,负责向类中加载对象的,使用反射机制来获取对象的classLoder类a,a.getClass().getClassLoder,目标对象的类加载器。

2.Class<?>[] interfaces接口,目标对象的类加载器

3.InvocationHandler h :我们自己写的,代理类要完成的功能。

返回值:就是代理对象。(这三个参数加工出这个目标的一个代理对象)

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)

实现动态代理的步骤:

  • 1.创建接口,定义目标类要完成的功能
  • 2.创建目标类实现接口
  • 3.创建InvocationHandler接口实现类,在invoke方法中完成代理类的功能
  • 4.使用Proxy类的静态方法,创建代理对象,并把返回值转为接口类型。

 ps:Method中的invoke方法和invocationHandler类中的invoke方法虽然同名,但是并没有什么联系,只是碰巧同名而已。

4.2代码演示

代理处理器,StudentProxyDTJdk,这个类不能完全称为代理类,应该理解为一个可以生成代理类的程序处理段,只有传入实现类对象,调用getProxy()方法后,才会生成一个代理类。

package com.liu.demo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class StudentProxyDTJdk implements InvocationHandler {
    //被代理的接口
    private Object target;
    public void setStudent(Object target) {
        this.target = target;
    }
    //生成得到代理类<类加载器(加载到那个位置),代理类的接口,代理处理程序实现类,也就是此类this>
    public Object getProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result=null;
        if(method.getName().equals("study")){
            result = method.invoke(target, args);
            openBook();
            lectures();
            classOver();
        }
        if(method.getName().equals("homework")){
            result = method.invoke(target, args);
            proDoHomework();
            DoHomework();
            HomeworkOver();
        }
        return result;
    }
    public void openBook(){
        System.out.println("学生打开书本预习。。。。");
    }
    public void lectures(){
        System.out.println("学生听老师讲课,做笔记,完成课堂练习。。。。");
    }
    public void classOver(){
        System.out.println("学生上课结束,收拾书包,带走垃圾。。");
    }
    public void proDoHomework(){
        System.out.println("学生准备做家庭作业,复习今天课堂所学得的知识。。");
    }
    public void DoHomework(){
        System.out.println("学生打开作业本,写作业、检查改错、预习。。");
    }
    public void HomeworkOver(){
        System.out.println("学生完成家庭作业,收拾书本。。。");
    }
}

 测试类:创建目标类的对象和代理类的对象,将目标类的对象丢给处理程序后,生成得到动态代理类对象,然后用这个对象调用学生业务。也就是经过

proxyJdk.setStudent(student);和 Student proxy = (Student) proxyJdk.getProxy();后

得到了代理proxy对象。

            StudentImpl student=new StudentImpl();
            StudentProxyDTJdk proxyJdk = new StudentProxyDTJdk();
            proxyJdk.setStudent(student);
            Student proxy = (Student) proxyJdk.getProxy();
            proxy.study();
            proxy.homework();

ps:jdk只能代理接口,如果项目没有接口,就不能用这种方法。这种情况就应该考虑cglib了。

4.3Cglib动态代理

      JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。

        Cglib本质是继承重写,所以只要不是final类都可以被代理。运用时需要导包即可。

        实现CGLIB动态代理必须实现MethodInterceptor(方法拦截器)接口

这个接口只有一个intercept()方法,这个方法有4个参数:

  • 1)o表示增强的对象,即实现这个接口类的一个对象;
  • 2)method表示要被拦截的方法;
  • 3)objects表示要被拦截方法的参数;
  • 4)methodproxy表示要触发父类的方法对象;
package com.liu.demo1;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class StudentProxyDTCglib implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object result=null;
        if(method.getName().equals("study")){
            result = methodProxy.invokeSuper(o, objects);
            openBook();
            lectures();
            classOver();
        }
        if(method.getName().equals("homework")){
            result = methodProxy.invokeSuper(o, objects);
            proDoHomework();
            DoHomework();
            HomeworkOver();
        }
        return result;
    }
    public void openBook(){
        System.out.println("学生打开书本预习。。。。");
    }
    public void lectures(){
        System.out.println("学生听老师讲课,做笔记,完成课堂练习。。。。");
    }
    public void classOver(){
        System.out.println("学生上课结束,收拾书包,带走垃圾。。");
    }
    public void proDoHomework(){
        System.out.println("学生准备做家庭作业,复习今天课堂所学得的知识。。");
    }
    public void DoHomework(){
        System.out.println("学生打开作业本,写作业、检查改错、预习。。");
    }
    public void HomeworkOver(){
        System.out.println("学生完成家庭作业,收拾书本。。。");
    }
}

4.4cglib代码测试

        Enhancer是一个非常重要的类,它允许为非接口类型创建一个JAVA代理,Enhancer动态的创建给定类的子类并且拦截代理类的所有的方法,和JDK动态代理不一样的是不管是接口还是类它都能正常工作。

        StudentProxyDTCglib cglibProxy = new StudentProxyDTCglib();
        Enhancer enhancer = new Enhancer();
        //设置代理目标
        enhancer.setSuperclass(StudentImpl.class);
        //设置单一回调对象,在调用中拦截对目标方法的调用
        enhancer.setCallback(cglibProxy);
        //设置类加载器
        enhancer.setClassLoader(StudentImpl.class.getClassLoader());
        Student student = (Student) enhancer.create();
        student.study();
        student.homework();

5.小结

        静态代理和动态代理都有自己的优势,当实现类比较少时用静态代理会更方便,当代理的目标类比较多时,动态代理的优势就体现出来了。如果目标类经常修改更新,用动态代理就可避免重复修改这一繁琐操作,所以最好掌握动态代理的方法,这样才可以避免更多的重复操作。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

记录菌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值