前言
在看一个项目源码的时候发现一个很有趣的封装 是对mybatis-plus进行了二次封装 来达到一个减少controller重复crud代码量的一个封装 于是抱着学习的态度学习了一下
首先我们知道对于一个javaweb后端来一般都有三层 controller service dao 不同的业务会有很多相似的代码,比如crud 一般来说大部分业务都有一些添加删除修改的功能 这些功能大多又有着相同的套路,就是说完全可以通过改变量名的方式来重复利用这些代码,利用mybatis-plus我们可以很简单实现从数据库中取得数据和编写service的crud(甚至不需要写代码 只需要继承一个类就是) 但是没有提供controller层 那么如何利用 类似的思想来实现controller层的呢?
先说结论 (压缩版:创建一个基类 利用的泛型来调用方法 使用的时候直接把泛型赋值成下一层对应的对象 这样就相当于基类就是模板 使用的时候套个模板就行)我们创建基类的时候设计可以传入的泛型包括 下一层的对象 和实体类 基类中方法都是通过下一层对象的泛型来调用的 当我们继承这个基类的时候给出两个泛型参数 然后程序就会将基类中的泛型参数变成我们传入的对象,那么其实就是下一层的那个对象来调用的这些方法 要实现先必须实现传入泛型的限制 我们可以设置 < M extends Mapper> 所有的公共方法都由Mapper来定义 这样只要是继承 Mapper的对象都会有这些公共方法就不会报错
这里我们写一个demo来模拟一下
首先我们会编写一个类来模拟一个mybatis-plus的Mapper来提供数据这里为了简单并没有对Mapper添加泛型 故所有类调用返回的都是一个结果
public interface Mapper{
default String get(){
return "我是BaseMapper里的get-----------" ;
}
}
首先我们先建立一个包来模拟我们封装的代码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AShrAkum-1642484343638)(C:\Users\sun\AppData\Local\Temp\1642429976039.png)]
实体类的抽象基类 所有的实体类都需要基础该类
public abstract class BaseEntity {
protected int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
当我们的BaseDao继承了Mapper接口自然就有了Mapper中默认实现的方法
package kj.dao;
import kj.bean.BaseEntity;
import mybatisplus.BaseMapper;
public interface BaseDao extends Mapper{
}
现在如果有一个我们业务的dao继承BaseDao那么自然就拥有了 Mapper中的默认方法
这是一个工具类目的就是获取当前类的父级的泛型类型,就是说调用这个工具中的方法就可以获得我们传入的 T 的类型
package kj.uitl;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 通用实效
* 用来获取实现父类
* @author sun
* @date 2022/01/15
*/
public class GenericUtil {
//获取第一个泛型类型
public static <T> Class<T> getSuperGenericClass(Class<?> clz) {
Class<T> result = null;
//得到当前对象的父类"泛型类型"(也叫参数化类型)
//superclass == GenericDao<Dept>成为参数化类型
//superclass == BaseDao不是参数化类型
Type superclass = clz.getGenericSuperclass();
//判断类型是否为参数化类型
if (superclass instanceof ParameterizedType) {
//把父类类型转换成参数化类型(泛型类型)
//只有ParameterizedType才能通过getActualTypeArguments得到参数
ParameterizedType parameterizedType = (ParameterizedType) superclass;
//得到参数化类型类型的参数
//types == GenericDao<Dept>的"<Dept>"参数
Type[] types = parameterizedType.getActualTypeArguments();
//返回子类传递的类型
result = (Class<T>) types[0];
}
return result;
}
//获取第二个泛型类型
public static <T> Class<T> getSuperGenericClass2(Class<?> clz) {
Class<T> result = null;
//得到当前对象的父类"泛型类型"(也叫参数化类型)
//superclass == GenericDao<Dept>成为参数化类型
//superclass == BaseDao不是参数化类型
Type superclass = clz.getGenericSuperclass();
//判断类型是否为参数化类型
if (superclass instanceof ParameterizedType) {
//把父类类型转换成参数化类型(泛型类型)
//只有ParameterizedType才能通过getActualTypeArguments得到参数
ParameterizedType parameterizedType = (ParameterizedType) superclass;
//得到参数化类型类型的参数
//types == GenericDao<Dept>的"<Dept>"参数
Type[] types = parameterizedType.getActualTypeArguments();
//返回子类传递的类型
result = (Class<T>) types[1];
}
return result;
}
}
随口再提一下给自己长点印象 现在有两个对象有这样的关系
B extends A<T>
现在 在A中的 this指向是哪个类呢? 其实我记得不清记推断应该是B 但作为一个为新时代青年咋能迷迷糊糊呢 所以就写了一个测试 指向的 new的实例 (基础知识还是要多复习多补啊)
public class Text {
public static void main(String[] args) {
B b = new B(); //A B@1b6d3586
}
}
class A {
public A(){
System.out.println("A "+this);
}
}
class B extends A{
}
这个我们Service层的封装 BaseServiceImpl是所有service的基类
通过继承该类在传入相应的泛型就可以得到其方法的实现
package kj.service;
import kj.bean.BaseEntity;
import kj.uitl.GenericUtil;
import kj.dao.Dao;
public class BaseServiceImpl<T extends BaseEntity, M extends BaseDao> {
M dao;
Class<Object> t;
public BaseServiceImpl() {
//获取到泛型 M 的类型 创建出对象 这里因为用原生写的 如果使用spring就可以通过IOC注入
Class<M> class2 = GenericUtil.getSuperGenericClass2(this.getClass());
try {
this.dao= class2.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
this.t = GenericUtil.getSuperGenericClass(this.getClass());
}
//在这里打印出t的类型
public String get(){
System.out.println("我是BaseServiceImpl里的 get----------"+t.toString());
return dao.get();
}
}
假如现在一个serviceImpl继承了BaseServiceImpl 那么就会出现
serviceImpl extends BaseServiceImpl<实体类 Dog, 实际业务dao的实现类 DaoImpl>
我们new并调用serviceImpl.get() 会发生什么呢首先会去实例化 那么实例化的父类自然也就初始化了 别忘了 我们上面的测试哦 这个时候BaseServiceImpl的this指向的serviceImpl的 所以我们就获取到了 实体类 dao的实现类 并且通过 BaseServiceImpl 的构造函数赋值给 他的M dao;
Class t; 这个时候 不管是 dao 还是t 都是通过我们传入的类型了 t 做一些返回值什么的 调用的方法也就换成了我们传入的daoImpl来调用了 当然我们这里的dao没有做太多处理 但是道理是相同的 我们可以通过给dao层添加一些泛型来达到同样的效果 对controller 也是一样
下面就是我们的验证环节
bean
public class Dog extends BaseEntity {
}
public class User extends BaseEntity {
}
dao
public interface DogBaseDao extends BaseDao {
}
public interface UserBaseDao extends BaseDao {
}
daoImpl
public class DogBaseDaoImpl implements DogBaseDao {
}
public class UserBaseDaoImpl implements UserBaseDao {
}
service
public class DogServiceImpl extends BaseServiceImpl<Dog, DogBaseDaoImpl> {
}
public class UserServiceImpl extends BaseServiceImpl<User, UserBaseDaoImpl> {
}
测试类
public class Main {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
String s = userService.get();
System.out.println(s);
DogServiceImpl dogService = new DogServiceImpl();
String s1 = dogService.get();
System.out.println(s1);
}
}
打印结果:
我是BaseServiceImpl里的 get----------class test.bean.User
我是BaseMapper里的get-----------
我是BaseServiceImpl里的 get----------class test.bean.Dog
我是BaseMapper里的get-----------
我们如果对controller层进行同样的操作 写一个基类把大家都有的方法提取出来在去继承并且传入自己的ServiceImpl就可以不用写controller层crud (路由区别可以在子类加一个@RequestMapping)
public abstract class BaseController<S extends ICrudService<T>, T extends BaseEntity> {
@Autowired
protected S service;
protected Logger LOG = LoggerFactory.getLogger(this.getClass());
protected Class<T> entityClass;
public BaseController(){
this.entityClass = GenericUtil.getSuperGenericClass2(this.getClass());
}
/**
* 加载
*
* @param id
* @return
* @throws Exception
*/
@ApiOperation(value="加载", notes="根据ID加载")
@GetMapping("/edit/{id}")
public T edit(@PathVariable Long id) throws Exception {
T entity = service.getById(id);
afterEdit(entity);
return entity;
}
/**
* 分页查询
* @param entity
* @param page
* @param rows
* @return
*/
@ApiOperation(value="分页查询", notes="分页查询")
@PostMapping("/list-page")
public IPage<T> listPage(T entity,
@RequestParam(name = "page", defaultValue = "1", required = false) long page,
@RequestParam(name = "rows", defaultValue = "10", required = false) long rows) {
Page<T> p = new Page<>(page, rows);
IPage<T> result = service.listPage(p, entity);
return result;
}
/**
* 查询所有
* @return
*/
@ApiOperation(value="查询", notes="查询所有")
@RequestMapping(value = "/list", method = {RequestMethod.POST, RequestMethod.GET})
public List<T> listAll() {
List<T> list = service.list();
return list;
}
/**
* 增加,修改
*/
@ApiOperation(value="保存", notes="ID存在修改,不存在添加")
@PostMapping("/save")
public ResponseBean save(T entity) throws Exception {
ResponseBean rm = new ResponseBean();
try {
beforeSave(entity); //保存前处理实体类
service.saveOrUpdate(entity);
rm.setModel(entity);
} catch (Exception e) {
e.printStackTrace();
rm.setSuccess(false);
rm.setMsg("保存失败");
}
return rm;
}
/**
* 删除
*/
@ApiOperation(value="删除", notes="根据ID删除")
@GetMapping("/delete/{id}")
public ResponseBean delete(@PathVariable Long id) throws Exception {
ResponseBean rm = new ResponseBean();
try {
service.removeById(id);
rm.setModel(null);
} catch (Exception e) {
e.printStackTrace();
rm.setSuccess(false);
rm.setMsg("保存失败");
}
return rm;
}
/**
* 批量删除
*/
@ApiOperation(value="删除", notes="批量删除")
@RequestMapping(value = "/delete", method = {RequestMethod.POST, RequestMethod.GET})
public ResponseBean delete(@RequestParam List<Long> ids) {
ResponseBean rm = new ResponseBean();
try {
service.removeByIds(ids);
} catch (Exception e) {
e.printStackTrace();
rm.setMsg("删除失败");
rm.setSuccess(false);
}
return rm;
}
/**
* 保存前执行
* @param entity
* @throws Exception
*/
public void beforeSave(T entity) throws Exception {
}
/**
* 模板方法:在加载后执行
* @param entity
*/
public void afterEdit(T entity) {
}
}
rintStackTrace();
rm.setMsg(“删除失败”);
rm.setSuccess(false);
}
return rm;
}
/**
* 保存前执行
* @param entity
* @throws Exception
*/
public void beforeSave(T entity) throws Exception {
}
/**
* 模板方法:在加载后执行
* @param entity
*/
public void afterEdit(T entity) {
}
}