代理模式: 目标对象不可访问,通过代理对象增强访问。
案例:客户A想要租房,都是通过与中介去租房,而不是直接去找房东租房。这就是代理模式。
代理模式的作用:
代理模式的分类:
静态代理的特点:
案例: 张三现在找到了一份工作,需要去公司附近租一套房子,且已经看中了一套离公司很近的loft公寓,现在需要去联系中介租这一套房。
上代码:
Service
public interface Service {
void rent();
}
Landlord
public class Landlord implements Service{
//只有房东才有房子
private House house;
@Override
public void rent() {
house = new House();
System.out.println("把房子租出去");`public class Zhang {
@Test
public void testRent(){
Agency agency = new Agency();
agency.rent();
}
}`
}
private class House{
public House() {
}
}
}
Agency
public class Agency implements Service{
@Override
public void rent() {
System.out.println("添置家具");
System.out.println("商谈租金");
Landlord landlord = new Landlord();
landlord.rent();
System.out.println("签合同,收租金");
}
}
Zhang
public class Zhang {
@Test
public void testRent(){
Service agency = new Agency();
agency.rent();
}
}
分析一下:
代理过程:
Java多态的展现:子类可以根据自身情况对父类/接口的方法进行重写(扩展)
当我们需要新增一个目标对象交由中介代理的时候,如何让中介根据客户需求去实现准确的代理(租户要租别墅就给他别墅,要房子就给他房子)?
这就涉及到面向接口编程了。
面向接口编程:将类中的成员变量设计为接口,方法的形参设计为接口,方法的返回值设计为接口,调用时,接口只想实现类。
以下便是上述案例关于面向接口编程的一个优化:
首先,我们新增一个房东Pro,他想租出去一套别墅。
LandlordPro
public class LandlordPro implements Service{
private Villa villa;
@Override
public void rent() {
villa = new Villa();
System.out.println("出租一套别墅");
}
private class Villa{
public Villa() {
}
}
}
因为新增了一个目标对象(现在有两个了),那么相应的代理对象也需要根据客户需求进行判断(该把谁的租借出去),所以我们在类中新增一个接口对象作为传输参数,判断该先执行哪个目标对象的业务。
public class Agency implements Service{
//类中的成员变量设计为接口
public Service target; //目标对象
public Agency() {
}
//传入目标对象,方法的参数设计为接口
public Agency(Service target){
this.target = target;
}
@Override
public void rent() {
System.out.println("添置家具");
System.out.println("商谈租金");
//面向接口编程:调用时,接口指向实现类
target.rent();
System.out.println("签合同,收租金");
}
}
现在来看看客户代码:
public class Zhang {
@Test
public void testRent(){
//需要将参数传入构造器,从而调用相应的目标对象的相应业务
Service agency = new Agency(new LandlordPro());
agency.rent();
}
}
通过上述的案例我们可以预料到一种情况——当我们需要向接口中新增业务方法的时候,我们的两个目标对象都必须改写代码(实现新增的业务方法),代理对象也需要进行改变,如图:
我们在业务接口中新增一个 违约的方法 default(),那么整个模块都需要进行改动,巨麻烦。
因此,静态代理只适合——业务功能固定,最多目标对象需要改变的情况,因此我们需要动态代理。
动态代理: 代理对象在程序运行的过程中动态在内存构建,可以灵活的进行业务逻辑功能的切换。
简而言之,就是基于静态代理,突破其业务功能必须固定这一瓶颈。
jdk动态代理:
jdk动态代理是需要使用工具类来完成jdk动态实现的:
java.lang.reflect.proxy 类
public static Object newProxyInstance(...)
专门用来生成动态代理对象 @CallerSensitive
public static Object newProxyInstance(ClassLoader loader,//类加载器,完成目标对象的加载
Class<?>[] interfaces,//目标对象实现的所有接口
InvocationHandler h)//代理的功能和目标对象的业务功能的调用
throws IllegalArgumentException
{
...
java.lang.reflect.Method 类
进行目标对象的方法的反射调用,method对象接收我们要调用的方法(如上述案例的 rent())
public Object invoke(Object obj, Object... args)
InvocationHandler 接口
用来实现代理和业务功能的,在调用时使用 匿名内部类 实现
啥是匿名内部类?一般情况,接口或者抽象类是不能直接实例化的(new 一个接口对象),而使用匿名内部类就可以实现接口或者抽象类的实例化。
比如,我们非常熟悉的 Runnable 接口:这就是一个匿名内部类的实现。
public class Test {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("一个线程在运行...");
}
};
Thread thread = new Thread(runnable);
}
}
而因为Runnable接口只有一个方法,故而这是一个 函数式接口 ,可以使用Lambda表达式简化代码:
public class Test {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("一个线程在运行...");
Thread thread = new Thread(runnable);
}
}
业务接口,两个房东类都不变:
Service
public interface Service {
void rent();
}
Landlord
public class Landlord implements Service{
//只有房东才有房子
private House house;
@Override
public void rent() {
house = new House();
System.out.println("把房子租出去");
}
private class House{
public House() {
}
}
}
LandlordPro
public class LandlordPro implements Service{
private Villa villa;
@Override
public void rent() {
villa = new Villa();
System.out.println("出租一套别墅");
}
private class Villa{
public Villa() {
}
}
}
接下来,重点来了,动态代理类——ProxyFactory
public class ProxyFactory {
//类中的成员变量设计为接口
Service target;
//传入目标对象
public ProxyFactory(Service target){
this.target = target;
}
//返回动态代理对象
public Object getAgency(){
return Proxy.newProxyInstance(//这个返回值是动态代理对象
target.getClass().getClassLoader(), //类加载器,完成目标对象的加载
target.getClass().getInterfaces(),//目标对象实现的所有接口
new InvocationHandler() {//实现代理功能的接口,传入匿名内部类实现
@Override
public Object invoke(//这个方法的返回值就是目标方法的返回值
Object proxy, //创建好的代理对象
Method method, //目标方法,如 rent()
Object[] args//目标方法的参数
) throws Throwable {
//代理功能,功能扩展
System.out.println("添置家具");
System.out.println("商谈租金");
//主业务功能实现,如何灵活地调用业务接口的各种方法?
Object obj = method.invoke(target,args);
//代理功能,功能扩展
System.out.println("签合同,收租金");
return obj;
}
}
);
}
}
分析一下这个类:
java.lang.reflect.Proxy.newProxyInstance
返回一个动态代理对象(根据传入接口生成对应的对象)java.lang.reflect.InvocationHandler
这是实现代理的方法的接口,使用此接口中的 invoke
方法实现代理和业务功能最后就是测试类:
public class Zhang {
@Test
public void testRent(){
ProxyFactory proxyFactory = new ProxyFactory(new LandlordPro());
Service agency = (Service) proxyFactory.getAgency();
agency.rent();
}
}
现在向业务接口中新添一个业务方法 int increase(int num)
Service
public interface Service {
void rent();
String increase(int num);//新增业务
}
Landlord
public class Landlord implements Service{
//只有房东才有房子
private House house;
@Override
public void rent() {
house = new House();
System.out.println("把房子租出去");
}
@Override
public String increase(int num) {
return "房子租金涨价" + num + "元人民币";
}
private class House{
public House() {
}
}
}
LandlordPro
public class LandlordPro implements Service{
private Villa villa;
@Override
public void rent() {
villa = new Villa();
System.out.println("出租一套别墅");
}
@Override
public String increase(int num) {
return "别墅租金涨价" + num + "元人民币";
}
private class Villa{
public Villa() {
}
}
}
动态代理类完全不用更改
直接进测试类调用新的方法:
public class Zhang {
@Test
public void testRent(){
ProxyFactory proxyFactory = new ProxyFactory(new LandlordPro());
Service agency = (Service) proxyFactory.getAgency();
System.out.println(agency.increase(1000));
}
}
注意: 动态代理只会代理接口中的方法,目标对象自身的对象是不会被代理的,这是因为动态代理对象与目标对象类型不一样。
ProxyFactory proxyFactory = new ProxyFactory(new LandlordPro());
Service agency = (Service) proxyFactory.getAgency();
我们从代码可以很轻易看出,代理对象 agency 应该是个 Service类型,那么实际上是什么呢?
输出看一下:
System.out.println(agency.getClass());
CGLib动态代理: 通过动态地在内存中构建子类对象,重写父类的方法进行代理功能的增强。
CGLib大概的实现形式如下:子类重写扩展父类的方法。
代理父类对象:
父类——Animal
public class Animal {
public void roar(){
System.out.println("动物进行吼叫");
}
}
子类——Cat
public class Cat extends Animal{
@Override
public void roar() {
//父类实现自己的功能
super.roar();
//子类实现代理功能
System.out.println("喵喵喵~");
}
}
测试类——Test
public class Test {
@org.junit.jupiter.api.Test
public void testAgency(){
Animal cat = new Cat();
cat.roar();
}
}
jdk动态代理有一个瓶颈——使用动态代理的目标对象必须实现一个或多个接口。如果想要代理没有实现接口的类,那么就必须使用CGLib代理。
CGLib的特点:
注意: 因为CGLib需要子类扩展父类方法,所以不能代理被 final
、static
修饰的方法、类。
需要导入cglib包
父类——Animal
public class Animal {
public void roar(){
System.out.println("动物进行吼叫");
}
}
代理类——ProxyFactory
public class ProxyFactory implements MethodInterceptor {
//目标对象
private Object target;
//传入目标对象
public ProxyFactory(Object target){
this.target = target;
}
//CGLib采用低层的字节码技术,在子类中采用方法拦截的技术,拦截父类指定方法的调用,并顺势植入代理功能的代码
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//调用目标对象的方法
Object returnValue = method.invoke(target,objects);
//代理对象的功能
System.out.println("喵喵喵");
return returnValue;
}
//生成代理对象
public Object getProxyInstance(){
//使用工具类
Enhancer enhancer = new Enhancer();
//设置父类
enhancer.setSuperclass(target.getClass());
//设置回调函数
enhancer.setCallback(this);
//创建子类(代理)对象
return enhancer.create();
}
}
测试类——Test
@org.junit.jupiter.api.Test
public void testCGLibProxy(){
Animal animal = new Animal();
System.out.println(animal.getClass());
Animal proxy = (Animal) new ProxyFactory(animal).getProxyInstance();
System.out.println(proxy.getClass());
proxy.roar();
}
什么是代理模式?
无法直接访问目标对象,必须要通过代理对象作为中介来访问目标对象。
代理模式的功能?
增强功能
限制目标对象的访问
代理模式分类
静态代理
动态代理
动态代理使用到的三个类?
java.lang.reflect.proxy
负责生成代理对象
java.lang.reflect.Method
负责目标方法的回调,使用反射机制(即利用反射进行目标对象的调用)
java.lang.reflect.InvocationHandler
增强功能的代码,当外部目标对象方法被调用时,此接口的实现类被执行
CGLib动态代理的特点?
子类代理模式,在程序运行时,动态生成代理对象,实现功能扩展
目标对象不需要实现接口,全靠子类来扩展功能
规避了jdk代理中目标对象必须实现接口的弊端
被代理的类和方法不能被 final
修饰
底层是由字节码处理框架ASM来完成的