结构型模式主要总结了一些类或对象组合在一起的经典结构,这些经典的结构可以解决特定应用场景的问题。结构型模式包括:代理模式、桥接模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式。
代理模式很好理解:即在不改变原有代码的情况下,通过代理类去增强或者扩展原有类的功能。
角色 | 职责 |
---|---|
抽象角色 | 通过接口或类继承真实角色实现的业务方法。一般代理和真实对象都实现接口,但对于三方的类进行代理则需要使用继承的方式进行处理 |
真实角色 | 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用 |
代理角色 | 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。 |
用户添加、修改逻辑中,现在需要对如上两个接口添加日志打印逻辑,最原始的代码如下:有两个服务UserController(业务功能模块)和LogService服务(日志打印服务)
用户业务功能模块
public class UserController {
private List<SecurityProperties.User> userList = new ArrayList<>();
@Resource
private LogService logService;
public void addUser(String userName){
long startTime = System.currentTimeMillis();
SecurityProperties.User user = new SecurityProperties.User();
user.setName(userName);
user.setPassword("123456");
userList.add(user);
//记录日志
logService.addLog("添加用户",startTime);
}
public void editUser(Integer index,String newName){
long startTime = System.currentTimeMillis();
SecurityProperties.User user = userList.get(index);
user.setName(newName);
//记录日志
logService.addLog("编辑用户",startTime);
}
}
日志打印服务
public class LogService {
public void addLog(String scene,Long startTime){
String desc = scene+"日记记录";
long endTime = System.currentTimeMillis();
//获取耗时
long duration = endTime - startTime;
System.out.println("添加日志:"+desc+",日志耗时:"+duration);
}
}
上述代码虽然完成了功能,但是有两个问题:
public interface IUserController {
void addUser(String userName);
void editUser(Integer index,String userName);
}
public class UserController implements IUserController {
private List<SecurityProperties.User> userList = new ArrayList<>();
@Override
public void addUser(String userName){
SecurityProperties.User user = new SecurityProperties.User();
user.setName(userName);
user.setPassword("123456");
userList.add(user);
}
@Override
public void editUser(Integer index,String newName){
SecurityProperties.User user = userList.get(index);
user.setName(newName);
}
}
有对应接口情况
public class UserControllerProxy implements IUserController {
private IUserController userController;
public UserControllerProxy(IUserController userController) {
this.userController = userController;
}
@Override
public void addUser(String userName){
userController.addUser(userName);
long startTime = System.currentTimeMillis();
addLog("添加用户",startTime);
}
@Override
public void editUser(Integer index,String newName){
userController.editUser(index,newName);
long startTime = System.currentTimeMillis();
addLog("编辑用户",startTime);
}
}
没有接口,或者原始类来自三方类库 则使用继承原始类的方式进行代理扩展
public void test(){
UserControllerProxy proxy = new UserControllerProxy
(new UserController());
proxy.addUser("张三");
}
上述通过静态代理解决了业务代码耦合,职责不单一的问题,但是如果类似UserController的类有很多个,那么我们需要对每一个类似UserController类创建对应的proxy,这样岂不是很麻烦,我们可以使用动态代理来解决此类问题。
在java语言中常用的动态代理模式有两种,下面我们使用如下两种方案对上述例子进行改造。
代理对象
public class LogJdkDynamicProxy {
/**
* 引入通用的日志打印服务
*/
private LogService logService;
public LogJdkDynamicProxy() {
this.logService = new LogService();
}
/**
* 创建代理对象
* @param proxiedObject 真实对象
* @return 代理对象 核心处理逻辑在InvocationHandler实现类
*/
public Object createProxy(Object proxiedObject){
ClassLoader classLoader = LogService.class.getClassLoader();
Class<?>[] interfaces = proxiedObject.getClass().getInterfaces();
LogInvocationHandler invocationHandler = new LogInvocationHandler(proxiedObject);
return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
}
/**
* 业务处理核心逻辑
*/
private class LogInvocationHandler implements InvocationHandler{
//真实对象
private Object proxiedObject;
public LogInvocationHandler(Object proxiedObject) {
this.proxiedObject = proxiedObject;
}
/***
* @param proxy 当前生成的jdk代理对象本身 而非真实对象
* 故调用反射调用方法 不能使用该对象 会陷入循环调用
* @param method 调用的方法
* @param args 调用方法的参数
* @return 调用结果
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Long startTime = System.currentTimeMillis();
//调用业务逻辑
Object result = method.invoke(proxiedObject, args);
//添加日志
logService.addLog("添加日志",startTime);
return result;
}
}
}
使用
/**
* jdk动态代理测试
*/
@Test
public void tesJdktDynamicAgent(){
LogJdkDynamicProxy proxy = new LogJdkDynamicProxy();
//创建用户服务代理并添加日志
IUserController userController = (IUserController) proxy.createProxy
(new UserController());
//创建订单服务代理并添加日志
IOrderController uController = (IOrderController) proxy.createProxy(
new OrderController());
userController.addUser("张三");
}
jdk动态代理是基于接口进行动态代理的实现,对于没有接口的类,可以cglib其代理类是基于继承。
代理对象
public class LogCglibDynamicProxy {
/**
* 引入通用的日志打印服务
*/
private LogService logService;
public LogCglibDynamicProxy() {
this.logService = new LogService();
}
/**
* 创建代理对象
* @param targetClass 目标对象的class
* @return 代理对象 核心处理逻辑在LogMethodInterceptor实现类
*/
public Object createProxy(Class targetClass){
//创建增强类
Enhancer enhancer = new Enhancer();
//可以设置代理接口
//enhancer.setInterfaces(new Class[]{Animal.class});
//传入被代理的类 父类class
enhancer.setSuperclass(targetClass);
//构建cglib中被代理方法详情类
Callback callback = new LogMethodInterceptor();
enhancer.setCallback(callback);
//生产代理类
return enhancer.create();
}
private class LogMethodInterceptor implements MethodInterceptor {
/**
* @param obj cglib创建代理对象
* @param method 需要调用真实对象的方法
* @param args 真实对象的入参
* @param methodProxy cglib创建
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Long startTime = System.currentTimeMillis();
//调用业务逻辑
Object result = methodProxy.invokeSuper(obj, args);
//添加日志
logService.addLog("添加日志",startTime);
return result;
}
}
}
/**
* cglib动态代理测试
*/
@Test
public void testCglibDynamicAgent(){
LogCglibDynamicProxy proxy = new LogCglibDynamicProxy();
UserController userController = (UserController) proxy.createProxy(UserController.class);
userController.addUser("李四");
}
动态代理的作用:
1.在目标类源代码不改变的情况下,增加目标类的功能
2.当涉及到多个方法都需要增加某些同样的功能时,使用动态代理可以减少代码的重复
3.可以使目标类专注于业务逻辑代码
4.实现解耦合,让业务功能和日志、事务和非事务功能分离
核心代码逻辑梳理
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
//获取代理对象的class
----> Class<?> cl = getProxyClass0(loader, intfs);
//从缓存proxyClassCache中获取class,class创建交由ProxyClassFactory
----> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//调用apply生成class
---->ProxyClassFactory#apply()方法
//具体生成字节类型的class数据
---->byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
保存生成的代理对象
public void tesJdktDynamicAgent(){
//使用该方式 保存在项目根目录 com.sun下 尝试一下不好使
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
LogJdkDynamicProxy proxy = new LogJdkDynamicProxy();
//创建用户服务代理并添加日志
IUserController iUserController = new UserController();
IUserController userController = (IUserController) proxy.createProxy(iUserController);
//手动输出保存class文件
saveClassFile(iUserController.getClass(),"UserControllerProxy");//调用保存为class字节码文件
userController.addUser("张三");
}
/**
* 保存class文件
* @param clazz class对象
* @param proxyName 代理类的名字,可随意指定
*/
public void saveClassFile(Class clazz,String proxyName) {
//生成class的字节数组,此处生成的class与proxy.newProxyInstance中生成的class除了代理类的名字不同,其它内容完全一致
byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
String path = clazz.getResource(".").getPath();
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path + proxyName + ".class");
fos.write(classFile);//保存到磁盘
fos.flush();
}catch(Exception e) {
e.printStackTrace();
}finally {
try {
fos.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
代理对象
/**
* jdk自动生产的代理对象
* java是单继承 因为jdk动态代理继承了Proxy 所以代理对象只能对接口进行代理增强
*/
public final class UserControllerProxy extends Proxy implements IUserController {
private static Method addUserMethod;
private static Method editUserMethod;
//静态代码块获取到IUserController代理增强接口的对应方法
static {
try {
addUserMethod = Class.forName("com.xiu.design.structure.proxy.staticp.IUserController").getMethod("addUser", Class.forName("java.lang.String"));
editUserMethod = Class.forName("com.xiu.design.structure.proxy.staticp.IUserController").getMethod("editUser", Class.forName("java.lang.Integer"), Class.forName("java.lang.String"));
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
//通过构造器传入代理增强的handler处理器
public UserControllerProxy(InvocationHandler h) {
super(h);
}
@Override
public final void addUser(String var1) {
try {
//调用InvocationHandler的invoke方法
//invoke(Object proxy, Method method, Object[] args)
super.h.invoke(this, addUserMethod, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
//省略hashcode,equals、toString方法
}
Jdk动态代理的拦截对象是通过反射的机制来调用被拦截实例方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,调用方法时根据方法的签名来计算索引,通过索引来直接调用相应的方法。
这也就是为什么CGLIB生成的动态代理会包含3个class文件的原因,其中一个是生成的代理类,另外两个类都是FastClass机制需要的。另外两个类都继承了FastClass这个类。其中一个class为生成的代理类中的每个方法建立了索引,另外一个则为我们被代理类的所有方法包含其父类的方法建立了索引。
//不同于jdk对接口进行代理
public class UserControllerCGlibProxy extends UserController implements Factory {
//省略部分代码
//拦截器对象
private MethodInterceptor interceptor;
private static Method addUserMethod = null;
private static MethodProxy addUserMethodProxy = null;
private static Object[] argsArray = null;
//获取对应的代理方法
static void CGLIB$STATICHOOK1() throws ClassNotFoundException {
argsArray = new Object[0];
Class currentClass = Class.forName("com.xiu.design.structure.proxy.staticp.UserController$$EnhancerByCGLIB$$a97337c8");
Class var1;
Method[] targetMethods = ReflectUtils.findMethods(new String[]{"addUser", "(Ljava/lang/String;)V", "editUser", "(Ljava/lang/Integer;Ljava/lang/String;)V"}, (var1 = Class.forName("com.xiu.design.structure.proxy.staticp.UserController")).getDeclaredMethods());
addUserMethod = targetMethods[0];
addUserMethodProxy = MethodProxy.create(var1, currentClass, "(Ljava/lang/String;)V", "addUser", "CGLIB$addUser$0");
}
@Override
public final void addUser(String var1) throws Throwable {
MethodInterceptor interceptor = this.interceptor;
//如果没有对应拦截器则需要绑定方法
if (interceptor == null) {
//获取拦截器 MethodInterceptor拦截器
bindInterceptor(this);
interceptor = this.interceptor;
}
if (interceptor != null) {
//调用拦截器的intercept 进行代理增强
interceptor.intercept(this, addUserMethod, new Object[]{var1}, addUserMethodProxy);
} else {
super.addUser(var1);
}
}
}
当我们通过xml 或者注解,都会注册AnnotationAwareAspectJAutoProxyCreator实例 本测试基于spring-aop版本4.3.13
查看继承关系可以发现,此类实现了Aware与BeanPostProcessor接口,这两个接口都和spring bean的初始化有关,有理由推测此类主要处理方法都来自这两个接口的实现方法,其关键方式是在AbstractAutoProxyCreator#wrapIfNecessary其中进行动态代理的处理
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//如果对象已经创建则返回
if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
return bean;
}
//不需要进行动态代理返回
else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
//4.3.2 isInfrastructureClass判断如果class是 Advice、Pointcut、Advisor、AopInfrastructureBean 不会进行动态代理
// !this.shouldSkip 且不跳过进行代理增强
else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
//动态代理
Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} else {
//不需要进行动态代理
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
} else {
//不需要进行动态代理
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
}
逻辑梳理
~~~java
//进行动态代理
createProxy(beanClass,beanName,specificInterceptors,targetSource)
//ProxyFactory获取代理对象
—>ProxyFactory#getProxy(java.lang.ClassLoader)
//AopProxy 根据类型创建jdk动态代理或者cglib代理
—>AopProxy#getProxy(java.lang.ClassLoader)
~~~
在spring中使用 Mybatis,一般是定义相关的接口并自动生成对应的mapper。当调用 SqlSession 的 getMapper 方法时,会对传入的接口生成一个代理对象,而程序要真正用到的就是这个代理对象,在调用代理对象的方法时,Mybatis 会取出该方法所对应的 sql 语句,然后利用JDBC 去执行 sql 语句,最终得到结果。基于mybatis 3.5.2和mybatis-plus 3.2.0测试
@Autowired
SqlSessionFactory sqlSessionFactory;
@Test
public void mapperTest(){
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
CdsAxBatchMapper axBatchMapper = sqlSession.getMapper(CdsAxBatchMapper.class);
CdsAxBatch cdsAxBatch = axBatchMapper.selectById("1");
System.out.println("batch : " + cdsAxBatch);
}
}
普通sql的流程分析
//交由配置获取mapper
this.configuration.getMapper(type, this)
//mapperRegistry获取mapper对象
this.mapperRegistry.getMapper(type, sqlSession);
//笔者此处使用了mybatis-plus所以此处
this.mybatisMapperRegistry.getMapper(type, sqlSession);
//mapper代理工厂创建mapper代理对象
mapperProxyFactory.newInstance(sqlSession)
//最终创建MapperProxy对象。maybatis-plus创建的是MybatisMapperProxy
MapperProxy或者MybatisMapperProxy 都实现了InvocationHandler接口,所以此处对于mapper接口,mybatis使用jdk动态代理的形式创建代理对象。