写在前面:代理模式可以在不改变原来写好的功能类上,增加新的功能。动态代理的难点在于反射机制的实现,所以有必要在了解反射机制。
目录
1.代理模式简介
1.1 使用代理模式的作用
1.功能增强:在原有的功能上,增加了额外的功能。
2.控制增强:代理类不让你访问目标,例如商家不让用户访问厂家。
1.2 实现代理模式的方式
1.静态代理:
(1)代理类是自己手工实现的,自己创建一个java类,表示代理类。
(2)同时你要代理的目标类是要确定的。
创建过程:
- 创建一个接口(核心功能)
- 创建实现类(实现接口的方法)
- 创建代理类(实现接口的方法还可添加自己的方法)
- 创建客户端类(测试)
优点:代码简单,容易实现。
缺点:当接口中的功能增加了或修改了,会影响到众多的实现类,都需要修改,工作量会增大,影响比较大。
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.小结
静态代理和动态代理都有自己的优势,当实现类比较少时用静态代理会更方便,当代理的目标类比较多时,动态代理的优势就体现出来了。如果目标类经常修改更新,用动态代理就可避免重复修改这一繁琐操作,所以最好掌握动态代理的方法,这样才可以避免更多的重复操作。