代理模式
1. 代理模式定义
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
换句话说:当我们要扩展一个类,使其功能更加丰富时,我们可以不直接修改这个类,而是创建代理对象,使用代理对象中调用这个类,并添加而外的功能,这样就可以不修改这个类而增加功能,常用于权限控制,日志记录等!
举个例子:
我们在项目中经常在操作数据库前后用到日志来记录操作信息,而反复的日志代码不仅破坏了代码的美观程度,还会因为重复代码损失相当多时间,如下代码所示:
package com.wojiushiwo;
import java.util.Date;
/**
* @author 我就是我500
* @date 2020-02-20 15:01
* @describe
**/
public class ProxyTest {
public static void main(String[] args) {
UserDAO userDao =new UserDaoImpl();
//删除用户1并做好日志处理
System.out.println("开始请求删除用户1,时间:"+new Date());
if(userDao.deleteUser(1))
{
System.out.println("用户1删除成功,请求时间:"+new Date());
}
else
{
System.out.println("用户1删除失败,请求时间:"+new Date());
}
//删除用户2并做好日志处理
System.out.println("开始请求删除用户2,时间:"+new Date());
if(userDao.deleteUser(2))
{
System.out.println("用户2删除成功,请求时间:"+new Date());
}
else
{
System.out.println("用户2删除失败,请求时间:"+new Date());
}
//删除用户3并做好日志处理
System.out.println("开始请求删除用户3,时间:"+new Date());
if(userDao.deleteUser(3))
{
System.out.println("用户2删除成功,请求时间:"+new Date());
}
else
{
System.out.println("用户2删除失败,请求时间:"+new Date());
}
}
}
//DAO接口
interface UserDAO
{
boolean deleteUser(int id);
}
//DAO实现类
class UserDaoImpl implements UserDAO
{
//模拟删除一个用户
@Override
public boolean deleteUser(int id) {
System.out.println("数据库操作......");
return true;
}
}
这时虽然可以执行,但是重复代码相当对,这个时候我们就使用到了代理模式,可以将以上重复代码写入代理类中,使用代理类对Dao类进行自定的日志输出处理!
2. 静态代理模式
静态代理就是手动创建代理对象,好处是简单,坏处就是每一个要扩展的类都需要一个代理对象,增加工作量。
静态代理要求:
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一
起实现相同的接口或者是继承相同父类。
示例:将上方代码改为静态代理模式
package com.wojiushiwo;
import java.util.Date;
/**
* @author 我就是我500
* @date 2020-02-20 15:01
* @describe
**/
public class ProxyTest {
public static void main(String[] args) {
//创建目标对象
UserDAO userDao =new UserDaoImpl();
//将目标对象传给代理对象
UserDAO proxy =new UserDaoProxy(userDao);
proxy.deleteUser(1);
proxy.deleteUser(2);
proxy.deleteUser(3);
//这时,我们只是调用了基础的方法,就可以连同输出语句一起调用
}
}
//DAO接口
interface UserDAO
{
boolean deleteUser(int id);
}
//DAO实现类
class UserDaoImpl implements UserDAO
{
//模拟删除一个用户
@Override
public boolean deleteUser(int id) {
System.out.println("数据库操作......");
return true;
}
}
//代理类
class UserDaoProxy implements UserDAO
{
UserDAO userDAO;
@Override
public boolean deleteUser(int id) {
System.out.println("开始请求删除用户"+id+",时间:"+new Date());
boolean result = userDAO.deleteUser(id);
if(result)
{
System.out.println("用户"+id+"删除成功,请求时间:"+new Date());
}
else
{
System.out.println("用户"+id+"删除失败,请求时间:"+new Date());
}
return result;
}
public UserDaoProxy(UserDAO userDAO)
{
this.userDAO=userDAO;
}
}
执行结果:
这是我们可以看到,我们只需定义代理对象,然后将代理对象中进行原有功能的增强(增加日志信息),即可在调用时自动执行!
在此案例中,我们的目标对象(UserDaoImpl)对数据库进行操作,定义UserDaoProxy代理类对目标对象进行代理,并实现UserDAO接口,这样我们在新建目标对象只需要使用代理对象对目标对象进行处理,因为实现统一接口,我们拿到的代理对象也是UserDao类型,这是我们不再使用原对象,而是使用代理对象,使用方法与之前完全相同,又因为多态性,使用时可以自动调用代理对象中已经加入了日志处理的代码,这样就可以实现更加简洁的代码!
Java经典静态代理模式应用:
Java线程操作时,需要实现Runnable接口,并使用Thread类进行操作,实际上,这就是代理模式的一个典型应用!
Thread类中包含了线程启动的一些底层方法,而这些方法不需要手动调用,只需要使用Thread方法的Start方法即可自动生成一个新的线程并执行Run方法!
package com.wojiushiwo;
import com.sun.org.apache.bcel.internal.generic.RET;
import java.util.Date;
/**
* @author 我就是我500
* @date 2020-02-20 15:01
* @describe
**/
public class ProxyTest {
public static void main(String[] args) {
MyRunnable runnable =new MyRunnable();
Thread thread =new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable
{
@Override
public void run() {
System.out.println("线程开始了!");
}
}
3. JDK动态代理
相比较于静态代理,静态代理每一个代理类都需要手写,而动态代理则可以对任何对象进行代理,只要增强代码的逻辑相同,就可以直接使用,动态代理的对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理,这是动态代理的弊端之一,代理对象的生成,是利用JDK的API, 动态的在内存中构建代理对象,动态代理也叫做: JDK代理、 接口代理。
方法简介:
动态代理是使用JDK内提供的方法生成代理对象,涉及到Java反射机制,所在包是java.lang.reflect,涉及的类用类Proxy类,使用方法是Proxy.newProxyInstance方法。
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
其中:
- ClassLoader:类所使用的ClassLoader类加载器
- Interfaces:目标对象所实现的接口Class数组,代理类可以代理其中农的方法
- InvocationHandler:确定代理类最终会绑定在那个Handler上,换句话说,接口里定义了代理方法所执行的逻辑。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
//这个方法里的代码就是我们调用代理对象方法时要执行的逻辑
}
此接口中只有一个抽象方法invoke此方法有几个参数:
- Proxy:被代理的目标对象(原对象)
- Method:调用了什么方法
- Args:调用方法时传递的参数
- return:调用方法时的返回值
我们在此方法中定义的语句,就是我们调用代理对象任意方法(接口里的抽象方法)后所执行的代码!
总结来说:
我们使用Proxy.newProxyInstance方法创建代理对象,创建时需要传入加载类使用的类加载器,目标对象所实现的接口,和要执行的代码的Handler,在代理对象调用方法时,我们可以通过Hnadler获取到,在此Handler中定义我们调用代理对象时所执行的代码,并且可以获取到调用方法时所传递的参数,还可以手动确定代理对象调用后的返回值!
这里涉及到Java反射机制,JVM类加载器等知识点,可以看我其他帖子,里面有比较详细的介绍!
实例演示:将上方代码使用动态代理重写
package com.wojiushiwo;
import com.sun.org.apache.bcel.internal.generic.RET;
import com.sun.org.apache.xpath.internal.operations.Bool;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
/**
* @author 我就是我500
* @date 2020-02-20 15:01
* @describe
**/
public class ProxyTest {
public static void main(String[] args) {
UserDAO userDAO = new UserDaoImpl();
UserDAO proxy = getProxyInstance(userDAO);
proxy.deleteUser(1);
proxy.deleteUser(2);
proxy.deleteUser(3);
//这时,我们只是调用了基础的方法,就可以连同输出语句一起调用
}
//定义获取代理对象的方法
public static <T> T getProxyInstance(T t)
{
//创建InvocationHandler,里面定义了调用代理方法后所执行的代码
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始请求删除用户"+args[0]+",时间:"+new Date());
//执行原本的方法,这里可以进行其他判断,比如获取方法名称调用等
Object result = method.invoke(t,args);
if((boolean)result)
{
System.out.println("用户"+args[0]+"删除成功,请求时间:"+new Date());
}
else
{
System.out.println("用户"+args[0]+"删除失败,请求时间:"+new Date());
}
return result;
}
};
return (T)Proxy.newProxyInstance(t.getClass().getClassLoader(),t.getClass().getInterfaces(),handler);
}
}
//DAO接口
interface UserDAO
{
boolean deleteUser(int id);
}
//DAO实现类
class UserDaoImpl implements UserDAO
{
//模拟删除一个用户
@Override
public boolean deleteUser(int id) {
System.out.println("数据库操作......");
return true;
}
}
结果与上方完全相同:
4. Cglib动态代理
静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现,代理这就Cglib代理。Cglib代理也叫作子类代理,它是在内存中构建-一个子类对象从而实现对目标对象功能扩展,有些书也将Cglib代理归属到动态代理。Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口,它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。
那么在AOP编程中如何选择代理模式:
其实Cglib动态代理与JDK动态代理没有优劣之分,可以根据需求选择,比如:
- 目标对象需要实现接口,用JDK代理
- 目标对象不需要实现接口,用Cglib代理
方法简介:
cglib的jar下载地址:
https://download.csdn.net/download/qq_42628989/12177838
先引入jar包:cglib-nodep-2.2.2.jar
Cglib创建动态代理对象需要实现MethodInterceptor接口,并实现其中的intercept
方法。Interect方法中所定义的代码就是我们调用代理对象时所执行的方法!
interface MethodInterceptor
{
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable;
//这个方法里的代码就是我们调用代理对象方法时要执行的逻辑
}
其中:
Obj:被代理的目标对象(原对象)
Method:调用了什么方法
Args:调用方法时传递的参数
methodProxy:生成的代理对象
return:调用方法时的返回值
MethodInterceptor是我们的方法拦截器,等同于上文中的InvocationHandler,而创建代理对象需要使用Enhancer类,Enhancer生成代理对象主要用到以下三种方法:
- enhancer.setSuperclass(目标对象);
- enhancer.setCallback(方法拦截器);
- enhancer.create();
调用create()后所生成的对象即是我们的代理对象!
实例演示:将上方代码改为Cglib实现
package com.wojiushiwo;
import com.sun.org.apache.bcel.internal.generic.RET;
import com.sun.org.apache.xpath.internal.operations.Bool;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;
/**
* @author 我就是我500
* @date 2020-02-20 15:01
* @describe
**/
public class ProxyTest {
public static void main(String[] args) {
UserDao userDao =new UserDao();
UserDao proxy = getProxyInstance(userDao);
proxy.deleteUser(1);
proxy.deleteUser(2);
proxy.deleteUser(3);
}
public static <T> T getProxyInstance(T t)
{
MethodInterceptor methodInterceptor = new MethodInterceptor()
{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("开始请求删除用户"+args[0]+",时间:"+new Date());
Object result = method.invoke(t,args);
if((boolean)result)
{
System.out.println("用户"+args[0]+"删除成功,请求时间:"+new Date());
}
else
{
System.out.println("用户"+args[0]+"删除失败,请求时间:"+new Date());
}
return result;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(t.getClass());
enhancer.setCallback(methodInterceptor);
return (T)enhancer.create();
}
}
class UserDao
{
//模拟删除一个用户
public boolean deleteUser(int id) {
System.out.println("数据库操作......");
return true;
}
}
执行结果完全相同!
cglib动态代理与JDK动态代理步骤基本相同,两者可以类比学习!