JAVA spring mvc 项目单元测试基类及单元测试自动生成工具类

JAVA spring mvc 项目单元测试基类及单元测试自动生成工具类

测试类基类

package com.fzy.gdaba.test.web.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONArray;
import org.json.JSONException;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/**
 * ClassName: SupperTest <br>
 * Description: 测试类-基类
 *
 * @author sunlight
 * @date 2020/2/26 17:55
 * @since JDK 1.8
 */
@RunWith(SpringJUnit4ClassRunner.class)
// 使用spring-boot的方式启动单元测试,并且使用默认的端口 模拟http请求
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
// 测试类方法执行顺序  按代码顺序执行
@FixMethodOrder(MethodSorters.JVM)
// 启用 @ConfigurationProperties注解
@EnableConfigurationProperties({ServerProperties.class})
public abstract class SupperTest {

    @Autowired
    private ServerProperties serverProperties;
    HttpHeaders header = new HttpHeaders();
    // 日志类
    private final static Logger LOG = LoggerFactory.getLogger(SupperTest.class);
    private static String info = "";

    private final TestRestTemplate TEST_REST_TEMPLATE = new TestRestTemplate();
    protected final HttpMethod POST = HttpMethod.POST;
    protected final HttpMethod GET = HttpMethod.GET;
    protected final HttpMethod PUT = HttpMethod.PUT;
    protected final HttpMethod DELETE = HttpMethod.DELETE;

    // 用户登陆名
    protected String loginCode = "sunlight-yb";
    // 用户登陆密码
    protected String password = "123";
    protected String token;

    /** 执行 */
    protected static final boolean RUN = true;
    /** 忽略 */
    protected static final boolean IGNORE = false;

    /**
     * Description: 返回请求路径[clazz不为空会把Controller请求路径进行拼接]
     *
     * @param clazz Controller
     * @return 返回请求路径[clazz不为空会把Controller请求路径进行拼接]
     * @author sunlight
     */
    protected String getLocalhostUrl(Class<?> clazz) {
        int port = serverProperties.getPort() == null ? 8080 : serverProperties.getPort();
        if (clazz != null && clazz.isAnnotationPresent(RequestMapping.class)) {
            return "http://localhost:" + port + clazz.getAnnotation(RequestMapping.class).value()[0];
        }
        return "http://localhost:".concat(String.valueOf(port));
    }

    /**
     * Description: 使用反射返回响应信息
     *
     * @param remark 备注
     * @param clazz 反射类
     * @param method 请求方式[GET, POST, PUT, DELETE;]
     * @param apiUrl api地址
     * @param request 入参
     * @param urlVariables url参数
     * @param <T> 入参类型泛型
     * @param <R> 出参类型泛型
     * @return R 出参类型
     * @author sunlight
     */
    protected <T, R> R execute(String remark, Class<R> clazz, HttpMethod method, String apiUrl, T request
            , Object... urlVariables) {
        String token = getToken();
        long start = System.currentTimeMillis();
        LOG.info("\n\n//======================== {}:开始测试[请求地址<{}>] ========================//\n\n", remark, apiUrl);
        LOG.info("请求参数:{}\n", Objects.requireNonNull(prettyFormat(toJSONString(request))));
        LOG.info("地址参数:{}\n", Objects.requireNonNull(prettyFormat(toJSONString(urlVariables))));
        info += "//======================== " + remark + ":开始测试[请求地址<"+ apiUrl +">] ========================//\n";
        info += "请求参数:" + Objects.requireNonNull(prettyFormat(toJSONString(request))) + "\n";
        info += "地址参数:" + Objects.requireNonNull(prettyFormat(toJSONString(urlVariables))) + "\n";
        // 创建Http请求头部
        HttpHeaders header = new HttpHeaders();
        // 设置token
        header.add(HttpHeaders.AUTHORIZATION, token);
        // 设置内容类型
        header.setContentType(MediaType.APPLICATION_JSON);
        // 创建Http请求实体
        HttpEntity<T> httpEntity = new HttpEntity<>(request, header);
        // 执行Http请求
        ResponseEntity<R> result = TEST_REST_TEMPLATE.exchange(apiUrl, method, httpEntity, clazz, urlVariables);
        if (result.getStatusCodeValue() != 200) {
            LOG.info(prettyFormat(toJSONString(result.getBody())));
            info += "返回值:" + Objects.requireNonNull(prettyFormat(toJSONString(result.getBody()))) + "\n";
            printInfo();
        }
        LOG.info(prettyFormat(toJSONString(result.getBody())));
        info += "返回值:" + Objects.requireNonNull(prettyFormat(toJSONString(result.getBody()))) + "\n";
        long end = System.currentTimeMillis();
        LOG.info("\n\n//======================== {}:完成测试-耗时{}ms ========================//\n\n", remark, end - start);
        info += "//======================== " + remark + ":完成测试-耗时" + (end - start) + "ms ========================//\n";
        // 返回body信息
        return result.getBody();
    }

    /**
     * Description: 获取Token
     *
     * @return java.lang.String
     * @author sunlight
     */
    private String getToken() {
        if (null != this.token) {
            return token;
        }
        return getToken(this.loginCode, this.password);
    }

    /**
     * Description: 获取token
     *
     * @return java.lang.String
     * @author sunlight
     */
    private String getToken(String mobile, String password) {
        info += "获取Token信息:mobile - " + mobile + " | password - " + password + "\n";
        String appType = "pc";
        String language = "zh-cn";
        // TOKEN请求地址
        String apiUrl = "";
        // 创建Http请求头部
        HttpHeaders header = new HttpHeaders();
        // 设置token
        header.add(HttpHeaders.AUTHORIZATION, null);
        // 设置内容类型
        header.setContentType(MediaType.APPLICATION_JSON);
        // 创建Http请求实体
        HttpEntity<String> httpEntity = new HttpEntity<>(null, header);
        // 执行Http请求
        ResponseEntity<String> result = TEST_REST_TEMPLATE.exchange(apiUrl, HttpMethod.GET, httpEntity, String.class
                , mobile, password, appType, language);
        // 返回body信息
        String data = result.getBody();
        String token = null;
        try {
            token = new JSONArray(data).getJSONObject(0).getString("token");
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return token;
    }

    public static String toJSONString(Object obj) {
        if (StringUtils.isEmpty(obj)) {
            return null;
        } else {
            ObjectMapper mapper = new ObjectMapper();
            try {
                // 对象转字符串
                return mapper.writeValueAsString(obj);
            } catch (JsonProcessingException var2) {
                throw new IllegalArgumentException(" clazz to json fail.");
            }
        }
    }

    /**
     * Description: 格式化输出
     *
     * @param json 需格式化的目标
     * @return 格式化后的目标
     * @author sunlight
     */
    private String prettyFormat(String json) {
        if (StringUtils.isEmpty(json)) {
            return "null";
        }
        ObjectMapper mapper = new ObjectMapper();
        String ret;
        try {
            Object obj = mapper.readValue(json, Object.class);
            // writerWithDefaultPrettyPrinter -- 格式化、writeValueAsString -- 对象转字符串
            ret = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
        } catch (IOException e) {
            return null;
        }
        return System.getProperty("line.separator").concat(ret);
    }

    /**
     * Description: 获取完整请求地址
     *
     * @param url controller 中方法的请求地址
     * @return 完整请求地址 eg:getLocalhostUrl(Controller.class).concat(url);
     * @author sunlight
     */
    protected abstract String getApiUrl(String url);



    /**
     * Description: 执行测试方法
     *
     * @return void
     * @author sunlight
     */
    @Test
    public abstract void execute();

    /**
     * Description: 数组转list
     *
     * @param sources 源数据
     * @return java.util.List<T> list数据
     * @author sunlight
     */
    protected <T> List<T> arraysAsList(T[] sources) {
        return sources == null || sources.length < 1 ? null : Arrays.asList(sources);
    }

    /**
     * Description: 打印本轮测试信息
     *
     * @return void
     * @author sunlight
     */
    protected void printInfo() {
        throw new IllegalArgumentException("//======================== 本次测试完成,测试结果如下: ========================//\n" + info);
    }
}

测试基类用例

package com.fzy.gdaba.test.web.restapi;

import com.fzy.gdaba.test.web.utils.SupperTest;
import javax.servlet.http.HttpServletRequest;
import java.lang.String;
import java.lang.Long;
import com.fzy.gdaba.web.restapi.BlogContentRestApi;

 /**
* ClassName: Test_BlogContentRestApi <br>
* Description: 文章详情相关接口
*
* @author sunlight
* @date 2021/03/25 11:22
* @since JDK 1.8.0_181
*/
public class Test_BlogContentRestApi extends SupperTest {

	@Override
	protected String getApiUrl(String url) {
		return getLocalhostUrl(BlogContentRestApi.class) + url;
	}

	@Override
	public void execute() {
		// false不执行,true执行
		// 通过Uid获取博客内容
		getBlogByUid(false);
		// 通过Uid获取博客内容
		getBlogByZhuangtiUid(false);
		// 通过Uid获取博客点赞数
		getBlogPraiseCountByUid(false);
		// 根据BlogUid获取相关的博客
		getSameBlogByBlogUid(false);
		// 根据标签Uid获取相关的博客
		getSameBlogByTagUid(false);
		// 通过Uid给博客点赞
		praiseBlogByUid(false);

		// 打印测试信息
		printInfo();
	}

	// 通过Uid获取博客内容
	private void getBlogByUid(boolean execute) {
		if (!execute) {return;}
		String uid = null;
		execute("通过Uid获取博客内容", String.class, GET, getApiUrl("/getBlogByUid"), null);
	}

	// 通过Uid获取博客内容
	private void getBlogByZhuangtiUid(boolean execute) {
		if (!execute) {return;}
		String uid = null;
		execute("通过Uid获取博客内容", String.class, GET, getApiUrl("/getBlogByZhuangtiUid"), null);
	}

	// 通过Uid获取博客点赞数
	private void getBlogPraiseCountByUid(boolean execute) {
		if (!execute) {return;}
		String uid = null;
		execute("通过Uid获取博客点赞数", String.class, GET, getApiUrl("/getBlogPraiseCountByUid"), null);
	}

	// 根据BlogUid获取相关的博客
	private void getSameBlogByBlogUid(boolean execute) {
		if (!execute) {return;}
		HttpServletRequest request = null;
		String blogUid = null;
		execute("根据BlogUid获取相关的博客", String.class, GET, getApiUrl("/getSameBlogByBlogUid"), null);
	}

	// 根据标签Uid获取相关的博客
	private void getSameBlogByTagUid(boolean execute) {
		if (!execute) {return;}
		String tagUid = null;
		Long currentPage = null;
		Long pageSize = null;
		execute("根据标签Uid获取相关的博客", String.class, GET, getApiUrl("/getSameBlogByTagUid"), null);
	}

	// 通过Uid给博客点赞
	private void praiseBlogByUid(boolean execute) {
		if (!execute) {return;}
		String uid = null;
		execute("通过Uid给博客点赞", String.class, GET, getApiUrl("/praiseBlogByUid"), null);
	}

}

单元测试类生成工具类

该工具类需与测试基类一起使用,需要项目引用了Spring MVC以及swagger,生成测试类效果请看:测试基类用例

package com.fzy.gdaba.test.web.utils;

import com.fzy.gdaba.web.restapi.BlogContentRestApi;
import com.fzy.gdaba.web.restapi.ClassifyRestApi;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.assertj.core.util.Sets;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * ClassName: GenerationTestCode <br>
 * Description: 生成测试代码
 *
 * @author sunlight
 * @date 2020/12/3 15:13
 * @since JDK 1.8
 */
public class GenerationTestCode {

    public static void main(String[] args) {
        new GenerationTestCode(
            // 代码存放的包路径
            "com.fzy.gdaba.test.web.restapi"
            // 包前缀 com.x3
            , "com.fzy"
            // 要扫描的controller路径
            , "com.fzy.gdaba.web.restapi"
            // 全局变量 -- 可忽略
            , new String[]{"billsId"}
            // 可指定生成的类 -- 可忽略,此属性设置后 scanPack 失效
            , BlogContentRestApi.class, ClassifyRestApi.class
        ).generate();
    }

    /**
     * Description: 生成代码
     *
     * @return void
     * @author sunlight
     */
    public void generate() {
        // 如果指定了需要生成测试类的 controller 则只对指定的controller生成
        Set<Class<?>> classes = controllers != null && controllers.length > 0 ?
            Sets.newHashSet(Arrays.asList(controllers)) : ClassUtil.getClasses(scanPack);
        // 遍历Controller
        Predicate<Class<?>> effectiveController = controller -> null != controller
            && (controller.isAnnotationPresent(RestController.class) || controller.isAnnotationPresent(Controller.class));
        classes.stream().filter(effectiveController)
            .forEach(controller -> {
                // 类
                StringBuilder classStr = new StringBuilder();
                // 引用类
                Set<String> imports = new HashSet<>();
                // 添加Controller引用
                imports.add(controller.getName());
                // 固定方法
                StringBuilder fixedMehthodStr = new StringBuilder();
                // 方法
                StringBuilder mehthodStr = new StringBuilder();
                // 方法调用代码
                StringBuilder executeStr = new StringBuilder();
                // 控制器描述
                String apiTags = null;
                if (controller.isAnnotationPresent(Api.class)) {
                    if (isNotBlankAndNull(controller.getAnnotation(Api.class).value())) {
                        apiTags = controller.getAnnotation(Api.class).value();
                    } else {
                        apiTags = controller.getAnnotation(Api.class).tags()[0];
                    }
                }
                final String testClassName = "Test_" + controller.getSimpleName();
                // 遍历方法 -- 控制需要生成的方法
                Predicate<Method> isMethod = item -> item.isAnnotationPresent(RequestMapping.class)
                    || item.isAnnotationPresent(PostMapping.class)
                    || item.isAnnotationPresent(GetMapping.class)
                    || item.isAnnotationPresent(PutMapping.class)
                    || item.isAnnotationPresent(DeleteMapping.class);
                Arrays.stream(controller.getDeclaredMethods())
                    .filter(isMethod)
                    .sorted(Comparator.comparing(Method::getName))
                    .forEach(method -> {
                        String url = "";
                        String requestMethod = "";
                        String apiOperation = null;
                        if (method.isAnnotationPresent(ApiOperation.class)) {
                            if (isNotBlankAndNull(method.getAnnotation(ApiOperation.class).value())) {
                                apiOperation = method.getAnnotation(ApiOperation.class).value();
                            } else {
                                apiOperation = method.getAnnotation(ApiOperation.class).notes();
                            }
                        }

                        // 添加出参引用
                        if (method.getReturnType().getName().contains(".")) {
                            if (List.class.equals(method.getReturnType())) {
                                // List添加泛型引用
                                imports.add(ClassUtil.getListActualTypeArgumentsTypeName(method));
                            } else {
                                imports.add(method.getReturnType().getName());
                            }
                        }

                        String responseClass = "void".equals(method.getReturnType().getSimpleName()) ?
                            // 无返回值类型
                            "String.class" :
                            List.class.equals(method.getReturnType()) ?
                                // List 类型
                                ClassUtil.getListActualTypeArgumentsSimpleTypeName(method) + "[].class" :
                                // 对象类型
                                method.getReturnType().getSimpleName() + ".class";
                        StringBuilder requestBodyClass = new StringBuilder("null");
                        // 方法调用代码
                        executeStr.append(Character.LINE_BREAK + Character.TAB + Character.TAB)
                            .append(Character.SINGLE_LINE_COMMENTS)
                            .append(apiOperation).append(Character
                            .LINE_BREAK + Character.TAB + Character.TAB)
                            .append(method.getName()).append("(false);");

                        // 方法
                        mehthodStr.append(Character.LINE_BREAK + Character.TAB).append(Character.SINGLE_LINE_COMMENTS).append(apiOperation)
                            .append(Character.LINE_BREAK + Character.TAB)
                            .append("private void ").append(method.getName()).append("(boolean execute) {");

                        // 判断是否执行
                        mehthodStr.append(Character.LINE_BREAK + Character.TAB + Character.TAB).append("if (!execute) {return;}");
                        // 处理请求参数
                        if (method.getParameters() != null && method.getParameters().length > 0) {
                            for (Parameter parameter : method.getParameters()) {
                                // 添加入参引用
                                imports.add(parameter.getType().getName());

                                // 处理 RequestBody 注解参数 注意:只处理第一个
                                if (parameter.isAnnotationPresent(RequestBody.class) && "null".equals(requestBodyClass.toString())) {
                                    requestBodyClass = new StringBuilder(parameter.getName());
                                }

                                // 集合
                                if (List.class.equals(parameter.getType())) {
                                    // 添加List引用
                                    imports.add(List.class.getName());

                                    // 添加ArrayList引用
                                    imports.add(ArrayList.class.getName());

                                    String typeName = "T";
                                    if (parameter.getParameterizedType() instanceof ParameterizedType) {
                                        ParameterizedType type = (ParameterizedType) parameter.getParameterizedType();
                                        typeName = type.getActualTypeArguments()[0].getTypeName().lastIndexOf(".") == -1 ?
                                            type.getActualTypeArguments()[0].getTypeName() :
                                            type.getActualTypeArguments()[0].getTypeName().substring(type.getActualTypeArguments()[0].getTypeName().lastIndexOf(".") + 1);
                                        // 添加List泛型引用
                                        imports.add(type.getActualTypeArguments()[0].getTypeName());
                                    }
                                    parameter.getParameterizedType();
                                    mehthodStr.append(Character.LINE_BREAK + Character.TAB + Character.TAB)
                                        .append("List<").append(typeName).append("> ")
                                        .append(parameter.getName())
                                        .append(" = new ArrayList<>();");
                                    continue;
                                }

                                // 本项目的入参实体特殊处理
                                if (parameter.getType().getName().startsWith(projectPackPrefix)) {
                                    String parameterName = parameter.getName();
                                    mehthodStr.append(Character.LINE_BREAK + Character.TAB + Character.TAB)
                                        .append(parameter.getType().getSimpleName()).append(" ")
                                        .append(parameter.getName())
                                        .append(" = new ").append(parameter.getType().getSimpleName()).append("();");
                                    mehthodStr.append(ClassUtil.getAllSet(parameter.getType(), parameterName, Character.LINE_BREAK + Character.TAB + Character.TAB, this.globalFinalName));
                                    continue;
                                }

                                // 普通对象类型
                                mehthodStr.append(Character.LINE_BREAK + Character.TAB + Character.TAB)
                                    .append(parameter.getType().getSimpleName()).append(" ")
                                    .append(parameter.getName())
                                    .append(" = null;");
                            }

                            // 处理地址入参[PathVariable]
                            for (Parameter parameter : Objects.requireNonNull(method.getParameters())) {
                                if (parameter.isAnnotationPresent(PathVariable.class)) {
                                    requestBodyClass.append(", ").append(parameter.getName());
                                }
                            }
                        }

                        // 判断请求类型
                        if (method.isAnnotationPresent(PostMapping.class)) {
                            url = method.getAnnotation(PostMapping.class).value()[0];
                            requestMethod = "POST";
                        }
                        if (method.isAnnotationPresent(GetMapping.class)) {
                            url = method.getAnnotation(GetMapping.class).value()[0];
                            requestMethod = "GET";
                        }
                        if (method.isAnnotationPresent(PutMapping.class)) {
                            url = method.getAnnotation(PutMapping.class).value()[0];
                            requestMethod = "PUT";
                        }
                        if (method.isAnnotationPresent(DeleteMapping.class)) {
                            url = method.getAnnotation(DeleteMapping.class).value()[0];
                            requestMethod = "DELETE";
                        }
                        if (method.isAnnotationPresent(RequestMapping.class)) {
                            url = method.getAnnotation(RequestMapping.class).value()[0];
                            requestMethod = "POST";
                        }

                        mehthodStr.append(Character.LINE_BREAK + Character.TAB + Character.TAB).append("execute(\"").append(apiOperation).append("\", ")
                            .append(responseClass).append(", ")
                            .append(requestMethod).append(", ")
                            .append("getApiUrl(\"").append(url).append("\"), ")
                            .append(requestBodyClass)
                            .append(");");
                        mehthodStr.append(Character.LINE_BREAK + Character.TAB).append("}\n");
                    });

                // 注释
                classStr.append("package ").append(packageUrl).append(";")
                    .append(Character.LINE_BREAK).append(Character.LINE_BREAK).append("import ").append(SupperTest.class.getName()).append(";")
                    .append(getImports(imports))
                    .append(Character.LINE_BREAK).append(Character.LINE_BREAK).append(" /**")
                    .append(Character.LINE_BREAK).append("* ClassName: ").append(testClassName).append(" <br>")
                    .append(Character.LINE_BREAK).append("* Description: ").append(apiTags)
                    .append(Character.LINE_BREAK).append("*")
                    // 作者
                    .append(Character.LINE_BREAK).append("* @author ").append(System.getenv("USERNAME"))
                    // 日期
                    .append(Character.LINE_BREAK).append("* @date ").append(new SimpleDateFormat("yyyy/MM/dd HH:mm").format(new Date()))
                    // Java 版本
                    .append(Character.LINE_BREAK).append("* @since ").append("JDK ").append(System.getProperty("java.version"))
                    .append(Character.LINE_BREAK).append("*/")
                    .append(Character.LINE_BREAK).append("public class ").append(testClassName).append(" extends ").append(SupperTest.class.getSimpleName()).append(" {");

                StringBuilder globalFinal = new StringBuilder();
                // 常量
                if (this.globalFinalName != null && this.globalFinalName.length > 0) {
                    Arrays.stream(this.globalFinalName).filter(Objects::nonNull).distinct()
                        .forEach(item -> globalFinal.append(Character.LINE_BREAK + Character.LINE_BREAK + Character.TAB)
                            .append("private final String ").append(item).append(" = null;"));
                }

                // 固定方法
                fixedMehthodStr.append(globalFinal)
                    .append(Character.LINE_BREAK + Character.LINE_BREAK + Character.TAB).append("@Override")
                    .append(Character.LINE_BREAK + Character.TAB).append("protected String getApiUrl(String url) {")
                    .append(Character.LINE_BREAK + Character.TAB + Character.TAB).append("return getLocalhostUrl(").append(controller.getSimpleName()).append(".class) + url;")
                    .append(Character.LINE_BREAK + Character.TAB).append("}")
                    .append(Character.LINE_BREAK + Character.LINE_BREAK + Character.TAB).append("@Override")
                    .append(Character.LINE_BREAK + Character.TAB).append("public void execute() {")
                    .append(executeStr)
                    .append(Character.LINE_BREAK + Character.LINE_BREAK + Character.TAB + Character.TAB).append("// 打印测试信息")
                    .append(Character.LINE_BREAK + Character.TAB + Character.TAB).append("printInfo();")
                    .append(Character.LINE_BREAK + Character.TAB).append("}")
                    .append(Character.LINE_BREAK);

                // 拼接全部内容
                classStr.append(fixedMehthodStr.toString()).append(mehthodStr.toString()).append(Character.LINE_BREAK).append("}");
                write(address, testClassName + ".java", classStr.toString(), true);
                System.out.println("测试类生成完毕,存放路径:" + address + "/" + testClassName + ".java");
            });

    }

    private StringBuilder getImports(Set<String> imports) {
        StringBuilder builder = new StringBuilder();
        if (imports != null && imports.size() > 0) {
            imports.stream().sorted(Comparator.reverseOrder()).forEach(item -> builder.append(Character.LINE_BREAK).append("import ").append(item).append(";"));
        }
        return builder;
    }

    /**
     * 代码存储包
     */
    private final String codeStoragePack;

    /**
     * 项目包前缀
     */
    private final String projectPackPrefix;

    /**
     * 扫描包
     */
    private final String scanPack;

    /**
     * 文件分隔符
     */
    private final String fileSeparator = System.getProperty("file.separator");

    /**
     * 当前类地址
     */
    private String address = this.getClass().getResource(fileSeparator).getPath();

    /**
     * 代码存放路径
     */
    private String packageUrl;

    /**
     * 需要生成测试代码的controller
     */
    private Class<?>[] controllers;

    /**
     * 全局常量
     */
    private String[] globalFinalName = {};

    public GenerationTestCode(String codeStoragePack, String projectPackPrefix, String scanPack, Class<?>... controllers) {
        this.codeStoragePack = codeStoragePack;
        this.projectPackPrefix = projectPackPrefix;
        this.scanPack = scanPack;
        this.controllers = controllers;
        init();
    }

    public GenerationTestCode(String codeStoragePack, String projectPackPrefix, String scanPack, String[] globalFinalName, Class<?>... controllers) {
        this.codeStoragePack = codeStoragePack;
        this.projectPackPrefix = projectPackPrefix;
        this.scanPack = scanPack;
        this.controllers = controllers;
        this.globalFinalName = globalFinalName == null ? new String[]{} : globalFinalName;
        init();
    }

    public GenerationTestCode(String projectPackPrefix, String scanPack, String[] globalFinalName, Class<?>... controllers) {
        this.codeStoragePack = null;
        this.projectPackPrefix = projectPackPrefix;
        this.scanPack = scanPack;
        this.controllers = controllers;
        this.globalFinalName = globalFinalName == null ? new String[]{} : globalFinalName;
        init();
    }

    public GenerationTestCode(String projectPackPrefix, String scanPack, Class<?>... controllers) {
        this.codeStoragePack = null;
        this.projectPackPrefix = projectPackPrefix;
        this.scanPack = scanPack;
        this.controllers = controllers;
        init();
    }

    /**
     * Description: 初始
     *
     * @return void
     * @author sunlight
     */
    private void init() {
        if (this.controllers == null || this.controllers.length < 1) {
            checkNullException(scanPack, "扫描包路径为空且未指定controller");
        }
        checkNullException(address, "当前类地址获取失败");
        // 测试类target目录
        String testPath = "/target/test-classes/";
        if (address.lastIndexOf(testPath) == -1) {
            abnormalOutput("当前类只能放在测试文件夹");
        }
        // 计算生成代码实际包路径
        packageUrl = isNotBlankAndNull(codeStoragePack) ? codeStoragePack
            : this.getClass().getName().substring(0, this.getClass().getName().lastIndexOf(".")) + ".generate";
        // 计算当前类地址物理地址
        address = (address.substring(0, address.lastIndexOf(testPath)) + "/src/test/java/" + packageUrl)
            .replace(".", "/");
    }

    /**
     * Description: 异常输出
     *
     * @param errMsg 异常信息
     * @return void
     * @author sunlight
     */
    private void abnormalOutput(final String errMsg) {
        throw new IllegalArgumentException(this.getClass().getSimpleName() + " - error - " + errMsg);
    }

    /**
     * Description: 判断String为空
     *
     * @param str 源字符串
     * @return 为空返回true
     * @author sunlight
     */
    private boolean isBlankOrNull(final String str) {
        return str == null || "".equals(str.trim());
    }

    /**
     * Description: 检查Null异常
     *
     * @param str 要检查的字符
     * @param msg 错误信息
     * @return void
     * @author sunlight
     */
    private void checkNullException(final String str, final String msg) {
        if (isBlankOrNull(str)) {
            abnormalOutput(msg);
        }
    }

    /**
     * ClassName: character <br>
     * Description: 常用字符
     *
     * @author sunlight
     * @date 2020/12/11 14:43
     * @since JDK 1.8
     */
    private static class Character {
        /**
         * 标准空格[8个\r]
         */
        private static final String TAB = "\t";

        /**
         * 换行
         */
        private static final String LINE_BREAK = "\n";

        /**
         * 单行注释
         */
        private static final String SINGLE_LINE_COMMENTS = "// ";
    }

    /**
     * ClassName: ClassUtil <br>
     * Description: Class工具类
     *
     * @author sunlight
     * @date 2020/9/30 14:38
     * @since JDK 1.8
     */
    private static class ClassUtil {

        /**
         * Description: 获取class
         *
         * @param pack 扫描包路径 eg: com.sunlight.controller
         * @return 扫描到的class
         * @author sunlight
         */
        private static Set<Class<?>> getClasses(String pack) {
            // 第一个class类的集合
            Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
            // 是否循环迭代
            boolean recursive = true;
            // 获取包的名字 并进行替换
            String packageName = pack;
            // 包名换成实体路径
            String packageDirName = packageName.replace('.', '/');
            // 定义一个枚举的集合 并进行循环来处理这个目录下的东西
            Enumeration<URL> dirs;
            try {
                dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
                // 循环迭代下去
                while (dirs.hasMoreElements()) {
                    // 获取下一个元素
                    URL url = dirs.nextElement();
                    // 得到协议的名称
                    String protocol = url.getProtocol();
                    // 如果是以文件的形式保存在服务器上
                    if ("file".equals(protocol)) {
                        // 获取包的物理路径
                        String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                        // 以文件的方式扫描整个包下的文件 并添加到集合中
                        findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                    } else if ("jar".equals(protocol)) {
                        // 如果是jar包文件
                        JarFile jar;
                        try {
                            // 获取jar
                            jar = ((JarURLConnection) url.openConnection()).getJarFile();
                            // 从此jar包 得到一个枚举类
                            Enumeration<JarEntry> entries = jar.entries();
                            // 同样的进行循环迭代
                            while (entries.hasMoreElements()) {
                                // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                                JarEntry entry = entries.nextElement();
                                String name = entry.getName();
                                // 如果是以/开头的
                                if (name.charAt(0) == '/') {
                                    // 获取后面的字符串
                                    name = name.substring(1);
                                }
                                // 如果前半部分和定义的包名相同
                                if (name.startsWith(packageDirName)) {
                                    int idx = name.lastIndexOf('/');
                                    // 如果以"/"结尾 是一个包
                                    if (idx != -1) {
                                        // 获取包名 把"/"替换成"."
                                        packageName = name.substring(0, idx).replace('/', '.');
                                    }
                                    // 如果可以迭代下去 并且是一个包
                                    // 如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                }
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return classes;
        }

        /**
         * Description: 在文件包中查找并添加类
         *
         * @param packageName 包名
         * @param packagePath 物理路径
         * @param recursive   是否迭代
         * @param classes     扫描到的class集合
         * @return void
         * @author sunlight
         */
        private static void findAndAddClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
            // 获取此包的目录 建立一个File
            File dir = new File(packagePath);
            // 如果不存在或者 也不是目录就直接返回
            if (!dir.exists() || !dir.isDirectory()) {
                return;
            }
            // 如果存在 就获取包下的所有文件 包括目录
            // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
            File[] dirfiles = dir.listFiles(file -> (recursive && file.isDirectory()) || (file.getName().endsWith(".class")));
            if (dirfiles == null || dirfiles.length < 1) {
                return;
            }
            // 循环所有文件
            for (File file : dirfiles) {
                // 如果是目录 则继续扫描
                if (file.isDirectory()) {
                    findAndAddClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
                } else {
                    // 如果是java类文件 去掉后面的.class 只留下类名
                    String className = file.getName().substring(0, file.getName().length() - 6);
                    try {
                        // 添加到集合中去
                        // 这里用forName有一些不好,会触发static方法,没有使用classLoader的load干净
                        classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
                    } catch (ClassNotFoundException e) {
                        // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
                        e.printStackTrace();
                    }
                }
            }
        }

        /**
         * Description: 获取对象所有set方法
         *
         * @param type            class
         * @param parameterName   参数名
         * @param indentation     缩进
         * @param globalFinalName 全局常量
         * @return java.lang.String 所有set方法
         * @author sunlight
         */
        private static String getAllSet(Class<?> type, String parameterName, String indentation, String... globalFinalName) {
            StringBuilder builder = new StringBuilder();
            Arrays.stream(type.getMethods()).sorted(Comparator.comparing(Method::getName)).forEach(method -> {
                String defaults = null;
                // 筛选 set方法
                if (method.getName().startsWith("set")) {
                    // 判断是否有入参,默认取第一入参
                    if (method.getParameters() != null && method.getParameters().length > 0) {
                        // 参数类型名称
                        final String parameterTypename = method.getParameterTypes()[0].getSimpleName();
                        // 判断类型
                        if ("String".equals(parameterTypename)) {
                            defaults = globalFinalName != null && Arrays.stream(globalFinalName).anyMatch(item -> method.getParameters()[0].getName().equals(item))
                                ? "this.".concat(method.getParameters()[0].getName())
                                : "\"" + method.getParameters()[0].getName() + "\"";
                        }
                        if ("byte".equals(parameterTypename) || "short".equals(parameterTypename)
                            || "int".equals(parameterTypename) || "long".equals(parameterTypename)
                            || "double".equals(parameterTypename) || "float".equals(parameterTypename)) {
                            defaults = "0";
                        }
                        if ("boolean".equals(parameterTypename) || "Boolean".equals(parameterTypename)) {
                            defaults = "false";
                        }
                    }
                    builder.append(indentation).append(parameterName).append(".").append(method.getName()).append("(").append(defaults).append(");");
                }
            });
            return builder.toString();
        }

        /**
         * Description: 获取方法出参List实际类型参数类型名称
         *
         * @param method 方法
         * @return 方法出参List实际类型参数类型名称
         * @author sunlight
         */
        private static String getListActualTypeArgumentsTypeName(Method method) {
            if (List.class.equals(method.getReturnType())) {
                return ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0].getTypeName();
            }
            throw new IllegalArgumentException("方法出参不属于List类型");
        }

        /**
         * Description: 获取方法出参List实际类型参数类型名称简称
         *
         * @param method 方法
         * @return 方法出参List实际类型参数类型名称简称
         * @author sunlight
         */
        private static String getListActualTypeArgumentsSimpleTypeName(Method method) {
            if (null == method) {
                throw new IllegalArgumentException("method 为空");
            }
            String typeName = getListActualTypeArgumentsTypeName(method);
            return typeName.lastIndexOf(".") == -1 ? typeName : typeName.substring(typeName.lastIndexOf(".") + 1);
        }
    }

    /**
     * Description: 文件写入
     *
     * @param writePath    写入路径
     * @param writeName    写入文件名称
     * @param writeContent 写入内容
     * @param createFile   创建文件
     * @return 写入成功返回true
     * @author sunlight
     */
    public boolean write(String writePath, String writeName, String writeContent, boolean... createFile) {
        checkNullException(writePath, "文件写入失败,写入路径为空");
        checkNullException(writeName, "文件写入失败,写入文件名称为空");
        checkNullException(writeContent, "文件写入失败,写入内容为空");
        File file = new File(writePath + File.separator + writeName);
        // 创建文件夹
        if (createFile != null && createFile.length > 0 && createFile[0]) {
            File folder = new File(writePath);
            if (!folder.exists() && !folder.mkdirs()) {
                throw new IllegalArgumentException("文件夹:" + folder + "创建失败");
            }
        }
        if (createFile == null || createFile.length == 0 || !createFile[0]) {
            // 判断文件
            if (!file.exists()) {
                throw new IllegalArgumentException("文件写入失败,文件不存在[" + writePath + File.separator + writeName + "]");
            }
        }
        try {
            OutputStream outputStream = new FileOutputStream(file);
            outputStream.write(writeContent.getBytes());
            outputStream.flush();
            outputStream.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("读取文件失败," + e.getMessage());
        }
    }

    /**
     * Description: 判断String不为空
     *
     * @param str 源字符串
     * @return 不为空返回true
     * @author sunlight
     */
    private static boolean isNotBlankAndNull(final String str) {
        return null != str && !"".equals(str.trim());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 简介 1.1. 概览 1.2. 使用场景 2. Spring 2.0 的新特性 2.1. 简介 2.2. 控制反转(IoC)容器 2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 中间层 2.4.1. 在XML里更为简单的声明性事务配置 2.4.2. JPA 2.4.3. 异步的JMS 2.4.4. JDBC 2.5. Web层 2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. 动态语言支持 2.6.2. JMX 2.6 .3. 任务规划 2.6.4. 对Java 5(Tiger)的支持 2.7. 移植到Spring 2.0 2.7.1. 一些变化 2.7.1.1. Jar包 2.7.1.2. XML配置 2.7.1.3. Deprecated的类和方法 2.7.1.4. Apache OJB 2.7.1.5. iBatis 2.8. 更新的样例应用 2.9. 改进的文档 I. 核心技术 3. 控制反转容器 3.1. 简介 3.2. 容器和bean的本原理 3.2.1. 容器 3.2.1.1. 配置元数据 3.2.2. 实例化容器 3.2.2.1. 组成于XML配置元数据 3.2.3. 多种bean 3.2.3.1. 命名bean 3.2.3.2. 实例化bean 3.2.4. 使用容器 3.3. 依赖 3.3.1. 注入依赖 3.3.1.1. Setter注入 3.3.1.2. 构造器注入 3.3.1.3. 一些例子 3.3.2. 构造器参数的解析 3.3.2.1. 构造器参数类型匹配 3.3.2.2. 构造器参数的索引 3.3.3. bean属性及构造器参数详解 3.3.3.1. 直接量(本类型、Strings类型等。) 3.3.3.2. 引用其它的bean(协作者) 3.3.3.3. 内部bean 3.3.3.4. 集合 3.3.3.5. Nulls 3.3.3.6. XML-based configuration metadata shortcuts 3.3.3.7. 组合属性名称 3.3.4. 使用depends-on 3.3.5. 延迟初始化bean 3.3.6. 自动装配(autowire)协作者 3.3.6.1. 设置Bean使自动装配失效 3.3.7. 依赖检查 3.3.8. 方法注入 3.3.8.1. Lookup方法注入 3.3.8.2. 自定义方法的替代方案 3.4. bean的作用域 3.4.1. Singleton作用域 3.4.2. Prototype作用域 3.4.3. 其他作用域 3.4.3.1. 初始化web配置 3.4.3.2. Request作用域 3.4.3.3. Session作用域 3.4.3.4. global session作用域 3.4.3.5. 作用域bean与依赖 3.4.4. 自定义作用域 3.5. 定制bean特性 3.5.1. Lifecycle接口 3.5.1.1. 初始化回调 3.5.1.2. 析构回调 3.5.2. 了解自己 3.5.2.1. BeanFactoryAware 3.5.2.2. BeanNameAware 3.6. bean定义的继承 3.7. 容器扩展点 3.7.1. 用BeanPostProcessor定制bean 3.7.1.1. 使用BeanPostProcessor的Hello World示例 3.7.1.2. RequiredAnnotationBeanPostProcessor示例 3.7.2. 用BeanFactoryPostProcessor定制配置元数据 3.7.2.1. PropertyPlaceholderConfigurer示例 3.7.2.2. PropertyOverrideConfigurer示例 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的访问 3.8.4. ApplicationContext在WEB应用中的实例化 3.9. 粘合代码和可怕的singleton 3.9.1. 使用Singleton-helper类 4. 资源 4.1. 简介 4.2. Resource 接口 4.3. 内置 Resource 实现 4.3.1. UrlResource 4.3.2. ClassPathResource 4.3.3. FileSystemResource 4.3.4. ServletContextResource 4.3.5. InputStreamResource 4.3.6. ByteArrayResource 4.4. ResourceLoader 4.5. ResourceLoaderAware 接口 4.6. 把Resource作为属性来配置 4.7. Application context 和Resource 路径 4.7.1. 构造application context 4.7.1.1. 创建 ClassPathXmlApplicationContext 实例 - 简介 4.7.2. Application context构造器中资源路径的通配符 4.7.2.1. Ant风格的pattern 4.7.2.2. classpath*: 前缀 4.7.2.3. 其他关于通配符的说明 4.7.3. FileSystemResource 提示 5. 校验,数据绑定,BeanWrapper,与属性编辑器 5.1. 简介 5.2. 使用Spring的Validator接口进行校验 5.3. 从错误代码到错误信息 5.4. Bean处理和BeanWrapper 5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 5.4.2.1. 注册用户自定义的PropertyEditor 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. Spring的AOP代理 6.2. @AspectJ支持 6.2.1. 启用@AspectJ支持 6.2.2. 声明一个切面 6.2.3. 声明一个切入点(pointcut) 6.2.3.1. 切入点指定者的支持 6.2.3.2. 合并切入点表达式 6.2.3.3. 共享常见的切入点(pointcut)定义 6.2.3.4. 示例 6.2.4. 声明通知 6.2.4.1. 前置通知(Before advice) 6.2.4.2. 返回后通知(After returning advice) 6.2.4.3. 抛出后通知(After throwing advice) 6.2.4.4. 后通知(After (finally) advice) 6.2.4.5. 环绕通知(Around Advice) 6.2.4.6. 通知参数(Advice parameters) 6.2.4.7. 通知(Advice)顺序 6.2.5. 引入(Introductions) 6.2.6. 切面实例化模型 6.2.7. 例子 6.3. Schema-based AOP support 6.3.1. 声明一个切面 6.3.2. 声明一个切入点 6.3.3. 声明通知 6.3.3.1. 通知(Advice) 6.3.3.2. 返回后通知(After returning advice) 6.3.3.3. 抛出异常后通知(After throwing advice) 6.3.3.4. 后通知(After (finally) advice) 6.3.3.5. 通知 6.3.3.6. 通知参数 6.3.3.7. 通知顺序 6.3.4. 引入 6.3.5. 切面实例化模型 6.3.6. Advisors 6.3.7. 例子 6.4. AOP声明风格的选择 6.4.1. Spring AOP还是完全用AspectJ? 6.4.2. Spring AOP中使用@AspectJ还是XML? 6.5. 混合切面类型 6.6. 代理机制 6.7. 编程方式创建@AspectJ代理 6.8. 在Spring应用中使用AspectJ 6.8.1. 在Spring中使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ Load-time weaving(LTW) 6.9. 其它资源 7. Spring AOP APIs 7.1. 简介 7.2. Spring中的切入点API 7.2.1. 概念 7.2.2. 切入点实施 7.2.3. AspectJ切入点表达式 7.2.4. 便利的切入点实现 7.2.4.1. 静态切入点 7.2.4.2. 动态切入点 7.2.5. 切入点的类 7.2.6. 自定义切入点 7.3. Spring的通知API 7.3.1. 通知的生命周期 7.3.2. Spring里的通知类型 7.3.2.1. 拦截around通知 7.3.2.2. 前置通知 7.3.2.3. 异常通知 7.3.2.4. 后置通知 7.3.2.5. 引入通知 7.4. Spring里的advisor(Advisor) API 7.5. 使用ProxyFactoryBean创建AOP代理 7.5.1. 础 7.5.2. JavaBean属性 7.5.3. 于JDK和CGLIB的代理 7.5.4. 对接口进行代理 7.5.5. 对类进行代理 7.5.6. 使用“全局”advisor 7.6. 简化代理定义 7.7. 使用ProxyFactory通过编程创建AOP代理 7.8. 操作被通知对象 7.9. 使用“自动代理(autoproxy)”功能 7.9.1. 自动代理bean定义 7.9.1.1. BeanNameAutoProxyCreator 7.9.1.2. DefaultAdvisorAutoProxyCreator 7.9.1.3. AbstractAdvisorAutoProxyCreator 7.9.2. 使用元数据驱动的自动代理 7.10. 使用TargetSources 7.10.1. 热交换目标源 7.10.2. 池化目标源 7.10.3. 原型目标源 7.10.4. ThreadLocal目标源 7.11. 定义新的通知类型 7.12. 更多资源 8. 测试 8.1. 简介 8.2. 单元测试 8.3. 集成测试 8.3.1. Context管理和缓存 8.3.2. 测试fixture的依赖注入 8.3.3. 事务管理 8.3.4. 方便的变量 8.3.5. 示例 8.3.6. 运行集成测试 8.4. 更多资源 II. 中间层数据访问 9. 事务管理 9.1. 简介 9.2. 动机 9.3. 关键抽象 9.4. 使用资源同步的事务 9.4.1. 高层次方案 9.4.2. 低层次方案 9.4.3. TransactionAwareDataSourceProxy 9.5. 声明式事务管理 9.5.1. 理解Spring的声明式事务管理实现 9.5.2. 第一个例子 9.5.3. 回滚 9.5.4. 为不同的bean配置不同的事务语义 9.5.5. <tx:advice/> 有关的设置 9.5.6. 使用 @Transactional 9.5.6.1. @Transactional 有关的设置 9.5.7. 插入事务操作 9.5.8. 结合AspectJ使用 @Transactional 9.6. 编程式事务管理 9.6.1. 使用 TransactionTemplate 9.6.2. 使用 PlatformTransactionManager 9.7. 选择编程式事务管理还是声明式事务管理 9.8. 与特定应用服务器集成 9.8.1. BEA WebLogic 9.8.2. IBM WebSphere 9.9. 公共问题的解决方案 9.9.1. 对一个特定的 DataSource 使用错误的事务管理器 9.10. 更多的资源 10. DAO支持 10.1. 简介 10.2. 一致的异常层次 10.3. 一致的DAO支持抽象类 11. 使用JDBC进行数据访问 11.1. 简介 11.1.1. Spring JDBC包结构 11.2. 利用JDBC核心类实现JDBC的本操作和错误处理 11.2.1. JdbcTemplate类 11.2.2. NamedParameterJdbcTemplate类 11.2.3. SimpleJdbcTemplate类 11.2.4. DataSource接口 11.2.5. SQLExceptionTranslator接口 11.2.6. 执行SQL语句 11.2.7. 执行查询 11.2.8. 更新数据库 11.3. 控制数据库连接 11.3.1. DataSourceUtils类 11.3.2. SmartDataSource接口 11.3.3. AbstractDataSource类 11.3.4. SingleConnectionDataSource类 11.3.5. DriverManagerDataSource类 11.3.6. TransactionAwareDataSourceProxy类 11.3.7. DataSourceTransactionManager类 11.4. 用Java对象来表达JDBC操作 11.4.1. SqlQuery类 11.4.2. MappingSqlQuery类 11.4.3. SqlUpdate类 11.4.4. StoredProcedure类 11.4.5. SqlFunction类 12. 使用ORM工具进行数据访问 12.1. 简介 12.2. Hibernate 12.2.1. 资源管理 12.2.2. 在Spring的application context中创建 SessionFactory 12.2.3. HibernateTemplate 12.2.4. 不使用回调的Spring的DAO实现 12.2.5. 于Hibernate3的原生API实现DAO 12.2.6. 编程式的事务划分 12.2.7. 声明式的事务划分 12.2.8. 事务管理策略 12.2.9. 容器资源 vs 本地资源 12.2.10. 在应用服务器中使用Hibernate的注意点 12.3. JDO 12.3.1. 建立PersistenceManagerFactory 12.3.2. JdoTemplate和JdoDaoSupport 12.3.3. 于原生的JDO API实现DAO 12.3.4. 事务管理 12.3.5. JdoDialect 12.4. Oracle TopLink 12.4.1. SessionFactory 抽象层 12.4.2. TopLinkTemplate 和 TopLinkDaoSupport 12.4.3. 于原生的TopLink API的DAO实现 12.4.4. 事务管理 12.5. iBATIS SQL Maps 12.5.1. iBATIS 1.x和2.x的概览与区别 12.5.2. iBATIS SQL Maps 1.x 12.5.2.1. 创建SqlMap 12.5.2.2. 使用 SqlMapTemplate 和 SqlMapDaoSupport 12.5.3. iBATIS SQL Maps 2.x 12.5.3.1. 创建SqlMapClient 12.5.3.2. 使用 SqlMapClientTemplate 和 SqlMapClientDaoSupport 12.5.3.3. 于原生的iBATIS API的DAO实现 12.6. JPA 12.6.1. 在Spring环境中建立JPA 12.6.1.1. LocalEntityManagerFactoryBean 12.6.1.2. LocalContainerEntityManagerFactoryBean 12.6.1.3. 处理多个持久化单元 12.6.2. JpaTemplate 和 JpaDaoSupport 12.6.3. 于原生的JPA实现DAO 12.6.4. 异常转化 12.6.5. 事务管理 12.6.6. JpaDialect III. Web 13. Web框架 13.1. 介绍 13.1.1. 与其他web框架的集成 13.1.2. Spring Web MVC框架的特点 13.2. DispatcherServlet 13.3. 控制器 13.3.1. AbstractController 和 WebContentGenerator 13.3.2. 其它的简单控制器 13.3.3. MultiActionController 13.3.4. 命令控制器 13.4. 处理器映射(handler mapping) 13.4.1. BeanNameUrlHandlerMapping 13.4.2. SimpleUrlHandlerMapping 13.4.3. 拦截器(HandlerInterceptor) 13.5. 视图与视图解析 13.5.1. 视图解析器 13.5.2. 视图解析链 13.5.3. 重定向(Rediret)到另一个视图 13.5.3.1. RedirectView 13.5.3.2. redirect:前缀 13.5.3.3. forward:前缀 13.6. 本地化解析器 13.6.1. AcceptHeaderLocaleResolver 13.6.2. CookieLocaleResolver 13.6.3. SessionLocaleResolver 13.6.4. LocaleChangeInterceptor 13.7. 使用主题 13.7.1. 简介 13.7.2. 如何定义主题 13.7.3. 主题解析器 13.8. Spring对分段文件上传(multipart file upload)的支持 13.8.1. 介绍 13.8.2. 使用MultipartResolver 13.8.3. 在表单中处理分段文件上传 13.9. 使用Spring的表单标签库 13.9.1. 配置标签库 13.9.2. form标签 13.9.3. input标签 13.9.4. checkbox标签 13.9.5. radiobutton标签 13.9.6. password标签 13.9.7. select标签 13.9.8. option标签 13.9.9. options标签 13.9.10. textarea标签 13.9.11. hidden标签 13.9.12. errors标签 13.10. 处理异常 13.11. 惯例优先原则(convention over configuration) 13.11.1. 对控制器的支持: ControllerClassNameHandlerMapping 13.11.2. 对模型的支持:ModelMap (ModelAndView) 13.11.3. 对视图的支持: RequestToViewNameTranslator 13.12. 其它资源 14. 集成视图技术 14.1. 简介 14.2. JSP和JSTL 14.2.1. 视图解析器 14.2.2. 'Plain-old' JSPs versus JSTL 'Plain-old' JSP与JSTL 14.2.3. 帮助简化开发的额外的标签 14.3. Tiles 14.3.1. 需要的资源 14.3.2. 如何集成Tiles 14.3.2.1. InternalResourceViewResolver 14.3.2.2. ResourceBundleViewResolver 14.4. Velocity和FreeMarker 14.4.1. 需要的资源 14.4.2. Context 配置 14.4.3. 创建模板 14.4.4. 高级配置 14.4.4.1. velocity.properties 14.4.4.2. FreeMarker 14.4.5. 绑定支持和表单处理 14.4.5.1. 用于绑定的宏 14.4.5.2. 简单绑定 14.4.5.3. 表单输入生成宏 14.4.5.4. 重载HTML转码行为并使你的标签符合XHTML 14.5. XSLT 14.5.1. 写在段首 14.5.1.1. Bean 定义 14.5.1.2. 标准MVC控制器代码 14.5.1.3. 把模型数据转化为XML 14.5.1.4. 定义视图属性 14.5.1.5. 文档转换 14.5.2. 小结 14.6. 文档视图(PDF/Excel) 14.6.1. 简介 14.6.2. 配置和安装 14.6.2.1. 文档视图定义 14.6.2.2. Controller 代码 14.6.2.3. Excel视图子类 14.6.2.4. PDF视图子类 14.7. JasperReports 14.7.1. 依赖的资源 14.7.2. 配置 14.7.2.1. 配置ViewResolver 14.7.2.2. 配置View 14.7.2.3. 关于报表文件 14.7.2.4. 使用 JasperReportsMultiFormatView 14.7.3. 构造ModelAndView 14.7.4. 使用子报表 14.7.4.1. 配置子报表文件 14.7.4.2. 配置子报表数据源 14.7.5. 配置Exporter的参数 15. 集成其它Web框架 15.1. 简介 15.2. 通用配置 15.3. JavaServer Faces 15.3.1. DelegatingVariableResolver 15.3.2. FacesContextUtils 15.4. Struts 15.4.1. ContextLoaderPlugin 15.4.1.1. DelegatingRequestProcessor 15.4.1.2. DelegatingActionProxy 15.4.2. ActionSupport 类 15.5. Tapestry 15.5.1. 注入 Spring 托管的 beans 15.5.1.1. 将 Spring Beans 注入到 Tapestry 页面中 15.5.1.2. 组件定义文件 15.5.1.3. 添加抽象访问方法 15.5.1.4. 将 Spring Beans 注入到 Tapestry 页面中 - Tapestry 4.0+ 风格 15.6. WebWork 15.7. 更多资源 16. Portlet MVC框架 16.1. 介绍 16.1.1. 控制器 - MVC中的C 16.1.2. 视图 - MVC中的V 16.1.3. Web作用范围的Bean 16.2. DispatcherPortlet 16.3. ViewRendererServlet 16.4. 控制器 16.4.1. AbstractController和PortletContentGenerator 16.4.2. 其它简单的控制器 16.4.3. Command控制器 16.4.4. PortletWrappingController 16.5. 处理器映射 16.5.1. PortletModeHandlerMapping 16.5.2. ParameterHandlerMapping 16.5.3. PortletModeParameterHandlerMapping 16.5.4. 增加 HandlerInterceptor 16.5.5. HandlerInterceptorAdapter 16.5.6. ParameterMappingInterceptor 16.6. 视图和它们的解析 16.7. Multipart文件上传支持 16.7.1. 使用PortletMultipartResolver 16.7.2. 处理表单里的文件上传 16.8. 异常处理 16.9. Portlet应用的部署 IV. 整合 17. 使用Spring进行远程访问与Web服务 17.1. 简介 17.2. 使用RMI暴露服务 17.2.1. 使用 RmiServiceExporter 暴露服务 17.2.2. 在客户端链接服务 17.3. 使用Hessian或者Burlap通过HTTP远程调用服务 17.3.1. 为Hessian配置DispatcherServlet 17.3.2. 使用HessianServiceExporter暴露你的bean 17.3.3. 客户端连接服务 17.3.4. 使用Burlap 17.3.5. 对通过Hessian或Burlap暴露的服务使用HTTP础认证 17.4. 使用HTTP调用器暴露服务 17.4.1. 暴露服务对象 17.4.2. 在客户端连接服务 17.5. Web服务 17.5.1. 使用JAXI-RPC暴露服务 17.5.2. 访问Web服务 17.5.3. 注册bean映射 17.5.4. 注册自己的处理方法 17.5.5. 使用XFire来暴露Web服务 17.6. 对远程接口不提供自动探测 17.7. 在选择这些技术时的一些考虑 18. Enterprise Java Bean(EJB)集成 18.1. 简介 18.2. 访问EJB 18.2.1. 概念 18.2.2. 访问本地的无状态Session Bean(SLSB) 18.2.3. 访问远程SLSB 18.3. 使用Spring提供的辅助类实现EJB组件 19. JMS 19.1. 简介 19.2. 使用Spring JMS 19.2.1. JmsTemplate 19.2.2. 连接工厂 19.2.3. (消息)目的地管理 19.2.4. 消息侦听容器 19.2.4.1. SimpleMessageListenerContainer 19.2.4.2. DefaultMessageListenerContainer 19.2.4.3. ServerSessionMessageListenerContainer 19.2.5. 事务管理 19.3. 发送一条消息 19.3.1. 使用消息转换器 19.3.2. SessionCallback 和ProducerCallback 19.4. 接收消息 19.4.1. 同步接收 19.4.2. 异步接收 - 消息驱动的POJOs 19.4.3. SessionAwareMessageListener 接口 19.4.4. MessageListenerAdapter 19.4.5. 事务中的多方参与 20. JMX 20.1. 介绍 20.2. 输出bean到JMX 20.2.1. 创建一个MBeanServer 20.2.2. 复用现有的MBeanServer 20.2.3. MBean的惰性初始化 20.2.4. MBean的自动注册 20.2.5. 控制注册行为 20.3. 控制bean的管理接口 20.3.1. MBeanInfoAssembler 接口 20.3.2. 使用源码级元数据 20.3.3. 使用JDK 5.0注解 20.3.4. 源代码级的元数据类型 20.3.5. 接口AutodetectCapableMBeanInfoAssembler 20.3.6. 用Java接口定义管理接口 20.3.7. 使用MethodNameBasedMBeanInfoAssembler 20.4. 控制bean的 ObjectName 20.4.1. 从Properties中读取ObjectName 20.4.2. 使用 MetadataNamingStrategy 20.5. JSR-160连接器 20.5.1. 服务器端连接器 20.5.2. 客户端连接器 20.5.3. 于Burlap/Hessian/SOAP的JMX 20.6. 通过代理访问MBeans 20.7. 通知 20.7.1. 为通知注册监听器 20.7.2. 发布通知 20.8. 更多资源 21. JCA CCI 21.1. 介绍 21.2. 配置CCI 21.2.1. 连接器配置 21.2.2. 在Spring中配置ConnectionFactory 21.2.3. 配置CCI连接 21.2.4. 使用一个 CCI 单连接 21.3. 使用Spring的 CCI访问支持 21.3.1. 记录转换 21.3.2. CciTemplate 类 21.3.3. DAO支持 21.3.4. 自动输出记录生成 21.3.5. 总结 21.3.6. 直接使用一个 CCI Connection 接口和Interaction接口 21.3.7. CciTemplate 使用示例 21.4. 建模CCI访问为操作对象 21.4.1. MappingRecordOperation 21.4.2. MappingCommAreaOperation 21.4.3. 自动输出记录生成 21.4.4. 总结 21.4.5. MappingRecordOperation 使用示例 21.4.6. MappingCommAreaOperation 使用示例 21.5. 事务 22. Spring邮件抽象层 22.1. 简介 22.2. Spring邮件抽象结构 22.3. 使用Spring邮件抽象 22.3.1. 可插拔的MailSender实现 22.4. 使用 JavaMail MimeMessageHelper 22.4.1. 创建一条简单的MimeMessage,并且发送出去 22.4.2. 发送附件和嵌入式资源(inline resources) 23. Spring中的定时调度(Scheduling)和线程池(Thread Pooling) 23.1. 简介 23.2. 使用OpenSymphony Quartz 调度器 23.2.1. 使用JobDetailBean 23.2.2. 使用 MethodInvokingJobDetailFactoryBean 23.2.3. 使用triggers和SchedulerFactoryBean来包装任务 23.3. 使用JDK Timer支持类 23.3.1. 创建定制的timers 23.3.2. 使用 MethodInvokingTimerTaskFactoryBean类 23.3.3. 打包:使用TimerFactoryBean来设置任务 23.4. SpringTaskExecutor抽象 23.4.1. TaskExecutor接口 23.4.2. 何时使用TaskExecutor接口 23.4.3. TaskExecutor类型 23.4.4. 使用TaskExecutor接口 24. 动态语言支持 24.1. 介绍 24.2. 第一个例子 24.3. 定义动态语言支持的bean 24.3.1. 公共概念 24.3.1.1. <lang:language/> 元素 24.3.1.2. Refreshable bean 24.3.1.3. 内置动态语言源文件 24.3.1.4. 理解dynamic-language-backed bean context的构造器注入 24.3.2. JRuby beans 24.3.3. Groovy beans 24.3.4. BeanShell beans 24.4. 场景 24.4.1. Spring MVC控制器脚本化 24.4.2. Validator脚本化 24.5. 更多的资源 25. 注解和源代码级的元数据支持 25.1. 简介 25.2. Spring的元数据支持 25.3. 注解 25.3.1. @Required 25.3.2. Spring中的其它@Annotations 25.4. 集成Jakarta Commons Attributes 25.5. 元数据和Spring AOP自动代理 25.5.1. 本原理 25.5.2. 声明式事务管理 25.5.3. 缓冲 25.5.4. 自定义元数据 25.6. 使用属性来减少MVC web层配置 25.7. 元数据属性的其它用法 25.8. 增加对额外元数据API的支持 A. XML Schema-based configuration A.1. Introduction A.2. XML Schema-based configuration A.2.1. Referencing the schemas A.2.2. The util schema A.2.2.1. <util:constant/> A.2.2.2. <util:property-path/> A.2.2.3. <util:properties/> A.2.2.4. <util:list/> A.2.2.5. <util:map/> A.2.2.6. <util:set/> A.2.3. The jee schema A.2.3.1. <jee:jndi-lookup/> (simple) A.2.3.2. <jee:jndi-lookup/> (with single JNDI environment setting) A.2.3.3. <jee:jndi-lookup/> (with multiple JNDI environment settings) A.2.3.4. <jee:jndi-lookup/> (complex) A.2.3.5. <jee:local-slsb/> (simple) A.2.3.6. <jee:local-slsb/> (complex) A.2.3.7. <jee:remote-slsb/> A.2.4. The lang schema A.2.5. The tx (transaction) schema A.2.6. The aop schema A.2.7. The tool schema A.2.8. The beans schema A.3. Setting up your IDE A.3.1. Setting up Eclipse A.3.2. Setting up IntelliJ IDEA A.3.3. Integration issues A.3.3.1. XML parsing errors in the Resin v.3 application server B. Extensible XML authoring B.1. Introduction B.2. Authoring the schema B.3. Coding a NamespaceHandler B.4. Coding a BeanDefinitionParser B.5. Registering the handler and the schema B.5.1. META-INF/spring.handlers B.5.2. META-INF/spring.schemas C. spring-beans-2.0.dtd D. spring.tld D.1. Introduction D.2. The bind tag D.3. The escapeBody tag D.4. The hasBindErrors tag D.5. The htmlEscape tag D.6. The message tag D.7. The nestedPath tag D.8. The theme tag D.9. The transform tag E. spring-form.tld E.1. Introduction E.2. The checkbox tag E.3. The errors tag E.4. The form tag E.5. The hidden tag E.6. The input tag E.7. The label tag E.8. The option tag E.9. The options tag E.10. The password tag E.11. The radiobutton tag E.12. The select tag E.13. The textarea tag F. Spring 2.0 开发手册中文化项目 F.1. 声明 F.2. 致谢 F.3. 参与人员及任务分配 F.4. Spring 2.0 正式版开发手册翻译说明 F.5. 项目历程 F.5.1. Spring 2.0 RC2 开发手册翻译项目 F.5.2. Spring 2.0 正式版开发手册翻译项目
目录 前言 1. 简介 1.1. 概览 1.2. 使用场景 2. Spring 2.0 的新特性 2.1. 简介 2.2. 控制反转(IoC)容器 2.2.1. 更简单的XML配置 2.2.2. 新的bean作用域 2.2.3. 可扩展的XML编写 2.3. 面向切面编程(AOP) 2.3.1. 更加简单的AOP XML配置 2.3.2. 对@AspectJ 切面的支持 2.4. 中间层 2.4.1. 在XML里更为简单的声明性事务配置 2.4.2. JPA 2.4.3. 异步的JMS 2.4.4. JDBC 2.5. Web层 2.5.1. Spring MVC的表单标签库 2.5.2. Spring MVC合理的默认值 2.5.3. Portlet 框架 2.6. 其他特性 2.6.1. 动态语言支持 2.6.2. JMX 2.6.3. 任务规划 2.6.4. 对Java 5(Tiger)的支持 2.7. 移植到Spring 2.0 2.7.1. 一些变化 2.8. 更新的样例应用 2.9. 改进的文档 I. 核心技术 3. 控制反转容器 3.1. 简介 3.2. 容器和bean的本原理 3.2.1. 容器 3.2.2. 实例化容器 3.2.3. 多种bean 3.2.4. 使用容器 3.3. 依赖 3.3.1. 注入依赖 3.3.2. 构造器参数的解析 3.3.3. bean属性及构造器参数详解 3.3.4. 使用depends-on 3.3.5. 延迟初始化bean 3.3.6. 自动装配(autowire)协作者 3.3.7. 依赖检查 3.3.8. 方法注入 3.4. bean的作用域 3.4.1. Singleton作用域 3.4.2. Prototype作用域 3.4.3. 其他作用域 3.4.4. 自定义作用域 3.5. 定制bean特性 3.5.1. Lifecycle接口 3.5.2. 了解自己 3.6. bean定义的继承 3.7. 容器扩展点 3.7.1. 用BeanPostProcessor定制bean 3.7.2. 用BeanFactoryPostProcessor定制配置元数据 3.7.3. 使用FactoryBean定制实例化逻辑 3.8. ApplicationContext 3.8.1. 利用MessageSource实现国际化 3.8.2. 事件 3.8.3. 底层资源的访问 3.8.4. ApplicationContext在WEB应用中的实例化 3.9. 粘合代码和可怕的singleton 3.9.1. 使用Singleton-helper类 4. 资源 4.1. 简介 4.2. Resource 接口 4.3. 内置 Resource 实现 4.3.1. UrlResource 4.3.2. ClassPathResource 4.3.3. FileSystemResource 4.3.4. ServletContextResource 4.3.5. InputStreamResource 4.3.6. ByteArrayResource 4.4. ResourceLoader 4.5. ResourceLoaderAware 接口 4.6. 把Resource作为属性来配置 4.7. Application context 和Resource 路径 4.7.1. 构造application context 4.7.2. Application context构造器中资源路径的通配符 4.7.3. FileSystemResource 提示 5. 校验,数据绑定,BeanWrapper,与属性编辑器 5.1. 简介 5.2. 使用Spring的Validator接口进行校验 5.3. 从错误代码到错误信息 5.4. Bean处理和BeanWrapper 5.4.1. 设置和获取属性值以及嵌套属性 5.4.2. 内建的PropertyEditor实现 6. 使用Spring进行面向切面编程(AOP) 6.1. 简介 6.1.1. AOP概念 6.1.2. Spring AOP的功能和目标 6.1.3. Spring的AOP代理 6.2. @AspectJ支持 6.2.1. 启用@AspectJ支持 6.2.2. 声明一个切面 6.2.3. 声明一个切入点(pointcut) 6.2.4. 声明通知 6.2.5. 引入(Introductions) 6.2.6. 切面实例化模型 6

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值