一、介绍
在某些操作中我们经常需要去从A表中查询出字段信息,然后去关联查询B表。最后做组合。此类为我们常用的业务,除了在SQL层进行处理外,我们可以通过封装组件的形式进行处理,形成全局通用化功能!
二、思想解析
-------了解开闭原则,为扩展性开发提供了一个方向
-------了解单一职责原则,明确方法应该具备单一职能的原则
-------了解元注解的使用,元注解的使用方式两种与基本配置
-------了解反射体系分为公用反射与暴力反射
-------了解spring的框架源码
-------了解AOP的原理与应用
-------了解SpirngBoot注解的基本配置
三、代码的解析与设计与优化
1、开闭原则:
允许对组件功能的扩展修改,对代码的修改是关闭的。也就是说你可以在不改变我代码的前提下丰富或者删减我的行为或者属性。
研讨交流中,我们重新定义对对象的了解是这样的:
编程是一种构造世界的行为,万物皆对象,每个对象都有其自己的特性和方式。死物我们约定为 特性和功能。活物我们约定为行为和属性。
(这些只是我们自己的思考方式,只要你开心能理解,把对象定义为屎都OK)
既然对象是一块屎,啊呸,呸,呸,呸~。绝不允许这种屎味的编程,这是异端,坚决铲除~
从java语言规范来说,一般而言,我们常用的有两种方式,【我们可以通过继承或者实现的方式对目标对象进行封装扩展,也可以通过代理模式对对象进行代理和丰富。】(常规开闭原则的使用)
说明:一般情况下,生产中的项目对于属性都是执行了私有化操作,我们都知道【子类无法继承父类的私有化变量】这句话,但实际上。当子类存在的时候,在内存中是将子类的属性与父类的属性放置在一起作为子类的属性,可以理解为子类包裹着父类的属性和方法,只是由于权限修饰符的限制导致了父类与子类之间的域不同,限制了访问。
当子类继承了父类之后,子类可以通过父类的公共get/set方法间接地执行修改。实际上这个位置并不是将父类的私有变量暴露给子类,而是父类选择开放了使用权给子类,子类向父类提交申请,但是父类如何操作的细节并没有向子类开放。(因为内存结构的原因而提出子类同样继承了父类的私有属性的说法,实际上是错误的)
(说了那么多是为了说明开闭原则在对对象功能进行扩展时,如果使用继承或实现类方式,父类必须开放私有属性的继承权限给子类,否则子类需要全量引用),如下:
子类:
结论:基于开闭原则我们如果存在对对象属性的丰富,可以通过继承或者实现的方式来满足其开闭原则。在进行数据数据传传递利用面向接口编程的特性实现无原对象无异动的修改。
2、单一职责原则
一个类或者模块应该有且只有一个改变的原因。一个类只有一个职责,如果职责过多,代码就会臃肿,可读性更差,也更难以维护。
研讨交流中,我们认为一个模块最佳状态是保持每个方法都只有单一的功能单元,如果存在多个,随着开发系统的维护和复杂度越来越高,这个模块或方法的职能将越来越复杂。不利于理解。
说明:为维护单一职责的原则,一般而言,我们有如下几种方式。【通过切面注解的方式将公共的功能给独立出来】,【将复杂的功能给予封装】、【将请求传递过来的数据给予分拆处理】。
四、组件
场景:当我们需要维护一个数据对象时候,可能存在某些字段(属性)缺失,或者需要根据查询出来的数据作为条件进行二次查询
这种场景,我们必然维护以上的开闭原则和单一职责原则,结合其处理方式,我们可以将此部分查询进行分拆。以切面的形式完成二次查询。从而避免在同一个方法里面进行二次查询。
<定义一个用于切面的锚点>
/**
* @Author: CYQ
* @Description: 关联查询-组件化封装---直接给别人用作为一个切面标签(切在方法上)
* @Date: Created in 2020/4/9 10:42
* @Modified By:
*/
@Target({ElementType.TYPE,ElementType.METHOD}) //封装限定在方法上(元注解[注解的注解])
@Retention(RetentionPolicy.RUNTIME) //系统编译环境运行时,编译解析型语言(开发-->编译-->加载)
public @interface LinkedQuery{
}
说明:此处的注解为一个空白的锚点标签。用于给AOP代理进行捕获定位。
/**
* @Author: CYQ
* @Description: 设置入参注解--将查询对象中确失的注解进行添加获取想要调用的对象,方法名,传入参数
* @Date: Created in 2020/4/9 10:58
* @Modified By:
*/
@Target({ElementType.METHOD}) //方法范围
@Retention(RetentionPolicy.RUNTIME) //限定阶段
public @interface NeedSetValue {
//需要调用哪个对象
Class> beanClass();
//需要给到的参数
String[] params();
//需要调用的方法
String method();
//需要获取目标方法查询出来的结果里面的哪个属性
String targetFeild();
//是否写入缓存中
boolean isCache() default false;
}
说明:用于注解数据对象中缺失的字段,二次查询的目标是为了通过另外的查询方式查到该数据并补充完整。
/**
* @Author: CYQ
* @Description: 封装拦截器
* @Date: Created in 2020/4/9 10:56
* @Modified By:
*/
@Aspect
@Component
public class SetFeildValueAspect {
//环绕注解(围绕这个注解进行切面)
@Around("@annotation(com.yuanyue.steam.comom.mode.query.LinkedQuery)")
public Object doSetFeildValue(ProceedingJoinPoint pjp) throws Throwable {
Object res = pjp.proceed();//执行被切面的方法获取结果集[{id:1,customId: null @NeedSetValue(属性)},{},{}]
//获取结果集合,将没有值的集合设置进值即可。
if (res instanceof Collection) {
BeanUtils.setFieldValueForceCol((Collection) res);
}
return res;
}
}
反射工具类:
/**
* @Author: CYQ
* @Description: 注解化--利用注解反射从代码中获取集合--利用注解为空属性赋值
* @Date: Created in 2020/4/9 11:03
* @Modified By:
*/
@Component
public class BeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
/**
* CYQ: 封装的组件信息--->获取注解--->AOP横切注解的方法,得到集合数据---->
* @param col ---查询的结果列表(对象中添加了)
* @throws Exception
*/
public static void setFieldValueForceCol(Collection col) throws Exception {
//获取注解 --- 反射method想要调用的方法名 method.invoke():通过反射方式调用方法--->User对象---->name属性--->set到结果集中
Class> clazz = col.iterator().next().getClass();
Field[] fields = clazz.getDeclaredFields();
//参数用于存放可能存在的
Map cache = new HashMap<>();//设置缓存--->存放容器
for (Field needfield : fields){
NeedSetValue sv= needfield.getAnnotation(NeedSetValue.class);
if (sv == null){
continue;
}
needfield.setAccessible(true);//设置为可见操作
Object bean = applicationContext.getBean(sv.beanClass());
//得到方法需要入参的参数属性与个数(多个)
Class[] cArg = new Class[sv.params().length];
Field[] paramsFields = new Field[sv.params().length];
for (int i=0 ;i