1.定义
一个对象本身不做实际操作,而是通过其他对象来得到自己想要的结果
意义:目标对象只需要关心自己的实现细节,通过代理对象来实现功能的增强
非常重要的一点就是不用修改源代码,增加代理对象就可以
2.角色分析
-
抽象角色 : 一般使用接口或者抽象类来实现
-
真实角色 : 被代理的角色
-
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
(可以加一个增强类,在代理类中添加功能用的) -
用户 : 使用代理角色来进行一些操作
3.静态代理
(1)接口
public interface StudentService {
void save(Student student);
Student query(Long id);
}
(2)目标类
public class StudentServiceImp implements StudentService{
@Override
public void save(Student student) {
//具体操作就是调用dao层,在数据库种保存学生信息
System.out.println("保存学生信息");
}
@Override
public Student query(Long id) {
//具体操作就是调用dao层,根据ID查询学生信息并返回实体类
//这里我们直接new一个Student返回就好了
return new Student("小红",12);
}
}
(3) 增强类(用来增加代理类的功能)
public class DaoTransaction {
public void start() {
System.out.println("事务开启");
}
public void end() {
System.out.println("事务结束");
}
}
(4)代理类
public class StudentProxy implements StudentService{
StudentService studentService;
DaoTransaction daoTransaction;
public StudentProxy(StudentService studentService, DaoTransaction daoTransaction) {
this.studentService = studentService;
this.daoTransaction = daoTransaction;
}
@Override
public void save(Student student) {
daoTransaction.start();
studentService.save(student);
daoTransaction.end();
}
@Override
public Student query(Long id) {
return studentService.query(id);
}
}
(5)测试
public class proxyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
DaoTransaction daoTransaction=new DaoTransaction();
StudentProxy studentProxy = new StudentProxy(studentService,daoTransaction);
studentProxy.save(new Student());
}
静态代理问题
在程序没有运行前,代理关系已经确定
1.不利于代码的扩展 ,比如接口中新添加一个功能,所有实现类都要重新实现
2.代理对象需要创建很多,这种设计很不便
比如:我现在又要代理TeacherService类,功能与StudentSrervice类一致。那么我又要添加一个代理类。
4.动态代理
分类:分为两类,基于接口的动态代理(jdk),基于类的动态代理(cglib)
我们这次主要学习基于接口的动态代理
定义:在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理的目标类。
也就是说,我们在静态代理中写的具体代理类可以消失了,通过反射在运行中生成代理类
1.反射复习---Method类
//可以通过反射得到类的方法,getMethod中第一个参数是获取的方法名字,第二个参数是获取方法的参数
Method save = StudentService.class.getMethod("save", Student.class);
//通过 方法.invoke 运行方法,第一个参数是运行此方法的对象,第二个参数是运行此方法传入的参数
Object invoke = save.invoke(new StudentServiceImp(), new Student());
Object invoke1 = save.invoke(new StudentServiceImp2(), new Student());
2.实现
(1) 接口
public interface StudentService {
void save(Student student);
Student query(Long id);
}
(2) 目标类
public class StudentServiceImp implements StudentService{
@Override
public void save(Student student) {
//具体操作就是调用dao层,在数据库种保存学生信息
System.out.println("保存学生信息");
}
@Override
public Student query(Long id) {
//具体操作就是调用dao层,根据ID查询学生信息并返回实体类
//这里我们直接new一个Student返回就好了
return new Student("小红",12);
}
}
(3) 增强类(用来增加代理类的功能)
public class DaoTransaction {
public void start() {
System.out.println("事务开启");
}
public void end() {
System.out.println("事务结束");
}
}
(4) 实现InvocationHandler接口
InvocationHandler接口是什么东西呢?
在反射包 java.lang.reflect,里面有三个类:InvocationHandler,Method,Proxy
其中 InvocationHandler 接口(调用处理器) :就一个方法 invoke() 方法
invoke():表示代理对象要执行的功能代码。你的代理类要完成的功能就写在invoke()方法中
方法原型: public Object invoke(Object proxy, Method method, Object[] args)
- Object proxy:jdk创建的代理对象,无需赋值。
- Method method:目标类中的方法,jdk提供method对象的
- Object[]args:目标类中方法的参数,jdk提供的。
可以看出来这个方法的参数不用我们传,是jdk在运行中调用此方法,从而运行的。
InvocationHandler接口:表示你的代理要干什么。
怎么用:
1.创建类实现接口InvocationHandler
2.重写invoke()方法,把原来静态代理中代理类要完成的功能,写在这
public class TransactionHandler implements InvocationHandler {
DaoTransaction daoTransaction;
Object obj;
public TransactionHandler(DaoTransaction daoTransaction, Object obj) {
this.daoTransaction = daoTransaction;
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy就是代理类
//method就是要实现的方法
//args是传入方法的参数
Object ret;
if("save".equals(method.getName())) {
daoTransaction.start();
ret=method.invoke(obj,args);
daoTransaction.end();
}else {
ret=method.invoke(obj,args);
}
return ret;
}
}
(5) 测试
public class proxyTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//增强类
DaoTransaction daoTransaction=new DaoTransaction();
//目标类
StudentService studentService=new StudentServiceImp();
TransactionHandler transactionHandler = new TransactionHandler(daoTransaction,studentService);
StudentService o = (StudentService) Proxy.newProxyInstance(StudentServiceImp.class.getClassLoader(), StudentServiceImp.class.getInterfaces(), transactionHandler);
o.save(new Student());
o.query(1L);
}
}
在这段代码中我们看到Proxy类
Proxy类:核心的对象,创建代理对象。之前创建对象都是new类的构造方法
现在我们是使用Proxy类的方法,代替new的使用。
方法:静态方法newProxyInstance ()
作用是:创建代理对象,等同于静态代理中的
StudentProxy studentProxy = new StudentProxy(studentService,daoTransaction);
参数:
- ClassLoaderloader类加器,负责向内存中加载对象的。使用反射获取对象的classLoader类a,a.getCalss().getClassLoader(),目标对象的类加载器
- class<?>[]interfaces:接口,目标对象实现的接口,也是反射获取的。
- InvocationHandler h:我们自己写的,代理类要完成的功能。
返回值:就是代理对象
5.执行流程
(摘自jdk动态代理1)
StudentService o = (StudentService) Proxy.newProxyInstance(StudentServiceImp.class.getClassLoader(), StudentServiceImp.class.getInterfaces(), transactionHandler);
1.用ClassLoader加载器生成一个实现了参数interfaces里所有接口且继承了Proxy的代理类的字节码对象。这个字节对象是怎么产生的呢?
- 所有生成的类都有对应的class文件,在运行中由jdk动态生成的代理类也是有class文件的。
- 这个class文件是加载器通过Proxy类的ProxyClassFactory工厂生成的,这个工厂类会去调用ProxyGenerator类的generateProxyClass()方法来生成代理类的字节码。ProxyGenerator这个类存放在sun.misc包下,我们可以通过OpenJDK源码来找到这个类,该类的generateProxyClass()静态方法的核心内容就是去调用generateClassFile()实例方法来生成Class文件。
- 这个方法生成class文件的方式就是
- 第一步:收集所有要生成的代理方法
首先为代理类生成toString, hashCode, equals等代理方法
遍历每一个接口的每一个方法, 并且为其生成ProxyMethod对象 - 第二步:收集所有要为Class文件生成的字段信息和方法信息。
- 第三步:完成了上面的工作后,开始组装Class文件。
- 具体可以看博客jdk动态代理2
2.使用Proxy的构造函数 Proxy(InvocationHandler h)来创造一个代理类的实例,将我们自定义的InvocationHandler子类传入。
3.返回这个代理类实例对象,因为我们构造的代理类实现了interfaces,所以可以直接转为接口类型
可以通过下面代码得到生成的代理类的代码
public static void saveProxyClass(String path) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
Class cl = Class.forName("java.lang.reflect.ProxyGenerator");
Method m =cl.getDeclaredMethod("generateProxyClass",String.class,Class[].class);
m.setAccessible(true);
byte[] classFile = (byte[]) m.invoke(null,"$Proxy1", StudentService.class.getInterfaces());
FileOutputStream out=null;
out=new FileOutputStream(new File(path+"$Proxy1.class"));
out.write(classFile);
out.close();
}