Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java - Avoid create empty sub Class & Interface or generate Java source code template

I am developing a java web project using Spring and Mybatis.
In the dao level, I defined a super class and a super interface which implemented all common methods.
Thus when create sub class or interface for a specific model in dao level, I only need to implement the super dao class & interface, and left the class body and interface body empty. Over half of the sub dao level class & interface is empty through all the time.


(Example of the empty dao class & interface:)

RoleDao.java

package core.dao;

import core.dao.base.BaseDao;
import core.model.Role;

public interface RoleDao extends BaseDao<Role> {
}

RoleDaoImpl.java

package core.dao.impl;

import org.springframework.stereotype.Repository;

import core.dao.RoleDao;
import core.dao.base.BaseDaoImpl;
import core.model.Role;

@Repository
public class RoleDaoImpl extends BaseDaoImpl<Role> implements RoleDao {
}

My question is:

Is there a good way to avoid writing these empty class & interface, while still could use them?

I am thinking of using Code generator to generate these class file, or use Java reflection to create such class & interface at runtime as need, but didn't get into detail yet.


@Update

It seems not flexible to achieve the target without creating source code, so I decided to write some simple java source code generator for java web project.

And a tool called codemodel is very suitable to do that, it is developed by Sun, and now owned by Oracle I guess.

And, I gave an answer by myself with code that I wrote to generate java source code.

like image 635
user218867 Avatar asked Jan 29 '15 08:01

user218867


3 Answers

The Repository classes for the classes in our projects that use QueryDSL and JPA only have an interface, but not an implementation. However, it does not answer the question whether it is possible to directly generate these repositories based on the entity classes, although it would be similar to what the Apt Maven Plugin does to create the QEntity classes for use with QueryDSL.

@NoRepositoryBean
public interface BaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID>, QueryDslPredicateExecutor<T> {
}

@Repository
public interface DummyDataRepository extends BaseRepository<DummyData, Long> {
}
like image 176
EpicPandaForce Avatar answered Oct 19 '22 03:10

EpicPandaForce


About a month ago I was asking myself the same thing :) So, seems that we have a kind of solution, since you are using Spring library. As I read on docs:

Rather than code data access objects (DAOs) manually using SqlSessionDaoSupport or SqlSessionTemplate, Mybatis-Spring provides a proxy factory: MapperFactoryBean. This class lets you inject data mapper interfaces directly into your service beans. When using mappers you simply call them as you have always called your DAOs, but you won't need to code any DAO implementation because MyBatis-Spring will create a proxy for you.

There's an example on GitHub and also on this MyBatis' page.

I hope that it gives you some insights, because maybe it isn't feasible refactoring your whole system to be benefited of such nice feature.

like image 42
Klerisson Avatar answered Oct 19 '22 05:10

Klerisson


I just wrote a simple code generator for my project.

It's just a single class, and could generate model/dao/service/action level code template for 1 or more models in a single execution.

Dependence:

It use codemodel and apache commons-io lib, and it's a spring + springMVC project.

How to use it:

It import some base class/interface in my project, from which the generated class extends/implements from, so you might can't run it directly. But you can create them as empty class/interface, or remove them from the genSourceXxx() function.

CodeGenerator.java:

package my.project.util;

import my.project.dao.base.BaseDao;
import my.project.dao.base.BaseDaoImpl;
import my.project.model.base.BaseIdModel;
import my.project.service.base.BaseService;
import my.project.service.base.BaseServiceImpl;
import my.project.web.action.base.BaseAction;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;

import com.sun.codemodel.ClassType;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;

/**
 * code generator
 * 
 * @author eric
 * @date Apr 10, 2015 3:32:57 PM
 */
public class CodeGenerator {
    // location of source folder
    public static final String tmpSourceFolderBaseLocation = "/tmp/java_code/"; // tmp location for generated code,
    public static final String actualSourceFolderBaseLocation = "/mnt/star/workplace/eclipse_j2ee_workplace/project-name/source/java/"; // actual source folder,

    // package
    public static final String packageSeparator = ".";
    public static final String basePackage = "my.project";
    public static final String modelPackage = "model";
    public static final String daoPackage = "dao";
    public static final String daoImplPackage = "dao.impl";
    public static final String servicePackage = "service";
    public static final String serviceImplPackage = "service.impl";
    public static final String actionPackage = "web.action";

    // source file path
    public static final String pkgPathSeparator = File.separator;
    public static final String sourceSuffix = ".java";
    public static final String basePkgPath = "my/project";
    public static final String modelPkgPath = "model";
    public static final String daoPkgPath = "dao";
    public static final String daoImplPkgPath = "dao" + pkgPathSeparator + "impl";
    public static final String servicePkgPath = "service";
    public static final String serviceImplPkgPath = "service" + pkgPathSeparator + "impl";
    public static final String actionPkgPath = "web" + pkgPathSeparator + "action";

    // naming
    public static final String daoSuffix = "Dao";
    public static final String daoImplSuffix = "DaoImpl";
    public static final String serviceSuffix = "Service";
    public static final String serviceImplSuffix = "ServiceImpl";
    public static final String actionSuffix = "Action";

    // compiler for generated source code,
    public static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    // classloader for compiled class,
    public static final ClassLoader cl = genCl(tmpSourceFolderBaseLocation);

    /**
     * compile a source file,
     * 
     * @param sourcePath
     * @throws MalformedURLException
     */
    public static void compileSource(String sourcePath) throws MalformedURLException {
        // set this so that won't get compile error,
        System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + tmpSourceFolderBaseLocation);
        compiler.run(null, null, null, sourcePath);
    }

    /**
     * generate a classloader,
     * 
     * @param path
     * @return
     * @throws MalformedURLException
     */
    public static ClassLoader genCl(String path) {
        ClassLoader cl = null;
        try {
            cl = new URLClassLoader(new URL[] { new File(path).toURI().toURL() });
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        return cl;
    }

    /**
     * <p>
     * Generate source for model.
     * </p>
     * 
     * @param modelName
     * @throws IOException
     * @throws JClassAlreadyExistsException
     */
    public static void genSourceModel(String modelName) throws IOException, JClassAlreadyExistsException {
        String modelFullName = genFullName(modelPackage, modelName);

        JCodeModel cm = new JCodeModel();
        // define type,
        JDefinedClass dc = cm._class(modelFullName, ClassType.CLASS);
        // extends
        dc._extends(BaseIdModel.class);

        // id
        JFieldVar idField = dc.field(JMod.PRIVATE, Integer.class, "id"); // field

        // id - getter method
        JMethod getIdMethod = dc.method(JMod.PUBLIC, Integer.class, "getId");
        getIdMethod.body()._return(idField);
        getIdMethod.annotate(cm.ref(Override.class)); // annotation - override

        // generate source code,
        cm.build(new File(tmpSourceFolderBaseLocation));

        // compile
        compileSource(genFullPath(modelPkgPath, modelName));
    }

    public static void genSourceDao(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
        String daoFullName = genFullName(daoPackage, modelName, daoSuffix);
        String modelFullName = genFullName(modelPackage, modelName);

        JCodeModel cm = new JCodeModel();
        // define type,
        JDefinedClass dc = cm._class(daoFullName, ClassType.INTERFACE);
        // extends
        JClass superClazz = cm.ref(BaseDao.class).narrow(cl.loadClass(modelFullName));
        dc._extends(superClazz);

        // generate source code,
        cm.build(new File(tmpSourceFolderBaseLocation));

        // compile
        compileSource(genFullPath(daoPkgPath, modelName, daoSuffix));
    }

    public static void genSourceDaoImpl(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
        String daoImplFullName = genFullName(daoImplPackage, modelName, daoImplSuffix);
        String daoFullName = genFullName(daoPackage, modelName, daoSuffix);
        String modelFullName = genFullName(modelPackage, modelName);

        JCodeModel cm = new JCodeModel();
        // define type,
        JDefinedClass dc = cm._class(daoImplFullName, ClassType.CLASS);
        dc.annotate(Repository.class);

        // extends
        JClass superClazz = cm.ref(BaseDaoImpl.class).narrow(cl.loadClass(modelFullName));
        dc._extends(superClazz);
        // implements
        dc._implements(cl.loadClass(daoFullName));

        // generate source code,
        cm.build(new File(tmpSourceFolderBaseLocation));

        // compile
        compileSource(genFullPath(daoImplPkgPath, modelName, daoImplSuffix));
    }

    public static void genSourceService(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
        String serviceFullName = genFullName(servicePackage, modelName, serviceSuffix);
        JCodeModel cm = new JCodeModel();
        // define type,
        JDefinedClass dc = cm._class(serviceFullName, ClassType.INTERFACE);

        // extends
        dc._extends(BaseService.class);

        // generate source code,
        cm.build(new File(tmpSourceFolderBaseLocation));

        // compile
        compileSource(genFullPath(servicePkgPath, modelName, serviceSuffix));
    }

    public static void genSourceServiceImpl(String modelName, boolean serviceTransaction) throws JClassAlreadyExistsException, ClassNotFoundException,
            IOException {
        String serviceImplFullName = genFullName(serviceImplPackage, modelName, serviceImplSuffix);
        String serviceFullName = genFullName(servicePackage, modelName, serviceSuffix);

        JCodeModel cm = new JCodeModel();
        // define type,
        JDefinedClass dc = cm._class(serviceImplFullName, ClassType.CLASS);

        // annotation
        dc.annotate(Service.class);
        if (serviceTransaction) {
            dc.annotate(Transactional.class);
        }

        // extends
        dc._extends(BaseServiceImpl.class);
        // implements
        dc._implements(cl.loadClass(serviceFullName));

        // generate source code,
        cm.build(new File(tmpSourceFolderBaseLocation));

        // compile
        compileSource(genFullPath(serviceImplPkgPath, modelName, serviceImplSuffix));
    }

    public static void genSourceAction(String modelName) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
        genSourceAction(modelName, null);
    }

    /**
     * generate action,
     * 
     * @param modelName
     * @param rootMappingPath
     *            root mapping path, if null or empty then don't have this annotation,
     * @throws JClassAlreadyExistsException
     * @throws ClassNotFoundException
     * @throws IOException
     */
    public static void genSourceAction(String modelName, String rootMappingPath) throws JClassAlreadyExistsException, ClassNotFoundException, IOException {
        String actionFullName = genFullName(actionPackage, modelName, actionSuffix);

        JCodeModel cm = new JCodeModel();
        // define type,
        JDefinedClass dc = cm._class(actionFullName, ClassType.CLASS);

        // annotation
        dc.annotate(Controller.class);
        if (StringUtils.isNotBlank(rootMappingPath)) {
            dc.annotate(cm.ref(RequestMapping.class)).param("value", rootMappingPath);
        }

        // extends
        dc._extends(BaseAction.class);

        // generate source code,
        cm.build(new File(tmpSourceFolderBaseLocation));

        // compile
        compileSource(genFullPath(actionPkgPath, modelName, actionSuffix));
    }

    /**
     * <p>
     * generate a serial java source code base on a single model, don't include service level,
     * </p>
     * <p>
     * Warning: this will override existing code, so, be careful!
     * </p>
     * 
     * @param modelName
     */
    public static void genStack(String modelName) {
        genStack(modelName, false, false, null);
    }

    /**
     * <p>
     * generate a serial java source code base on a single model.
     * </p>
     * <p>
     * Warning: this will override existing code, so, be careful!
     * </p>
     * 
     * @param modelName
     * @param includeService
     *            specify whether include service level,
     * @param serviceTransaction
     *            whether add transaction annotation to service impl class,
     * @param actionRootMappingPath
     *            root mapping path, if null or empty then don't have this annotation,
     */
    public static void genStack(String modelName, boolean includeService, boolean serviceTransaction, String actionRootMappingPath) {
        try {
            initTmp(); // clean or create folder,

            // generate code - start
            genSourceModel(modelName);
            genSourceDao(modelName);
            genSourceDaoImpl(modelName);
            if (includeService) {
                genSourceService(modelName);
                genSourceServiceImpl(modelName, serviceTransaction);
            }
            genSourceAction(modelName, actionRootMappingPath);
            // generate code - end

            merge(); // copy,
            initTmp(); // clean, so that won't have duplicated class,
        } catch (IOException | JClassAlreadyExistsException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * <p>
     * batch generate.
     * </p>
     * <p>
     * Warning: this will override existing code, so, be careful!
     * </p>
     * 
     * @param models
     *            map of "modelName : actionRootMappingPath"
     * @param includeService
     *            specify whether include service level,
     * @param serviceTransaction
     *            whether add transaction annotation to service impl class,
     */
    public static void genStackBatch(Map<String, String> models, boolean includeService, boolean serviceTransaction) {
        for (String modelName : models.keySet()) {
            genStack(modelName, includeService, serviceTransaction, models.get(modelName));
        }
    }

    /**
     * generate class fullname,
     * 
     * @param subPackage
     * @param modelName
     * @return
     */
    public static String genFullName(String subPackage, String modelName) {
        return genFullName(subPackage, modelName, "");
    }

    /**
     * generate class fullname,
     * 
     * @param subPackage
     * @param modelName
     * @param suffix
     * @return
     */
    public static String genFullName(String subPackage, String modelName, String suffix) {
        return new StringBuilder().append(basePackage).append(packageSeparator).append(subPackage).append(packageSeparator).append(modelName).append(suffix)
                .toString();
    }

    /**
     * generate source file path,
     * 
     * @param subPkgPath
     * @param modelName
     * @return
     */
    public static String genFullPath(String subPkgPath, String modelName) {
        return genFullPath(subPkgPath, modelName, "");
    }

    /**
     * generate source file path,
     * 
     * @param subPkgPath
     * @param modelName
     * @param suffix
     * @return
     */
    public static String genFullPath(String subPkgPath, String modelName, String suffix) {
        return new StringBuilder().append(tmpSourceFolderBaseLocation).append(basePkgPath).append(pkgPathSeparator).append(subPkgPath).append(pkgPathSeparator)
                .append(modelName).append(suffix).append(sourceSuffix).toString();
    }

    /**
     * clean tmp location,
     * 
     * @throws IOException
     */
    public static void initTmp() throws IOException {
        File tmp = new File(tmpSourceFolderBaseLocation);

        if (!tmp.exists()) { // create if not exists,
            tmp.mkdirs();
        } else { // clean if exists,
            FileUtils.cleanDirectory(tmp);
        }
    }

    /**
     * <p>
     * move generated code into source folder,
     * </p>
     * <p>
     * Warning: this will override existing code, so, be careful!
     * </p>
     */
    public static void merge() {
        File originalFile = new File(tmpSourceFolderBaseLocation + basePkgPath);
        File targetFile = new File(actualSourceFolderBaseLocation + basePkgPath);
        try {
            // filter - java file,
            IOFileFilter javaSuffixFilter = FileFilterUtils.suffixFileFilter(".java");
            IOFileFilter javaFiles = FileFilterUtils.and(FileFileFilter.FILE, javaSuffixFilter);

            // filter - dir or java file,
            FileFilter filter = FileFilterUtils.or(DirectoryFileFilter.DIRECTORY, javaFiles);

            FileUtils.copyDirectory(originalFile, targetFile, filter);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        // String modelName = "LoginHistory";
        // String actionRootMappingPath = "/loginHistory";
        // genStack(modelName, true, false, actionRootMappingPath);

        Map<String, String> models = new HashMap<String, String>();
        models.put("AdminAccount", "/adminAccount");
        models.put("CustomerAccount", "/customerAccount");
        models.put("Role", "/role");

        genStackBatch(models, true, true);
    }
}
like image 28
user218867 Avatar answered Oct 19 '22 03:10

user218867