思考代理模式
代理模式是通过创建一个代理对象,用这个代理对象去代表真实的对象,客户端得到这个代理对象后,对客户端并没有什么影响,就跟得到了真实对象一样来使用。
当客户端操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真正的对象。
1.代理模式的本质
代理模式的本质:控制对象访问。
代理模式通过代理目标对象,把代理对象插入到客户和目标对象之间,从而为客户和目标对象引入一定的间接性。正是这个间接性,给了代理对象很多的活动空间。代理对象可以在调用具体的目标对象前后,附加很多操作,从而实现新的功能或是扩展目标对象的功能。更狠的是,代理对象还可以不去创建和调用目标对象,也就是说,目标对象被完全代理掉了,或是被替换掉了。
2.何时选用代理模式
需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理。需要按照需要创建开销很大的对象的时候,可以使用虚代理。
- 需要为一个对象在不同的地址空间提供局部代表的时候,可以使用远程代理。
- 需要按照需要创建开销很大的对象的时候,可以使用虚代理。
- 需要在访问对象执行一些附加操作的时候,可以使用智能指引代理。
- 需要在访问对象执行一些附加操作的时候,可以使用智能指引代理。
3.代理的分类
-
远程代理:隐藏了一个对象存在于不同的地址空间的事实,也即是客户通过远程代理去访问一个对象,根本就不关心这个对象在哪里,也不关心如何通过网络去访问到这个对象。从客户的角度来讲,它只是在使用代理对象而已。
-
虚代理:可以根据需要来创建“大”对象,只有到必须创建对象的时候,虚代理才会创建对象,从而大大加快程序运行速度,并节省资源。通过虚代理可以对系统进行优化。
-
保护代理:可以在访问一个对象的前后,执行很多附加的操作,除了进行权限控制之外,还可以进行很多跟业务相关的处理,而不需要修改被代理的对象。也就是说,可以通过代理来给目标对象增加功能。
-
智能指引:和保护代理类似,也是允许在访问一个对象的前后,执行很多附加的操作,这样一来就可以做很多额外的事情,比如,引用计数等。
4.代理模式的结构
- Proxy:代理对象,通常具有如下功能。
实现与具体的目标对象一样的接口,这样就可以使用代理来代替具体的目标对象。保存一个指向具体目标对象的引用,可以在需要的时候调用具体的目标对象。
可以控制对具体目标对象的访问,并可以负责创建和删除它。 - Subject:目标接口,定义代理和具体目标对象的接口,这样就可以在任何使用具体目标对象的地方使用代理对象。
- RealSubject:具体的目标对象,真正实现目标接口要求的功能。
5.实现
静态代理
1.创建抽象角色
/**
* @description:租房接口
*/
public interface Renting {
/**
* 租房
*/
void rentingHouse();
}
2.创建真实角色
/**
* @description:房东
*/
public class Host implements Renting{
@Override
public void rentingHouse() {
System.out.println("房东要出租房子");
}
}
3.创建代理角色
/**
* @description:中介
*/
public class Mediation implements Renting{
//中介要代理房东这个角色,代理的是具体的实现类
private Host host;
public Mediation(Host host) {
this.host = host;
}
/**
* 可以干一些其他事情,比如看房子,签合同
*/
@Override
public void rentingHouse() {
seeHouse();
host.rentingHouse();
qianhetong();
}
public void qianhetong(){
System.out.println("签合同");
}
public void seeHouse(){
System.out.println("看房子");
}
}
4.用户测试
public class Test {
public static void main(String[] args) {
//实例化一个房东对象
Host host = new Host();
//中介代理房东,所以将房东对象传给中介
Mediation mediation = new Mediation(host);
//中介调用租房子方法
mediation.rentingHouse();
}
}
5.结果
JDK动态代理
动态代理类
/**
* @description:jdk动态代理类
*/
public class JDKProxy implements InvocationHandler {
//代理的是接口
private Renting renting;
public JDKProxy(Renting renting) {
this.renting = renting;
}
/**
* @param proxy 被代理对象
* @param method 目标对象中的方法对象
* @param args 方法对象的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//看房子
System.out.println("看房子");
//反射执行接口方法(租房)
Object invoke = method.invoke(renting, args);
//签合同
System.out.println("签合同");
return invoke;
}
}
测试类
/**
* @description:测试类
*/
public class Client {
public static void main(String[] args) {
//目标对象
Renting target=new Host();
JDKProxy jdkProxy = new JDKProxy(target);
//生成代理对象
Object proxyInstance = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), jdkProxy);
Renting renting=(Renting)proxyInstance;
renting.rentingHouse();
}
}
效果
现在的动态代理类只能代理Renting 接口,将其改为Object,就能代理所有接口了,变成通用类,效果一样
/**
* @description:jdk动态代理类
*/
public class JDKProxy implements InvocationHandler {
//代理的是接口
private Object object;
public JDKProxy(Object object) {
this.object = object;
}
/**
* @param proxy 被代理对象
* @param method 目标对象中的方法对象
* @param args 方法对象的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//看房子
System.out.println("看房子");
//反射执行接口方法(租房)
Object invoke = method.invoke(object, args);
//签合同
System.out.println("签合同");
return invoke;
}
}
cglib动态代理
cglib代理类
/**
* @description:cglib代理类
*/
public class CglibProxy implements MethodInterceptor {
/**
* @param proxy 代理对象
* @param method 目标对象中的方法
* @param args 目标对象中的参数
* @param methodProxy 代理对象中的代理方法对象
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//看房子
System.out.println("cglib看房子");
//反射执行接口方法(租房)
Object rtnObj = methodProxy.invokeSuper(proxy, args);
//签合同
System.out.println("cglib签合同");
return rtnObj;
}
}
测试类
/**
* @description:测试类
*/
public class Client {
public static void main(String[] args) {
// 创建空的字节码对象
Enhancer enhancer = new Enhancer();
// 设置字节码对象的父类也就是目标类(代理的是具体的类)
enhancer.setSuperclass(Host.class);
//创建回调对象
Callback callback = new CglibProxy();
// 设置字节码对象的回调方法
enhancer.setCallback(callback);
// 得到代理对象
Host cglibProxyDemo = (Host) enhancer.create();
// 调用方法
cglibProxyDemo.rentingHouse();
}
}
结果
Mybatis的Mapper接口代理
1.创建Mapper接口
- 首先,定义一个Java接口,用于声明数据访问的方法。每个方法对应一个具体的数据库操作,如查询、插入、更新等。
- 方法的名称和参数应与要执行的SQL语句相匹配。
public interface UserMapper {
User getUserById(Long id);
List<User> getAllUsers();
void insertUser(User user);
void updateUser(User user);
void deleteUser(Long id);
}
2.创建Mapper映射文件
- 在资源文件夹下创建一个与Mapper接口相对应的XML文件,用于定义与Mapper接口中方法对应的SQL语句。
- 在XML文件中,使用、、、等标签定义具体的SQL语句,并使用id属性指定语句的唯一标识符。
<mapper namespace="com.example.mapper.UserMapper">
<select id="getUserById" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
<select id="getAllUsers" resultType="com.example.model.User">
SELECT * FROM users
</select>
<insert id="insertUser">
INSERT INTO users (id, name) VALUES (#{id}, #{name})
</insert>
<update id="updateUser">
UPDATE users SET name = #{name} WHERE id = #{id}
</update>
<delete id="deleteUser">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper>
3.创建Mapper接口代理类
- 创建一个代理类,实现InvocationHandler接口,并重写invoke方法。
- 在invoke方法中,通过SqlSession执行底层的SQL语句,根据方法名动态匹配Mapper映射文件中的SQL语句,并将方法的参数传递给SQL语句。
- 最后,通过Java的Proxy类的newProxyInstance方法创建Mapper接口的代理对象。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MapperProxy<T> implements InvocationHandler {
private SqlSession sqlSession;
private Class<T> mapperInterface;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取Mapper接口方法的名称
String methodName = method.getName();
// 获取Mapper接口方法所在的类的名称
String className = method.getDeclaringClass().getName();
// 构造Mapper接口方法对应的Mapper映射文件中的statement id
String statementId = className + "." + methodName;
// 调用SqlSession的方法执行底层SQL
if (args == null) {
return sqlSession.selectOne(statementId);
} else {
return sqlSession.selectOne(statementId, args[0]);
}
}
public T newInstance() {
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[]{mapperInterface},
this
);
}
}
4.使用Mapper接口代理:
- 创建SqlSession实例,用于与数据库进行交互。
- 创建Mapper接口代理对象,并调用其中的方法。
- MyBatis框架会通过代理对象拦截方法的调用,并根据方法名动态执行Mapper映射文件中对应的SQL语句。
SqlSession sqlSession = ...; // 获取SqlSession实例
UserMapper userMapper = new MapperProxy<>(sqlSession, UserMapper.class).newInstance();
User user = userMapper.getUserById(1L);
List<User> users = userMapper.getAllUsers();
User newUser = new User();
newUser.setId(2L);
newUser.setName("John");
userMapper.insertUser(newUser);
User updatedUser = new User();
updatedUser.setId(2L);
updatedUser.setName("Updated John");
userMapper.updateUser(updatedUser);
userMapper.deleteUser(2L);
在上述示例中,通过创建MapperProxy的实例,并调用newInstance方法来获取代理对象。然后,可以使用代理对象调用Mapper接口中的方法,实际的SQL执行和结果返回由invoke方法中的逻辑完成。
这样,MyBatis框架通过动态代理的方式,将Mapper接口的方法与底层的SQL操作关联起来,使得开发者可以通过编写Mapper接口的方式进行数据库操作,而不必编写繁琐的SQL语句。同时,还能够利用MyBatis的各种特性,如缓存、参数映射等。