前言:
前一篇,我有介绍用基类实现MybatisPlus的的CURD,然后各子类中继承并重写来实现通用CRUD。
本文介绍通过JDK反射的方式来实现。可以根据入参中实体类的类型自动调用合适的实现类去执行相关服务。适合在一些入参不明确的通用接口或方法中使用。
1、实现思路。
举例,我们定义一个动物类,这个里面定义了通用方法吃。
然后,狗类,猫类就可以继承并实现它。这样调用时,我们只需要调用动物类就可以了。
Animal animal1 = new Animal(Dog);
animal1.eat("骨头");
Animal animal2 = new Animal(Cat);
animal2.eat("鱼");
这样,对外暴露,所有的方法都只需要调用animal类就好了,这个基类里定义了通用方法eat以及eat的具体行为。子类dog\cat分别继承并重写。比如狗什么都吃,但猫只吃鱼。那么就可以在dog\cat类进行个性化实现。
对应到我们的service,比如,我有一个接口,接收一个实体类。然后去保存数据。但是这是一个通用接口,不确定实体类是user\student还是什么。那么,我们就可以写一个代理类。用代理的保存方法。在代理类根据入参的类型和条件自动找对应的服务来实现保存功能。
2、建立反射的通用工具类。
interface service provide简称ISP,定义一个通用接口
这个接口中,定义了两个方法。一个是order方法,用于排序。
一个verify方法。因为实现的子类会有很多,我们是让所有实现类都去执行,还是仅某个实现类执行,可通过这个方法验证。
package com.luo.comm.utils.spi;
/**
* 这是一个接口类,供代理类调用
*/
public interface ISpi<T> {
boolean verify(T condition) throws InstantiationException, IllegalAccessException;
/**
* 排序使用
*/
default int order() {
return 10;
}
;
}
3、建立反射的工厂类,便于快捷调用
这是一个标准工厂类,基本上都是通用代码不用改,下面这一行代码解释一下:
if (spi.verify(args[0].getClass().toString())) { // 第一个参数作为条件选择 return method.invoke(spi, args); }
这代码的意思是,将代码类第一个参数提交给实现类的verify方法。如果返回为真则执行。否则不执行。如果要改参数方法,校验方法,只需要改这儿就行了。
执行是标准的代理方法。完整的代码如下:
package com.luo.comm.utils.spi;
import com.luo.comm.jovo.BusinessException;
import com.luo.comm.utils.spi.ISpi;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
/**
* Service Provider Interface 简称SPI,是服务商接口的意思,用于对外统一服务
*
* @Author bill luo
* 这是一个SPI的工具类,有约定ISpi.order排序,ISpi.verify返回为true则执行,如果返回是false则不执行。
* 如果有多个ISpi的实现类中verify满足条件,则只会在第一个满足条件的实现类中执行。
*/
public class SpiFactoryBean<T> implements FactoryBean<T> {
private Class<? extends ISpi> spiClz;
private List<ISpi> list;
public SpiFactoryBean(ApplicationContext applicationContext, Class<? extends ISpi> clz) {
this.spiClz = clz;
Map<String, ? extends ISpi> map = applicationContext.getBeansOfType(spiClz);
list = new ArrayList<>(map.values());
list.sort(Comparator.comparingInt(ISpi::order));
}
@Override
@SuppressWarnings("unchecked")
public T getObject() throws Exception {
// jdk动态代理类生成
InvocationHandler invocationHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
for (ISpi spi : list) {
System.out.println(args[0]);
if (spi.verify(args[0].getClass().toString())) {
// 第一个参数作为条件选择
return method.invoke(spi, args);
}
}
// 如果最后没有被执行,返回 -1
throw BusinessException.error("MP服务出错,proxy没有找到任何可用服务");
// throw new NoSpiChooseException("no spi server can execute! spiList: " + list);
}
};
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{spiClz},
invocationHandler);
}
@Override
public Class<?> getObjectType() {
return spiClz;
}
}
4、将工厂类通过config注入到Ioc容器
注入后,可以通过resouce使用该组件
package com.luo.comm.config;
import com.luo.comm.services.mp.IMpService;
import com.luo.comm.utils.spi.SpiFactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class SpiConfig {
@Bean
public SpiFactoryBean SpiPoxy(ApplicationContext applicationContext) {
return new SpiFactoryBean(applicationContext, IMpService.class);
}
@Bean
@Primary
public IMpService iMpService(SpiFactoryBean spiFactoryBean) throws Exception {
return (IMpService) spiFactoryBean.getObject();
}
}
5、定义对外暴露的服务接口
在这个接口列表中,定义save\update\remove\select等通用方法。
具体逻辑不展开介绍了,以save为例 。proxySave是对外暴露的方法,用default方法修饰。业务逻辑中只调用了supper.save方法。也可以自行增加before\after方法。
proxySave使用default修饰后,实现类是不能重写的,只能调用。实现需要实现save方法。
package com.luo.comm.services.mp;
import com.luo.comm.jovo.BusinessException;
import com.luo.comm.utils.mp.MpUtils;
import com.luo.comm.utils.spi.ISpi;
public interface IMpService extends ISpi<String> {
/**
* proxySave 通用保存方法,即添加一条记录
* proxyXXXX 相关方法,要保证第一个参数是要实体类,这个是用来区分是用哪个服务执行的。后面的参数可以随意。
* 比如,proxySelect(User,"haha")。User类是用来表示用UserService来执行的关键。
* @param beanObj,指需要一个实体类。比如User,Userinfo。
* 用实体类的好处,避免产生字段名称不对的问题,字段是否存在前道工序解决,传到这里来的一定是一个标准的对象。
* @return Integer i 。返回影响的行数。 beanObj本身会修改,如果要想获取新值,则直接使用beanObj即可。
* */
default int proxySave(Object beanObj) {
// beanObj是指需要一个实体类。比如User,Userinfo。
// 执行save动作
try {
return save(beanObj);
} catch (Exception ex) {
throw BusinessException.error("ex");
}
}
default Object proxySelectById(Object beanObj) {
// 。。。
// 执行select动作
try {
return selectById(beanObj);
} catch (Exception ex) {
throw BusinessException.error("ex");
}
}
default Object proxyUpdate(Object beanObj) {
// 。。。
// 执行select动作
try {
return update(beanObj);
} catch (Exception ex) {
throw BusinessException.error("ex");
}
}
default Object proxyDelete(Object beanObj) {
// beanObj必须包括id,updated_at字段
// 执行删除。全局变量中已经配置软删除is_delete=1表示删除。
try {
return delete(beanObj);
} catch (Exception ex) {
throw BusinessException.error("ex");
}
}
int save(Object beanObj) throws InstantiationException, IllegalAccessException;
Object selectById(Object beanObj) throws InstantiationException, IllegalAccessException;
Object update(Object beanObj) throws InstantiationException, IllegalAccessException;
int delete(Object beanObj) throws InstantiationException, IllegalAccessException;
}
6、实现类使用。
实现类通过implements继承接口IMpService接口,需要重写save方法。因为基类中save方法是抽像方法。并未定义任何作用。
实现类可能会有很多个,通过verify方法识别。返回true会执行,false不执行。这一段的逻辑在SpiFactoryBean中已经进行了封装。这个userService只执行入参的对象class是User对象的,其他的不执行。所以返回cls.equals(condition)即可。
package com.luo.comm.services.mp;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.luo.comm.entity.User;
import com.luo.comm.mapper.UserMapper;
import com.luo.comm.utils.mp.MpUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService implements IMpService {
// 复制为一个新的Service时,替换下面的targetMapper、target、queryWrapper,下面的相关服务尽量不要用个性化变量
@Resource
UserMapper targetMapper;
private QueryWrapper<User> queryWrapper = new QueryWrapper<>();
private User target =new User(); // UserService 采用注入方式被引用时该变量是共享的,所以使用时,要通过newTarget产生一个新实例!!
// 下面的方法尽量要保持公有、标准化。如果有全局的变量,尽量在上面定义好。
public User newTarget () throws InstantiationException, IllegalAccessException {
return target.getClass().newInstance(); // 每次都会产生一个新的变量。
}
public boolean verify(String condition) throws InstantiationException, IllegalAccessException {
target =newTarget();
String cls = target.getClass().toString();
return cls.equals(condition);
}
// 下面是重写IMpService的方法。
/**
* 通用保存方法
* @param beanObj 接收一个实体类对象。
* */
@Override
public int save(Object beanObj) throws InstantiationException, IllegalAccessException {
target =newTarget();
// 调用保存方法前处理过程,添加或删除一些字段,处理后的obj将属性复制到target实体类。
Object newObj = MpUtils.saveBeforeHandle(beanObj);
BeanUtils.copyProperties(newObj,target); //将newObj的属性复制到target中去。
// 执行insert方法
int i =targetMapper.insert(target); // 返回影响的行数
// 返向赋值,利用beanObj是引用数据类型的特性,这样调用者使用原对象就可以获得新值
BeanUtils.copyProperties(target,beanObj);
// return 影响行数
return i;
}
/**
* 通用保存方法
* @param beanObj 接收一个实体类对象。
* */
@Override
public Object selectById(Object beanObj) throws InstantiationException, IllegalAccessException {
target =newTarget();
BeanUtils.copyProperties(beanObj,target);
// 如果id不存在,则返回原对象。
if(target.getId()==null){
return beanObj;
}
target=targetMapper.selectById(target.getId());
System.out.println(target);
return target;
}
@Override
public Object update(Object beanObj) throws InstantiationException, IllegalAccessException {
target =newTarget();
BeanUtils.copyProperties(beanObj,target);
System.out.println(beanObj);
return beanObj;
}
@Override
public int delete(Object beanObj) throws InstantiationException, IllegalAccessException {
return 0;
}
}
这个里面target是没必要存在的。我加上的目的是为了复制使用的方法,只需要改target就行了
7、controller使用。
Resource IMpService mpProxy;
后面调用时,直接用mpProxy这个代理类就行了。
@Resource
IMpService mpProxy;
/**
* MybatisPlus 通用save方法,传递一个实体类。
* */
@PostMapping("/save")
public ResultsObj save(@RequestBody User user) {
int i = mpProxy.proxySave(user);
ResultsObj resultsObj = i>0? new ResultsObj(user):new ResultsObj(MyResEnum.FAIL);
return resultsObj;
}