cve研究

0x1 前言

1标签

java 复制代码
public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap)
java 复制代码
@RequiresPermissions("monitor:job:view")
    @GetMapping()
    public String jobLog(@RequestParam(value = "jobId", required = false) Long jobId, ModelMap mmap)
    {
        if (StringUtils.isNotNull(jobId))
        {
//数据库查询返回条目
            SysJob job = jobService.selectJobById(jobId);
//这里添加前端渲染
            mmap.put("job", job);
        }
        return prefix + "/jobLog";
    }

2 绑定进制

java 复制代码
  //这里自动发送 
<input type="hidden" name="createBy" th:value="${@permission.getPrincipalProperty('loginName')}">

参考

https://cloud.tencent.com/developer/article/2258688

数据绑定是将用户提交的表单数据绑定到Java对象的过程。在Spring Boot中,数据绑定的主要工作是由DataBinder和WebDataBinder两个类来完成。

DataBinder

DataBinder是Spring框架中的一个重要组件,它可以将HTTP请求参数绑定到Java对象的属性上。它主要包括以下几个步骤:

(1)创建DataBinder对象:在Spring Boot应用程序中,我们可以使用@InitBinder注解来初始化DataBinder对象。

复制代码
@Controller
public class MyController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

在上面的示例中,我们使用@InitBinder注解来初始化DataBinder对象。在initBinder()方法中,我们使用SimpleDateFormat类来格式化日期,并将格式化后的日期绑定到Date对象的属性上。

(2)绑定请求参数:在Spring Boot应用程序中,我们可以使用@ModelAttribute注解将请求参数绑定到Java对象的属性上。

代码语言:javascript

AI代码解释

java 复制代码
@RequestMapping(value = "/saveUser", method = RequestMethod.POST)
public String saveUser(@ModelAttribute("user") User user) {
    // ...
}

在上面的示例中,我们使用@ModelAttribute注解将请求参数绑定到User对象的属性上。

WebDataBinder

WebDataBinder是DataBinder的子类,它可以将HTTP请求参数绑定到Java对象的属性上,并提供了更多的数据绑定功能。例如,它可以将字符串类型的请求参数自动转换为Java中的基本数据类型,如Integer、Double等。在Spring Boot应用程序中,我们可以使用@InitBinder注解来初始化WebDataBinder对象。

代码语言:javascript

AI代码解释

java 复制代码
@Controller
public class MyController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), true));
        binder.registerCustomEditor(Integer.class, new CustomNumberEditor(Integer.class, true));
    }

    // ...
}

在上面的示例中,我们使用@InitBinder注解初始化了一个WebDataBinder对象,并为Date和Integer类型的属性分别注册了日期编辑器和数字编辑器。这样,在处理HTTP请求时,WebDataBinder对象就可以将请求参数自动转换为Java中的相应类型,并将它们绑定到Java对象的属性上。

java 复制代码
/**
     * 将前台传递过来的日期格式的字符串,自动转化为Date类型
     */
    @InitBinder
    public void initBinder(WebDataBinder binder)
    {
        // Date 类型转换
        binder.registerCustomEditor(Date.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText(String text)
            {
                setValue(DateUtils.parseDate(text));
            }
        });
    }

https://www.cnblogs.com/shijinglu2018/p/10389325.html

https://blog.csdn.net/chengxuzaza/article/details/60580779

3 数组绑定

  1. 路径决定了目的地

假设你的实体对象里同时有:

```java

private Long[] roleIds; // 角色ID数组

private List<Long> deptIds; // 部门ID列表

private Map<String, Object> params; // 参数Map

```

那么前端传参时必须用不同的"键"来区分:

· roleIds=1&roleIds=2 → 只绑定给 roleIds 属性

· deptIds=5&deptIds=6 → 只绑定给 deptIds 属性

· params[key1]=value1 → 只绑定给 params 这个 Map

路径解析时,第一段就是属性名。

Spring 的 BeanWrapperImpl 会先通过反射拿到 getRoleIds()、getDeptIds()、getParams(),它们返回的对象各自独立,互不干扰。


  1. 对于数组/List,内部如何"放入"?

当参数名是 roleIds(无下标)且目标类型是数组/List,Spring 会:

  1. 收集所有名叫 roleIds 的参数值 → 得到一个 String[](如 ["1","2"])

  2. 根据目标类型(Long[])进行类型转换,得到 Long[]。

  3. 调用 setRoleIds(convertedArray) 整个替换。

如果有下标,比如 users[0].name=张三,Spring 会:

  1. 解析到 users 是 List<User>。

  2. 找索引 0 位置的元素,如果不存在就调用默认构造函数创建一个 User 对象并放入该位置。

  3. 然后再对该对象的 name 属性赋值。

所以,无论有多少个不同的数组,只要参数名的"头部"(属性名)不同,Spring 就会精准地调用对应的 getter,然后对那个集合进行操作。


  1. 类型不同,行为也不同

Spring 会根据 getter 返回的类型,决定后续怎么处理:

· 数组:支持下标 [0] 或逗号分隔字符串、同名多值。

· List(有泛型):可以自动增长,通过下标或 add。

· Set(有泛型):默认通过 add,不能用下标(会报错)。

· Map:用中括号内的作为键,调用 put。

哪怕是同一个对象里有 10 个不同类型的集合,Spring 也会按照属性名一一分辨,完全不会混乱。


简而言之

Spring 绑定到"对应的数组"主要是按名导航:

· 请求参数名中的第一级名称(如 roleIds、deptIds、params)就是属性名。

· Spring 利用反射获取该属性对应的集合实例,然后根据集合类型使用对应的注入策略(整体赋值 / put 键值对 / 索引追加)。

所以,你看到的 params[treeParentCode] 只是众多可能中的一种,Spring 同样能正确处理 roleIds、userList[0].name 等,只要有正确的属性路径命名即可,绝不会把给 roleIds 的值塞到 deptIds 里去。

java 复制代码
/**
     * 修改业务
     * 
     * @param genTable 业务信息
     * @return 结果
     */
    @Override
    @Transactional
    public void updateGenTable(GenTable genTable)
    {
        String options = JSON.toJSONString(genTable.getParams());
        genTable.setOptions(options);
        int row = genTableMapper.updateGenTable(genTable);
        if (row > 0)
        {
            for (GenTableColumn genTableColumn : genTable.getColumns())
            {
                genTableColumnMapper.updateGenTableColumn(genTableColumn);
            }
        }
    }
java 复制代码
 public Map<String, Object> getParams()
    {
        if (params == null)
        {
            params = new HashMap<>();
        }
        return params;
    }

2

java 复制代码
 // 上传文件
	    function sendFile(file, obj) {
	        var data = new FormData();
	        data.append("file", file);
	        $.ajax({
	            type: "POST",
	            url: ctx + "common/upload",
	            data: data,
	            cache: false,
	            contentType: false,
	            processData: false,
	            dataType: 'json',
	            success: function(result) {
	                if (result.code == web_status.SUCCESS) {
	                	$(obj).summernote('editor.insertImage', result.url, result.fileName);
					} else {
						$.modal.alertError(result.msg);
					}
	            },
	            error: function(error) {
	                $.modal.alertWarning("图片上传失败。");
	            }
	        });
	    }
		
		$("#form-notice-add").validate({
			focusCleanup: true
		});
		
		function submitHandler() {
	        if ($.validate.form()) {
	        	var sHTML = $('.summernote').summernote('code');
				$("#noticeContent").val(sHTML);
				$.operate.save(prefix + "/add", $('#form-notice-add').serialize());
	        }
	    }
java 复制代码
 /**
     * 通用上传请求(单个)
     */
    @PostMapping("/upload")
    @ResponseBody
    public AjaxResult uploadFile(MultipartFile file) throws Exception
    {
        try
        {
            // 上传文件路径
            String filePath = RuoYiConfig.getUploadPath();
            // 上传并返回新文件名称
            String fileName = FileUploadUtils.upload(filePath, file);
//
            String url = serverConfig.getUrl() + fileName;
            AjaxResult ajax = AjaxResult.success();
            ajax.put("url", url);
            ajax.put("fileName", fileName);
            ajax.put("newFileName", FileUtils.getName(fileName));
            ajax.put("originalFilename", file.getOriginalFilename());
            return ajax;
        }
        catch (Exception e)
        {
            return AjaxResult.error(e.getMessage());
        }
    }
java 复制代码
 /**
     * 获取上传路径
     */
    public static String getUploadPath()
    {
        return getProfile() + "/upload";
    }
}
java 复制代码
public static String getProfile()
    {//这里可能可控?
        return profile;
    }
java 复制代码
 /**
     * 根据文件路径上传
     *
     * @param baseDir 相对应用的基目录
     * @param file 上传的文件
     * @return 文件名称
     * @throws IOException
     */
    public static final String upload(String baseDir, MultipartFile file) throws IOException
    {
        try
        {
//上传路径,文件信息,数组
            return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
        }
        catch (Exception e)
        {
            throw new IOException(e.getMessage(), e);
        }
    }

    /**
java 复制代码
 public static final String[] DEFAULT_ALLOWED_EXTENSION = {
            // 图片
            "bmp", "gif", "jpg", "jpeg", "png",
            // word excel powerpoint
            "doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
            // 压缩文件
            "rar", "zip", "gz", "bz2",
            // 视频格式
            "mp4", "avi", "rmvb",
            // pdf
            "pdf" };
java 复制代码
/**
     * 文件上传
     *
     * @param baseDir 相对应用的基目录
     * @param file 上传的文件
     * @param allowedExtension 上传文件类型
     * @return 返回上传成功的文件名
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws FileNameLengthLimitExceededException 文件名太长
     * @throws IOException 比如读写文件出错时
     * @throws InvalidExtensionException 文件校验异常
     */
//上传路径,文件信息,数组
    public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
            InvalidExtensionException
    {

//判断文件名长度是不是空的
        int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
//如果文件名长度大于100
        if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
        {
            throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
        }
//判断后缀
        assertAllowed(file, allowedExtension);
//
        String fileName = extractFilename(file);

        String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
        file.transferTo(Paths.get(absPath));
        return getPathFileName(baseDir, fileName);
    }
java 复制代码
/**
     * 编码文件名
     */
    public static final String extractFilename(MultipartFile file)
    {
        return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
                FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
    }
java 复制代码
/**
     * 格式化文本, {} 表示占位符<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例:<br>
     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     * 
     * @param template 文本模板,被替换的部分用 {} 表示
     * @param params 参数值
     * @return 格式化后的文本
     */
//模板构造
    public static String format(String template, Object... params)
    {
//如果有一个是空
        if (isEmpty(params) || isEmpty(template))
        {
//返回模板
            return template;
        }
        return StrFormatter.format(template, params);
    }
java 复制代码
/**
     * 获取文件名的后缀
     * 
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file)
    {
        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
        if (StringUtils.isEmpty(extension))
        {
            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
        }
        return extension;
    }
}
java 复制代码
/**
     * 格式化字符串<br>
     * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
     * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
     * 例:<br>
     * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b<br>
     * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a<br>
     * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b<br>
     * 
     * @param strPattern 字符串模板
     * @param argArray 参数列表
     * @return 结果
     */
    public static String format(final String strPattern, final Object... argArray)
    {
        if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray))
        {
            return strPattern;
        }
        final int strPatternLength = strPattern.length();

        // 初始化定义好的长度以获得更好的性能
        StringBuilder sbuf = new StringBuilder(strPatternLength + 50);

        int handledPosition = 0;
        int delimIndex;// 占位符所在位置
        for (int argIndex = 0; argIndex < argArray.length; argIndex++)
        {
            delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition);
            if (delimIndex == -1)
            {
                if (handledPosition == 0)
                {
                    return strPattern;
                }
                else
                { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果
                    sbuf.append(strPattern, handledPosition, strPatternLength);
                    return sbuf.toString();
                }
            }
            else
            {
                if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH)
                {
                    if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH)
                    {
                        // 转义符之前还有一个转义符,占位符依旧有效
                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
                        sbuf.append(Convert.utf8Str(argArray[argIndex]));
                        handledPosition = delimIndex + 2;
                    }
                    else
                    {
                        // 占位符被转义
                        argIndex--;
                        sbuf.append(strPattern, handledPosition, delimIndex - 1);
                        sbuf.append(C_DELIM_START);
                        handledPosition = delimIndex + 1;
                    }
                }
                else
                {
                    // 正常占位符
                    sbuf.append(strPattern, handledPosition, delimIndex);
                    sbuf.append(Convert.utf8Str(argArray[argIndex]));
                    handledPosition = delimIndex + 2;
                }
            }
        }
        // 加入最后一个占位符后所有的字符
        sbuf.append(strPattern, handledPosition, strPattern.length());

        return sbuf.toString();
    }
}
java 复制代码
public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
java 复制代码
 /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
java 复制代码
    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };

    public static final String[] FLASH_EXTENSION = { "swf", "flv" };

    public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
            "asf", "rm", "rmvb" };

    public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
java 复制代码
**
     * 文件大小校验
     *
     * @param file 上传的文件
     * @return
     * @throws FileSizeLimitExceededException 如果超出最大大小
     * @throws InvalidExtensionException
     */
    public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
            throws FileSizeLimitExceededException, InvalidExtensionException
    {
//获取文件大小
        long size = file.getSize();
//判断大小
        if (size > DEFAULT_MAX_SIZE)
        {
            throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
        }

//截取文件名
        String fileName = file.getOriginalFilename();
//获取后缀
        String extension = getExtension(file);
//判断白名单,这里过滤
//不是空,并且不是白名单
        if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
        {
//判断
            if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
                        fileName);
            }
//判断
            else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
                        fileName);
            }
            else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
                        fileName);
            }
            else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
            {
                throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
                        fileName);
            }
            else
            {
                throw new InvalidExtensionException(allowedExtension, extension, fileName);
            }
        }
    }
java 复制代码
 /**
     * 判断MIME类型是否是允许的MIME类型
     *
     * @param extension
     * @param allowedExtension
     * @return
     */
//后缀,数组
    public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
    {
//循环
        for (String str : allowedExtension)
        {//判断
            if (str.equalsIgnoreCase(extension))
            {
                return true;
            }
        }
        return false;
    }
java 复制代码
 /**
     * 默认大小 50M
     */
    public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L;
java 复制代码
public String getOriginalFilename() {
        String filename = this.fileItem.getName();
        if (filename == null) {
            return "";
//private boolean preserveFilename = false;
        } else if (this.preserveFilename) {
            return filename;
        } else {
//查找最后一个/的位置
            int unixSep = filename.lastIndexOf(47);
//返回最后一个\的位置
            int winSep = filename.lastIndexOf(92);
//查找哪一个位置最大
            int pos = Math.max(winSep, unixSep);
//substring 是一种用于‌从字符串中提取子字符串‌的常用方法

//System.out.println(s.substring(7));      // "World!"
//最大加1,截取文件名
            return pos != -1 ? filename.substring(pos + 1) : filename;
        }
    }
java 复制代码
/**
     * 获取文件名的后缀
     * 
     * @param file 表单文件
     * @return 后缀名
     */
    public static final String getExtension(MultipartFile file)
    {//返回后缀

        String extension = FilenameUtils.getExtension(file.getOriginalFilename());
//判断是否为空
        if (StringUtils.isEmpty(extension))
        {
//获取文件名判断是否为空
            extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
        }
//返回后缀
        return extension;
    }
}
java 复制代码
 public static String getExtension(String prefix)
    {
        switch (prefix)
        {
            case IMAGE_PNG:
                return "png";
            case IMAGE_JPG:
                return "jpg";
            case IMAGE_JPEG:
                return "jpeg";
            case IMAGE_BMP:
                return "bmp";
            case IMAGE_GIF:
                return "gif";
            default:
                return "";
        }
    }
java 复制代码
 /**
     * * 判断一个字符串是否为空串
     * 
     * @param str String
     * @return true:为空 false:非空
     */
    public static boolean isEmpty(String str)
    {
        return isNull(str) || NULLSTR.equals(str.trim());
    }
java 复制代码
 public static String getExtension(String fileName) throws IllegalArgumentException {
        if (fileName == null) {
            return null;
        } else {
//返回后缀数字
            int index = indexOfExtension(fileName);
//截断后缀,如果-1返回空
            return index == -1 ? "" : fileName.substring(index + 1);
        }
    }
java 复制代码
public static int indexOfExtension(String fileName) throws IllegalArgumentException {
        if (fileName == null) {
            return -1;
        } else {
// static boolean isSystemWindows() {
        //return SYSTEM_NAME_SEPARATOR == '\\';
    //}
            if (isSystemWindows()) {
                int offset = fileName.indexOf(58, getAdsCriticalOffset(fileName));
                if (offset != -1) {
                    throw new IllegalArgumentException("NTFS ADS separator (':') in file name is forbidden.");
                }
            }
//这里可能防../../qqq这种没有后缀的
//检查最后一个点出现的位置
            int extensionPos = fileName.lastIndexOf(46);
//返回最大位置数字
            int lastSeparator = indexOfLastSeparator(fileName);
//判断大小,返回数字
//如果没有这里的判断可能可以绕过../jsp这种
            return lastSeparator > extensionPos ? -1 : extensionPos;
        }
    }
java 复制代码
public static int indexOfLastSeparator(String fileName) {
        if (fileName == null) {
            return -1;
        } else {
            int lastUnixPos = fileName.lastIndexOf(47);
            int lastWindowsPos = fileName.lastIndexOf(92);
//返回最大位置
// /\
            return Math.max(lastUnixPos, lastWindowsPos);
        }
    }

代码注入

java 复制代码
/**
     * 新增保存调度
     */
    @Log(title = "定时任务", businessType = BusinessType.INSERT)
    @RequiresPermissions("monitor:job:add")
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException
    {
        if (!CronUtils.isValid(job.getCronExpression()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
        }
        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
        }
        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
        }
        job.setCreateBy(getLoginName());
        return toAjax(jobService.insertJob(job));
    }
java 复制代码
/**
     * 新增任务
     * 
     * @param job 调度信息 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(SysJob job) throws SchedulerException, TaskException
    {
//把任务设置为暂停
        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
//是否保存成功
        int rows = jobMapper.insertJob(job);
        if (rows > 0)
        {
//创建任务
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
        return rows;
    }
java 复制代码
/**
     * 新增调度任务信息
     * 
     * @param job 调度任务信息
     * @return 结果
     */
    public int insertJob(SysJob job);
java 复制代码
<insert id="insertJob" parameterType="SysJob" useGeneratedKeys="true" keyProperty="jobId">
 		insert into sys_job(
 			<if test="jobId != null and jobId != 0">job_id,</if>
 			<if test="jobName != null and jobName != ''">job_name,</if>
 			<if test="jobGroup != null and jobGroup != ''">job_group,</if>
 			<if test="invokeTarget != null and invokeTarget != ''">invoke_target,</if>
 			<if test="cronExpression != null and cronExpression != ''">cron_expression,</if>
 			<if test="misfirePolicy != null and misfirePolicy != ''">misfire_policy,</if>
 			<if test="concurrent != null and concurrent != ''">concurrent,</if>
 			<if test="status != null and status != ''">status,</if>
 			<if test="remark != null and remark != ''">remark,</if>
 			<if test="createBy != null and createBy != ''">create_by,</if>
 			create_time
 		)values(
 			<if test="jobId != null and jobId != 0">#{jobId},</if>
 			<if test="jobName != null and jobName != ''">#{jobName},</if>
 			<if test="jobGroup != null and jobGroup != ''">#{jobGroup},</if>
 			<if test="invokeTarget != null and invokeTarget != ''">#{invokeTarget},</if>
 			<if test="cronExpression != null and cronExpression != ''">#{cronExpression},</if>
 			<if test="misfirePolicy != null and misfirePolicy != ''">#{misfirePolicy},</if>
 			<if test="concurrent != null and concurrent != ''">#{concurrent},</if>
 			<if test="status != null and status != ''">#{status},</if>
 			<if test="remark != null and remark != ''">#{remark},</if>
 			<if test="createBy != null and createBy != ''">#{createBy},</if>
 			sysdate()
 		)
	</insert>
java 复制代码
/**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
    {
//检查是否允许并发
        Class<? extends Job> jobClass = getQuartzJobClass(job);
        // 构建job信息
//获取信息
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
//在软件开发中,build()通常针对构建对象或系统的操作
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

        // 表达式调度构建器
///** cron执行表达式 */
  //  @Excel(name = "执行表达式 ")
   // private String cronExpression;

//cronSchedule通常是指基于Cron 表达式的定时任务调度,广泛用于软件系统中按预定时间自动执行任务。
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
//
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
                .withSchedule(cronScheduleBuilder).build();

        // 放入参数,运行时的方法可以获取
        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);

        // 判断是否存在
        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
        {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(getJobKey(jobId, jobGroup));
        }

        // 判断任务是否过期
        if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
        {
            // 执行调度任务
//
            scheduler.scheduleJob(jobDetail, trigger);
        }

        // 暂停任务
        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
        {
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
    }
java 复制代码
/**
     * 设置定时任务策略
     */
    public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
            throws TaskException
    {
        switch (job.getMisfirePolicy())
        {
            case ScheduleConstants.MISFIRE_DEFAULT:
                return cb;
            case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
                return cb.withMisfireHandlingInstructionIgnoreMisfires();
            case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
                return cb.withMisfireHandlingInstructionFireAndProceed();
            case ScheduleConstants.MISFIRE_DO_NOTHING:
                return cb.withMisfireHandlingInstructionDoNothing();
            default:
                throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
                        + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
        }
    }
java 复制代码
 public static CronScheduleBuilder cronSchedule(String cronExpression) {
        try {
            return cronSchedule(new CronExpression(cronExpression));
        } catch (ParseException e) {
            throw new RuntimeException("CronExpression '" + cronExpression + "' is invalid.", e);
        }
    }
java 复制代码
public static JobBuilder newJob(Class<? extends Job> jobClass) {
        JobBuilder b = new JobBuilder();
        b.ofType(jobClass);
        return b;
    }
java 复制代码
/**
     * 得到quartz任务类
     *
     * @param sysJob 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
    {
//检查是否允许并发
        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
//返回不同的类信息
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }
java 复制代码
 /** 是否并发执行(0允许 1禁止) */
    @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止")
    private String concurrent;
java 复制代码
    public JobDetail build() {
        JobDetailImpl job = new JobDetailImpl();
        job.setJobClass(this.jobClass);
        job.setDescription(this.description);
        if (this.key == null) {
            this.key = new JobKey(Key.createUniqueName((String)null), (String)null);
        }

        job.setKey(this.key);
        job.setDurability(this.durability);
        job.setRequestsRecovery(this.shouldRecover);
        if (!this.jobDataMap.isEmpty()) {
            job.setJobDataMap(this.jobDataMap);
        }

        return job;
    }
java 复制代码
 /** 任务ID */
    @Excel(name = "任务序号", cellType = ColumnType.NUMERIC)
    private Long jobId;

    /** 任务名称 */
    @Excel(name = "任务名称")
    private String jobName;

    /** 任务组名 */
    @Excel(name = "任务组名")
    private String jobGroup;
java 复制代码
 /**
     * 立即运行任务
     * 
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean run(SysJob job) throws SchedulerException
    {
        boolean result = false;
        Long jobId = job.getJobId();
        SysJob tmpObj = selectJobById(job.getJobId());
        // 参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(ScheduleConstants.TASK_PROPERTIES, tmpObj);
        JobKey jobKey = ScheduleUtils.getJobKey(jobId, tmpObj.getJobGroup());
        if (scheduler.checkExists(jobKey))
        {
            result = true;
//这里
            scheduler.triggerJob(jobKey, dataMap);
        }
        return result;
    }
java 复制代码
package com.ruoyi.quartz.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.quartz.domain.SysJob;

/**
 * 任务执行工具
 *
 * @author ruoyi
 */
public class JobInvokeUtil
{
    /**
     * 执行方法
     *
     * @param sysJob 系统任务
     */
    public static void invokeMethod(SysJob sysJob) throws Exception
    {
        String invokeTarget = sysJob.getInvokeTarget();
        String beanName = getBeanName(invokeTarget);
        String methodName = getMethodName(invokeTarget);
        List<Object[]> methodParams = getMethodParams(invokeTarget);

        if (!isValidClassName(beanName))
        {
            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);
        }
        else
        {
            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
            invokeMethod(bean, methodName, methodParams);
        }
    }

    /**
     * 调用任务方法
     *
     * @param bean 目标对象
     * @param methodName 方法名称
     * @param methodParams 方法参数
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException
    {
        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
        {
            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        }
        else
        {
            Method method = bean.getClass().getMethod(methodName);
            method.invoke(bean);
        }
    }

    /**
     * 校验是否为为class包名
     * 
     * @param invokeTarget 名称
     * @return true是 false否
     */
    public static boolean isValidClassName(String invokeTarget)
    {
        return StringUtils.countMatches(invokeTarget, ".") > 1;
    }

    /**
     * 获取bean名称
     * 
     * @param invokeTarget 目标字符串
     * @return bean名称
     */
    public static String getBeanName(String invokeTarget)
    {
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringBeforeLast(beanName, ".");
    }

    /**
     * 获取bean方法
     * 
     * @param invokeTarget 目标字符串
     * @return method方法
     */
    public static String getMethodName(String invokeTarget)
    {
        String methodName = StringUtils.substringBefore(invokeTarget, "(");
        return StringUtils.substringAfterLast(methodName, ".");
    }

    /**
     * 获取method方法参数相关列表
     * 
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget)
    {
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr))
        {
            return null;
        }
        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> classs = new LinkedList<>();
        for (int i = 0; i < methodParams.length; i++)
        {
            String str = StringUtils.trimToEmpty(methodParams[i]);
            // String字符串类型,以'或"开头
            if (StringUtils.startsWithAny(str, "'", "\""))
            {
                classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
            }
            // boolean布尔类型,等于true或者false
            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
            {
                classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
            }
            // long长整形,以L结尾
            else if (StringUtils.endsWith(str, "L"))
            {
                classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
            }
            // double浮点类型,以D结尾
            else if (StringUtils.endsWith(str, "D"))
            {
                classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
            }
            // 其他类型归类为整形
            else
            {
                classs.add(new Object[] { Integer.valueOf(str), Integer.class });
            }
        }
        return classs;
    }

    /**
     * 获取参数类型
     * 
     * @param methodParams 参数相关列表
     * @return 参数类型列表
     */
    public static Class<?>[] getMethodParamsType(List<Object[]> methodParams)
    {
        Class<?>[] classs = new Class<?>[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams)
        {
            classs[index] = (Class<?>) os[1];
            index++;
        }
        return classs;
    }

    /**
     * 获取参数值
     * 
     * @param methodParams 参数相关列表
     * @return 参数值列表
     */
    public static Object[] getMethodParamsValue(List<Object[]> methodParams)
    {
        Object[] classs = new Object[methodParams.size()];
        int index = 0;
        for (Object[] os : methodParams)
        {
            classs[index] = (Object) os[0];
            index++;
        }
        return classs;
    }
}
java 复制代码
/**
     * 调用任务方法
     *
     * @param bean 目标对象
     * @param methodName 方法名称
     * @param methodParams 方法参数
     */
    private static void invokeMethod(Object bean, String methodName, List<Object[]> methodParams)
            throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException,
            InvocationTargetException
    {
        if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0)
        {
            Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams));
            method.invoke(bean, getMethodParamsValue(methodParams));
        }
        else
        {
            Method method = bean.getClass().getMethod(methodName);
            method.invoke(bean);
        }
    }
java 复制代码
**
     * 执行方法
     *
     * @param sysJob 系统任务
     */
    public static void invokeMethod(SysJob sysJob) throws Exception
    {
        String invokeTarget = sysJob.getInvokeTarget();
        String beanName = getBeanName(invokeTarget);
        String methodName = getMethodName(invokeTarget);
        List<Object[]> methodParams = getMethodParams(invokeTarget);

        if (!isValidClassName(beanName))
        {
            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);
        }
        else
        {
//这里forName,bean可控
            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
            invokeMethod(bean, methodName, methodParams);
        }
    }
java 复制代码
**
 * 定时任务处理(禁止并发执行)
 * 
 * @author ruoyi
 *
 */
@DisallowConcurrentExecution
public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
    {
        JobInvokeUtil.invokeMethod(sysJob);
    }

模板注入

java 复制代码
/**
 * @update zhixin wen <wenzhixin2010@gmail.com>
 */

var Utils = $.fn.bootstrapTable.utils

function printPageBuilderDefault (table, styles) {
  return `
    <html>
    <head>
//这里
// ${styles}是关键
//若以用到了这个引擎
    ${styles}
    <style type="text/css" media="print">
    @page {
      size: auto;
      margin: 25px 0 25px 0;
    }
    </style>
java 复制代码
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
//预处理,字符替换
//先解析,再拼接
//这个是关键字
<h1 th:href="@{__${name}__}">name1参数</h1>
//普通文本输出
<h1 th:text="${name2}">name2参数</h1>
</body>

https://blog.csdn.net/ljj20020718/article/details/131013126

java 复制代码
  @RequiresPermissions("monitor:cache:view")
    @PostMapping("/getNames")
    public String getCacheNames(String fragment, ModelMap mmap)
    {
        mmap.put("cacheNames", cacheService.getCacheNames());
//这里可以任意拼接
//触发模板解析
//这种::
//前端@{__${}__}
//a/.xx./b这种触发模板解析
//每个语言写法不一样
        return prefix + "/cache::" + fragment;
    }

代码执行

java 复制代码
POST /monitor/job/add HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:150.0) Gecko/20100101 Firefox/150.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8,zh-HK;q=0.7,en-US;q=0.6,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
X-Requested-With: XMLHttpRequest
Content-Length: 145
Origin: http://localhost
Connection: keep-alive
Referer: http://localhost/monitor/job/add
Cookie: JSESSIONID=b8f2b957-2e05-4312-8af1-82ebf41d25e6
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Priority: u=0

createBy=admin&jobName=123&jobGroup=DEFAULT&invokeTarget=ryTask.ryNoParams&cronExpression=0%2F10+*+*+*+*+%3F&misfirePolicy=1&concurrent=1&remark=

这种,然后利用反射

java 复制代码
/**
     * 新增保存调度
     */
    @Log(title = "定时任务", businessType = BusinessType.INSERT)
    @RequiresPermissions("monitor:job:add")
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(@Validated SysJob job) throws SchedulerException, TaskException
    {
        if (!CronUtils.isValid(job.getCronExpression()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确");
        }
        else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS }))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用");
        }
        else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规");
        }
        else if (!ScheduleUtils.whiteList(job.getInvokeTarget()))
        {
            return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内");
        }
        job.setCreateBy(getLoginName());
        return toAjax(jobService.insertJob(job));
    }
java 复制代码
  /**
     * 新增任务
     * 
     * @param job 调度信息 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(SysJob job) throws SchedulerException, TaskException
    {
        job.setStatus(ScheduleConstants.Status.PAUSE.getValue());
        int rows = jobMapper.insertJob(job);
        if (rows > 0)
        {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
        return rows;
    }
java 复制代码
/**
     * 创建定时任务
     */
    public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException
    {
//这里触发
        Class<? extends Job> jobClass = getQuartzJobClass(job);
        // 构建job信息
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build();

        // 表达式调度构建器
        CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression());
        cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder);

        // 按新的cronExpression表达式构建一个新的trigger
        CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup))
                .withSchedule(cronScheduleBuilder).build();

        // 放入参数,运行时的方法可以获取
        jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job);

        // 判断是否存在
        if (scheduler.checkExists(getJobKey(jobId, jobGroup)))
        {
            // 防止创建时存在数据问题 先移除,然后在执行创建操作
            scheduler.deleteJob(getJobKey(jobId, jobGroup));
        }

        // 判断任务是否过期
        if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression())))
        {
            // 执行调度任务
            scheduler.scheduleJob(jobDetail, trigger);
        }

        // 暂停任务
        if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue()))
        {
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
    }
java 复制代码
/**
     * 得到quartz任务类
     *
     * @param sysJob 执行计划
     * @return 具体执行任务类
     */
    private static Class<? extends Job> getQuartzJobClass(SysJob sysJob)
    {
        boolean isConcurrent = "0".equals(sysJob.getConcurrent());
        return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class;
    }
java 复制代码
/**
 * 定时任务处理(允许并发执行)
 * 
 * @author ruoyi
 *
 */
public class QuartzJobExecution extends AbstractQuartzJob
{
    @Override
    protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception
    {
        JobInvokeUtil.invokeMethod(sysJob);
    }
}

forName好东西

java 复制代码
**
     * 执行方法
     *
     * @param sysJob 系统任务
     */
    public static void invokeMethod(SysJob sysJob) throws Exception
    {
        String invokeTarget = sysJob.getInvokeTarget();
//这里
        String beanName = getBeanName(invokeTarget);
//小过滤,可控
        String methodName = getMethodName(invokeTarget);
//字符串
        List<Object[]> methodParams = getMethodParams(invokeTarget);

        if (!isValidClassName(beanName))
        {
            Object bean = SpringUtils.getBean(beanName);
            invokeMethod(bean, methodName, methodParams);
        }
        else
        {
//这里forName,bean可控
//只能调用无参构造类
            Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance();
            invokeMethod(bean, methodName, methodParams);
        }
    }
java 复制代码
/**
     * 获取method方法参数相关列表
     * 
     * @param invokeTarget 目标字符串
     * @return method方法相关参数列表
     */
    public static List<Object[]> getMethodParams(String invokeTarget)
    {
        String methodStr = StringUtils.substringBetween(invokeTarget, "(", ")");
        if (StringUtils.isEmpty(methodStr))
        {
            return null;
        }
        String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
        List<Object[]> classs = new LinkedList<>();
        for (int i = 0; i < methodParams.length; i++)
        {
            String str = StringUtils.trimToEmpty(methodParams[i]);
            // String字符串类型,以'或"开头
            if (StringUtils.startsWithAny(str, "'", "\""))
            {
                classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class });
            }
            // boolean布尔类型,等于true或者false
            else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str))
            {
                classs.add(new Object[] { Boolean.valueOf(str), Boolean.class });
            }
            // long长整形,以L结尾
            else if (StringUtils.endsWith(str, "L"))
            {
                classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class });
            }
            // double浮点类型,以D结尾
            else if (StringUtils.endsWith(str, "D"))
            {
                classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class });
            }
            // 其他类型归类为整形
            else
            {
                classs.add(new Object[] { Integer.valueOf(str), Integer.class });
            }
        }
//返回相应类型
        return classs;
    }
java 复制代码
@NotBlank(message = "调用目标字符串不能为空")
    @Size(min = 0, max = 1000, message = "调用目标字符串长度不能超过500个字符")
    public String getInvokeTarget()
    {
        return invokeTarget;
    }
java 复制代码
    /** 调用目标字符串 */
    @Excel(name = "调用目标字符串")
    private String invokeTarget;
java 复制代码
/**
     * 获取bean名称
     * 
     * @param invokeTarget 目标字符串
     * @return bean名称
     */
    public static String getBeanName(String invokeTarget)
    {
//功能‌:返回第一个参数字符串中,在‌第一次出现第二个参数字符串‌之前的所有字符。
//返回(前面出现的所有参数
        String beanName = StringUtils.substringBefore(invokeTarget, "(");
//返回.前面出现的所有参数
        return StringUtils.substringBeforeLast(beanName, ".");
    }

代码执行分析

相关推荐
yqcoder1 天前
原生 AJAX 揭秘:如何使用 XHR 发起请求
前端·ajax·okhttp
身如柳絮随风扬4 天前
你知道什么是 Ajax 吗?—— 从入门到原理,一篇彻底搞懂
前端·ajax·okhttp
小白学大数据9 天前
Python 自动化爬取网易云音乐歌手歌词实战教程
爬虫·python·okhttp·自动化
前端百草阁11 天前
【吃透 Promise】从基础到面试高频(手写 + 输出题 + 原理)
okhttp·面试·职场和发展
ppandss112 天前
JavaWeb从0到1-DAY4-AJAX
前端·ajax·okhttp
4311媒体网16 天前
织梦CMS点击率统计实现方法
okhttp
帅次17 天前
链路到端上:HTTPS 之后安全题还在考什么
android·okhttp·glide·zygote·retrofit
djk888818 天前
layui zTree 控件 AJAX绑定 点击tree事件 获取tree值
ajax·okhttp·layui
明天就是Friday20 天前
Android实战项目④ OkHttp WebSocket开发即时通讯App 完整源码详解
android·websocket·okhttp