background

When using DB frameworks such as Mybaties or Hibernate, it is often necessary to create a service based on each entity table. However, in actual business development, there are many intermediate tables that only perform some basic add, delete, change and query operations, that is, more modules need a service That’s fine, but each of these DB frameworks uses the Service layer to specify the entity type, so we consider using the ClassPool class to dynamically create the actual service to be called

Service dynamic generation

package com.dotoyo.archivedb.smartDb.buildBean; import com.baomidou.mybatisplus.service.IService; import com.baomidou.mybatisplus.service.impl.ServiceImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.ConstPool; import javassist.bytecode.SignatureAttribute.ClassSignature; import javassist.bytecode.SignatureAttribute.ClassType; import javassist.bytecode.SignatureAttribute.TypeArgument; import javassist.bytecode.annotation.Annotation; import lombok.extern.slf4j.Slf4j; import java.security.ProtectionDomain; @slf4j public class ServiceGenerator {public static String getPackagePatch(Class<? > cls) {return cls.getPackage().getName().replaceAll("package ".""); } public static Class<? > buildClass(Class<? > entityCls, String packagePath, String serviceImplName, String mapperName) throws Exception { ClassPool pool = ClassPool.getDefault(); String incls = packagePath +"."+ serviceImplName; CtClass cc = pool.makeClass(incls); / / parent CtClass superServiceImpl = pool. GetCtClass (ServiceImpl. Class. GetName ()); cc.setSuperclass(superServiceImpl); CtClass implInterface = pool.get(iservice.class.getName ()); cc.setInterfaces(new CtClass[]{implInterface}); / / fill generic buildServiceGerType (cc, ServiceImpl. Class. GetTypeName (), entityCls. GetTypeName (), mapperName, IService.class.getTypeName()); addClassAnnotation(cc); ClassLoader cl = entityCls.getClassLoader(); ProtectionDomain pd = entityCls.getProtectionDomain(); cc.toClass(cl, pd); cc.detach(); Class<? > serviceBeanCls = Class.forName(packagePath +"." + serviceImplName);

        if (log.isInfoEnabled()) {
            log.info("Javassit successfully generated [{}.class] and loaded it into JVM memory; {}", serviceImplName, cl);

            String path = "D://AssisService"; Cc.writefile (path); cc.writefile (path); log.info("{}. Class writes file path={}", serviceImplName, path);
        }
        returnserviceBeanCls; } /** * Fill in the generic parameter ** @param ctClass * @param superServiceImplName * @param entityName * @param mapperName * @param interfaceClass */ public static void buildServiceGerType(CtClass ctClass, String superServiceImplName, String entityName, String mapperName, String interfaceClass) { TypeArgument entityTypeArg = new TypeArgument(new ClassType(entityName)); TypeArgument mapperTypeArg = new TypeArgument(new ClassType(mapperName)); ClassType interfaceClassType = new ClassType(interfaceClass, new TypeArgument[]{entityTypeArg}); ClassType superClassType = new ClassType(superServiceImplName, new TypeArgument[]{mapperTypeArg, entityTypeArg}); // The implementation class's generics are composed of its own generics, parent generics, and interface generics [], so null ClassSignature signature = new ClassSignature(null, superClassType, new ClassType[]{interfaceClassType}); ctClass.setGenericSignature(signature.encode()); Public static void addClassAnnotation(CtClass cc) {ConstPool ConstPool = cc.getClassFile().getConstPool(); AnnotationsAttribute bodyAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag); Annotation bodyAnnot = new Annotation("org.springframework.stereotype.Service", constPool); bodyAttr.addAnnotation(bodyAnnot); cc.getClassFile().addAttribute(bodyAttr); }}Copy the code

Service Usage Process

We first get the service from the Spring container according to beanName. The first fetch is not available because it has not been created dynamically. When the fetch is empty, we construct one using classPool and inject it into the Spring container. Then you can use it directly from Spring

/** * Get the corresponding mapper file from the entity class (if not from the Spring container) ** @param CLS * @return
     */
    public static <T> IService<T> getService(Class<T> cls) {
        String serviceImplName = firstLower(cls.getSimpleName()) + "_Javassit_ServiceImpl";
        IService<T> service = null;
        try {
            service = (IService<T>) SpringUtils.getBean(serviceImplName);
        } catch (BeansException e) {
            log.info("Did not get {} from the Spring container the first time", serviceImplName);
        }

        if (service == null) {
            BaseMapper<T> mapper = null;
            try {
                mapper = getMapper(cls);
            } catch (BeansException e) {
                log.error({} mapper not found, cls);
                throw e;
            }
            String packagePath = ServiceGenerator.getPackagePatch(cls);
            try {
                Class serviceBeanCls = ServiceGenerator.buildClass(cls, packagePath, firstUpper(serviceImplName), mapper.getClass().getGenericInterfaces()[0].getTypeName());
                registerBeanDefinition(serviceBeanCls, serviceImplName);
                log.info("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = prompt = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
                log.info("Dynamically generate {}<{}> using Javassit and load it into SpringContext", serviceBeanCls.getName(), cls.getSimpleName()); service = (IService<T>) SpringUtils.getBean(serviceImplName); } catch (Exception e) { log.error(e.getMessage(), e); }}if (service == null) {
            log.error({} service cannot be found, cls);
            throw new RuntimeException("Can't find it." + cls + "Corresponding service");
        }
        returnservice; } /** * @param beanCls */ public static void beandefinition (Class<? > beanCls, String beanName) { DefaultListableBeanFactory dbf = (DefaultListableBeanFactory) ((ConfigurableApplicationContext) SpringUtils.getApplicationContext()).getBeanFactory(); BeanDefinitionBuilder beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(beanCls); / / the instance registered in the spring container bs is equivalent to the id configuration. DBF registerBeanDefinition (beanName, beanDefinition getBeanDefinition ()); }Copy the code

Spring context usage

To get beans dynamically from the Spring context and register them dynamically, you define the springUtil utility class to load context objects at startup time

package com.dotoyo.archivedb.smartDb.springContext; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @author rjx * @date 2019-12-08 */ @Component public class SpringUtils implements ApplicationContextAware { private  static ApplicationContext applicationContext; @Override public voidsetApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (SpringUtils.applicationContext == null) {
            SpringUtils.applicationContext = applicationContext;
        }
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -");
        System.out.println("---------------SpringUtil------------------------------------------------------");
        System.out.println("= = = = = = = = ApplicationContext configuration is successful, in the ordinary class by calling the SpringUtils. GetAppContext () to obtain ApplicationContext object, ApplicationContext =" + SpringUtils.applicationContext + "= = = = = = = =");
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -"); } // Get applicationContext public static applicationContextgetApplicationContext() {
        returnapplicationContext; } public static Object getBean(String name) {returngetApplicationContext().getBean(name); Public static <T> T getBean(class <T> clazz) {returngetApplicationContext().getBean(clazz); Public static <T> T getBean(String name, Class<T> Clazz) {public static <T> getBean(String name, Class<T> Clazz) {returngetApplicationContext().getBean(name, clazz); }}Copy the code

Mybatisplus Dynamic Service encapsulation

After service dynamic acquisition, we began to encapsulate MyBatisPlus, the idea is to define the basic service, and view myBatisPlus basic service common methods, we commonly used to define the encapsulation, add entity generic parameters, in the foundation To implement serviceImpl, we call the dynamic fetch Service and then call myBatisPlus’s own service

/** * Dynamically obtain service ** @param entityCls * @return
     */
    private static <T> IService<T> getService(Class<T> entityCls) {
        return BuildBeanAssist.getService(entityCls);
    }

    @Override
    public <T> boolean insert(T entity, Class<T> entityCls) {
        IService service=getService(entityCls);
        return service.insert(entity);
    }
Copy the code

Full project address: SmartService