Home

HttpHeaders源码

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.http;

import java.io.Serializable;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.Map.Entry;
import org.springframework.util.Assert;
import org.springframework.util.LinkedCaseInsensitiveMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;

public class HttpHeaders implements MultiValueMap<String, String>, Serializable {
    private static final long serialVersionUID = -8578554704772377436L;
    public static final String ACCEPT = "Accept";
    public static final String ACCEPT_CHARSET = "Accept-Charset";
    public static final String ACCEPT_ENCODING = "Accept-Encoding";
    public static final String ACCEPT_LANGUAGE = "Accept-Language";
    public static final String ACCEPT_RANGES = "Accept-Ranges";
    public static final String AGE = "Age";
    public static final String ALLOW = "Allow";
    public static final String AUTHORIZATION = "Authorization";
    public static final String CACHE_CONTROL = "Cache-Control";
    public static final String CONNECTION = "Connection";
    public static final String CONTENT_ENCODING = "Content-Encoding";
    public static final String CONTENT_DISPOSITION = "Content-Disposition";
    public static final String CONTENT_LANGUAGE = "Content-Language";
    public static final String CONTENT_LENGTH = "Content-Length";
    public static final String CONTENT_LOCATION = "Content-Location";
    public static final String CONTENT_RANGE = "Content-Range";
    public static final String CONTENT_TYPE = "Content-Type";
    public static final String COOKIE = "Cookie";
    public static final String DATE = "Date";
    public static final String ETAG = "ETag";
    public static final String EXPECT = "Expect";
    public static final String EXPIRES = "Expires";
    public static final String FROM = "From";
    public static final String HOST = "Host";
    public static final String IF_MATCH = "If-Match";
    public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
    public static final String IF_NONE_MATCH = "If-None-Match";
    public static final String IF_RANGE = "If-Range";
    public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
    public static final String LAST_MODIFIED = "Last-Modified";
    public static final String LINK = "Link";
    public static final String LOCATION = "Location";
    public static final String MAX_FORWARDS = "Max-Forwards";
    public static final String ORIGIN = "Origin";
    public static final String PRAGMA = "Pragma";
    public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
    public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
    public static final String RANGE = "Range";
    public static final String REFERER = "Referer";
    public static final String RETRY_AFTER = "Retry-After";
    public static final String SERVER = "Server";
    public static final String SET_COOKIE = "Set-Cookie";
    public static final String SET_COOKIE2 = "Set-Cookie2";
    public static final String TE = "TE";
    public static final String TRAILER = "Trailer";
    public static final String TRANSFER_ENCODING = "Transfer-Encoding";
    public static final String UPGRADE = "Upgrade";
    public static final String USER_AGENT = "User-Agent";
    public static final String VARY = "Vary";
    public static final String VIA = "Via";
    public static final String WARNING = "Warning";
    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";
    private static final String[] DATE_FORMATS = new String[]{"EEE, dd MMM yyyy HH:mm:ss zzz", "EEE, dd-MMM-yy HH:mm:ss zzz", "EEE MMM dd HH:mm:ss yyyy"};
    private static TimeZone GMT = TimeZone.getTimeZone("GMT");
    private final Map<String, List<String>> headers;

    public HttpHeaders() {
        this(new LinkedCaseInsensitiveMap(8, Locale.ENGLISH), false);
    }

    private HttpHeaders(Map<String, List<String>> headers, boolean readOnly) {
        Assert.notNull(headers, "'headers' must not be null");
        if (readOnly) {
            Map<String, List<String>> map = new LinkedCaseInsensitiveMap(headers.size(), Locale.ENGLISH);
            Iterator var4 = headers.entrySet().iterator();

            while(var4.hasNext()) {
                Entry<String, List<String>> entry = (Entry)var4.next();
                List<String> values = Collections.unmodifiableList((List)entry.getValue());
                map.put(entry.getKey(), values);
            }

            this.headers = Collections.unmodifiableMap(map);
        } else {
            this.headers = headers;
        }

    }

    public void setAccept(List<MediaType> acceptableMediaTypes) {
        this.set("Accept", MediaType.toString(acceptableMediaTypes));
    }

    public List<MediaType> getAccept() {
        String value = this.getFirst("Accept");
        List<MediaType> result = value != null ? MediaType.parseMediaTypes(value) : Collections.emptyList();
        if (result.size() == 1) {
            List<String> acceptHeader = this.get("Accept");
            if (acceptHeader.size() > 1) {
                value = StringUtils.collectionToCommaDelimitedString(acceptHeader);
                result = MediaType.parseMediaTypes(value);
            }
        }

        return result;
    }

    public void setAcceptCharset(List<Charset> acceptableCharsets) {
        StringBuilder builder = new StringBuilder();
        Iterator iterator = acceptableCharsets.iterator();

        while(iterator.hasNext()) {
            Charset charset = (Charset)iterator.next();
            builder.append(charset.name().toLowerCase(Locale.ENGLISH));
            if (iterator.hasNext()) {
                builder.append(", ");
            }
        }

        this.set("Accept-Charset", builder.toString());
    }

    public List<Charset> getAcceptCharset() {
        List<Charset> result = new ArrayList();
        String value = this.getFirst("Accept-Charset");
        if (value != null) {
            String[] tokens = value.split(",\\s*");
            String[] var4 = tokens;
            int var5 = tokens.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String token = var4[var6];
                int paramIdx = token.indexOf(59);
                String charsetName;
                if (paramIdx == -1) {
                    charsetName = token;
                } else {
                    charsetName = token.substring(0, paramIdx);
                }

                if (!charsetName.equals("*")) {
                    result.add(Charset.forName(charsetName));
                }
            }
        }

        return result;
    }

    public void setAllow(Set<HttpMethod> allowedMethods) {
        this.set("Allow", StringUtils.collectionToCommaDelimitedString(allowedMethods));
    }

    public Set<HttpMethod> getAllow() {
        String value = this.getFirst("Allow");
        if (StringUtils.isEmpty(value)) {
            return EnumSet.noneOf(HttpMethod.class);
        } else {
            List<HttpMethod> allowedMethod = new ArrayList(5);
            String[] tokens = value.split(",\\s*");
            String[] var4 = tokens;
            int var5 = tokens.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                String token = var4[var6];
                allowedMethod.add(HttpMethod.valueOf(token));
            }

            return EnumSet.copyOf(allowedMethod);
        }
    }

    public void setCacheControl(String cacheControl) {
        this.set("Cache-Control", cacheControl);
    }

    public String getCacheControl() {
        return this.getFirst("Cache-Control");
    }

    public void setConnection(String connection) {
        this.set("Connection", connection);
    }

    public void setConnection(List<String> connection) {
        this.set("Connection", this.toCommaDelimitedString(connection));
    }

    public List<String> getConnection() {
        return this.getFirstValueAsList("Connection");
    }

    public void setContentDispositionFormData(String name, String filename) {
        Assert.notNull(name, "'name' must not be null");
        StringBuilder builder = new StringBuilder("form-data; name=\"");
        builder.append(name).append('"');
        if (filename != null) {
            builder.append("; filename=\"");
            builder.append(filename).append('"');
        }

        this.set("Content-Disposition", builder.toString());
    }

    public void setContentLength(long contentLength) {
        this.set("Content-Length", Long.toString(contentLength));
    }

    public long getContentLength() {
        String value = this.getFirst("Content-Length");
        return value != null ? Long.parseLong(value) : -1L;
    }

    public void setContentType(MediaType mediaType) {
        Assert.isTrue(!mediaType.isWildcardType(), "'Content-Type' cannot contain wildcard type '*'");
        Assert.isTrue(!mediaType.isWildcardSubtype(), "'Content-Type' cannot contain wildcard subtype '*'");
        this.set("Content-Type", mediaType.toString());
    }

    public MediaType getContentType() {
        String value = this.getFirst("Content-Type");
        return StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null;
    }

    public void setDate(long date) {
        this.setDate("Date", date);
    }

    public long getDate() {
        return this.getFirstDate("Date");
    }

    public void setETag(String eTag) {
        if (eTag != null) {
            Assert.isTrue(eTag.startsWith("\"") || eTag.startsWith("W/"), "Invalid eTag, does not start with W/ or \"");
            Assert.isTrue(eTag.endsWith("\""), "Invalid eTag, does not end with \"");
        }

        this.set("ETag", eTag);
    }

    public String getETag() {
        return this.getFirst("ETag");
    }

    public void setExpires(long expires) {
        this.setDate("Expires", expires);
    }

    public long getExpires() {
        try {
            return this.getFirstDate("Expires");
        } catch (IllegalArgumentException var2) {
            return -1L;
        }
    }

    public void setIfModifiedSince(long ifModifiedSince) {
        this.setDate("If-Modified-Since", ifModifiedSince);
    }

    /** @deprecated */
    @Deprecated
    public long getIfNotModifiedSince() {
        return this.getIfModifiedSince();
    }

    public long getIfModifiedSince() {
        return this.getFirstDate("If-Modified-Since");
    }

    public void setIfNoneMatch(String ifNoneMatch) {
        this.set("If-None-Match", ifNoneMatch);
    }

    public void setIfNoneMatch(List<String> ifNoneMatchList) {
        this.set("If-None-Match", this.toCommaDelimitedString(ifNoneMatchList));
    }

    protected String toCommaDelimitedString(List<String> list) {
        StringBuilder builder = new StringBuilder();
        Iterator iterator = list.iterator();

        while(iterator.hasNext()) {
            String ifNoneMatch = (String)iterator.next();
            builder.append(ifNoneMatch);
            if (iterator.hasNext()) {
                builder.append(", ");
            }
        }

        return builder.toString();
    }

    public List<String> getIfNoneMatch() {
        return this.getFirstValueAsList("If-None-Match");
    }

    protected List<String> getFirstValueAsList(String header) {
        List<String> result = new ArrayList();
        String value = this.getFirst(header);
        if (value != null) {
            String[] tokens = value.split(",\\s*");
            String[] var5 = tokens;
            int var6 = tokens.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                String token = var5[var7];
                result.add(token);
            }
        }

        return result;
    }

    public void setLastModified(long lastModified) {
        this.setDate("Last-Modified", lastModified);
    }

    public long getLastModified() {
        return this.getFirstDate("Last-Modified");
    }

    public void setLocation(URI location) {
        this.set("Location", location.toASCIIString());
    }

    public URI getLocation() {
        String value = this.getFirst("Location");
        return value != null ? URI.create(value) : null;
    }

    public void setOrigin(String origin) {
        this.set("Origin", origin);
    }

    public String getOrigin() {
        return this.getFirst("Origin");
    }

    public void setPragma(String pragma) {
        this.set("Pragma", pragma);
    }

    public String getPragma() {
        return this.getFirst("Pragma");
    }

    public void setUpgrade(String upgrade) {
        this.set("Upgrade", upgrade);
    }

    public String getUpgrade() {
        return this.getFirst("Upgrade");
    }

    public long getFirstDate(String headerName) {
        String headerValue = this.getFirst(headerName);
        if (headerValue == null) {
            return -1L;
        } else {
            String[] var3 = DATE_FORMATS;
            int var4 = var3.length;
            int var5 = 0;

            while(var5 < var4) {
                String dateFormat = var3[var5];
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat, Locale.US);
                simpleDateFormat.setTimeZone(GMT);

                try {
                    return simpleDateFormat.parse(headerValue).getTime();
                } catch (ParseException var9) {
                    ++var5;
                }
            }

            throw new IllegalArgumentException("Cannot parse date value \"" + headerValue + "\" for \"" + headerName + "\" header");
        }
    }

    public void setDate(String headerName, long date) {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMATS[0], Locale.US);
        dateFormat.setTimeZone(GMT);
        this.set(headerName, dateFormat.format(new Date(date)));
    }

    public String getFirst(String headerName) {
        List<String> headerValues = (List)this.headers.get(headerName);
        return headerValues != null ? (String)headerValues.get(0) : null;
    }

    public void add(String headerName, String headerValue) {
        List<String> headerValues = (List)this.headers.get(headerName);
        if (headerValues == null) {
            headerValues = new LinkedList();
            this.headers.put(headerName, headerValues);
        }

        ((List)headerValues).add(headerValue);
    }

    public void set(String headerName, String headerValue) {
        List<String> headerValues = new LinkedList();
        headerValues.add(headerValue);
        this.headers.put(headerName, headerValues);
    }

    public void setAll(Map<String, String> values) {
        Iterator var2 = values.entrySet().iterator();

        while(var2.hasNext()) {
            Entry<String, String> entry = (Entry)var2.next();
            this.set((String)entry.getKey(), (String)entry.getValue());
        }

    }

    public Map<String, String> toSingleValueMap() {
        LinkedHashMap<String, String> singleValueMap = new LinkedHashMap(this.headers.size());
        Iterator var2 = this.headers.entrySet().iterator();

        while(var2.hasNext()) {
            Entry<String, List<String>> entry = (Entry)var2.next();
            singleValueMap.put(entry.getKey(), ((List)entry.getValue()).get(0));
        }

        return singleValueMap;
    }

    public int size() {
        return this.headers.size();
    }

    public boolean isEmpty() {
        return this.headers.isEmpty();
    }

    public boolean containsKey(Object key) {
        return this.headers.containsKey(key);
    }

    public boolean containsValue(Object value) {
        return this.headers.containsValue(value);
    }

    public List<String> get(Object key) {
        return (List)this.headers.get(key);
    }

    public List<String> put(String key, List<String> value) {
        return (List)this.headers.put(key, value);
    }

    public List<String> remove(Object key) {
        return (List)this.headers.remove(key);
    }

    public void putAll(Map<? extends String, ? extends List<String>> map) {
        this.headers.putAll(map);
    }

    public void clear() {
        this.headers.clear();
    }

    public Set<String> keySet() {
        return this.headers.keySet();
    }

    public Collection<List<String>> values() {
        return this.headers.values();
    }

    public Set<Entry<String, List<String>>> entrySet() {
        return this.headers.entrySet();
    }

    public boolean equals(Object other) {
        if (this == other) {
            return true;
        } else if (!(other instanceof HttpHeaders)) {
            return false;
        } else {
            HttpHeaders otherHeaders = (HttpHeaders)other;
            return this.headers.equals(otherHeaders.headers);
        }
    }

    public int hashCode() {
        return this.headers.hashCode();
    }

    public String toString() {
        return this.headers.toString();
    }

    public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
        return new HttpHeaders(headers, true);
    }
}

Read more

JAVA反射中的getFields()方法和getDeclaredFields ()方法的区别

关于获取类的字段有两种方式:getFields()和getDeclaredFields()。我们先来看看这两者的区别吧:

getFields():获得某个类的所有的公共(public)的字段,包括父类中的字段。 getDeclaredFields():获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。

同样类似的还有getConstructors()和getDeclaredConstructors()、getMethods()和getDeclaredMethods(),这两者分别表示获取某个类的方法、构造函数。

具体编码如下:

我们先创建一个POJO

public class User {
	private long id;
	private String name;

	public void setId(long id) {
		this.id = id;
	}

	public void setName(String name) {
		this.name = name;
	}

	public long getId() {
		return id;
	}

	public String getName() {
		return name;
	}
}

再来获取此类中的所有字段 Field[] fields = User.class.getDeclaredFields(); 获取字段的名称

String fieldName = field.getName();

获取字段的修饰符

int fieldValue = field.getModifiers();//如:private、static、final等

与某个具体的修饰符进行比较

Modifier.isStatic(fieldValue)//看此修饰符是否为静态(static)

获取字段的声明类型

field.getType();//返回的是一个class

与某个类型进行比较

field.getType() == Timestamp.class

获取指定对象中此字段的值

Object fieldObject= field.get(user);//user可以看做是从数据库中查找出来的对象

如果POJO类中有一个集合类型的字段,我们该如何获取字段中中的值呢?

首先创建一个POJO类

public class Bean {

	private String[] love;

	public String[] getLove() {
		return love;
	}

	public void setLove(String[] love) {
		this.love = love;
	}
}

创建一个操作类

import java.lang.reflect.Method;

public class Admin {

	public static void main(String[] args) {

		try {

			// 赋值
			Object obj = Bean.class.newInstance();
			Class paramClass = Class.forName("[Ljava.lang.String;");
			String[] param = { "吃", "喝", "玩", "乐" };
			Method method = Bean.class.getMethod("setLove", paramClass);
			method.invoke(obj, (Object) param);

			// 取值
			Bean bean = (Bean) obj;
			for (int i = 0; i < bean.getLove().length; i++) {
				System.out.println(bean.getLove()[i]);
			}
			System.out.println();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}


得到的结果为: 吃 喝 玩 乐

关于field还有一种情况我们需要注意,就是当字段修饰符为private时,我们需要加上:

field.setAccessible(true);

判断方法是:if (!field.isAccessible())

更详细的解释请点击

接下来补充一下在Java中使用正则表达式

先创建一个正则表达式对象

Pattern pattern = Pattern.compile(“[A-Z][a-z]* “);

指定为字符串的正则表达式必须首先被编译为此类的实例

Matcher matcher = pattern.matcher(fieldName);

尝试查找与该模式匹配的输入序列的下一个子序列

boolean isMatcher = matcher.find();

返回由以前匹配操作所匹配的输入子序列

String str = matcher.group();

实现非终端添加和替换步骤,以’_ ‘连接各个字段

matcher.appendReplacement(sb, str + “_ “);

Read more

Valid介绍及相关注解

主要用途

主要用于表单验证,减轻了代码量

相关依赖

implementation 'org.springframework.boot:spring-boot-starter-web'

在Springboot启动器的web包下包含了javax.validation.Valid所以无需添加多余的依赖

Valid相关注解的使用方式

1.在相关的实体类的相关字段添加用于充当验证条件的注解

示例:

  • 字段:

(ps:这里只是为了演示,大家要注意命名规范)

@NotNull(message = "用户名不能为空")
@Email(message = "邮箱不正确")
private String username;
  • 实体类
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class RegisterUser {
    
      @Min(value = 1000000)
      @NotNull(message = "ID不能为空")
      private Long userId;
    
      @NotNull(message = "用户名不能为空")
      @Email(message = "邮箱不正确")
      private String username;
    
      /**
       * 教师职称
       */
      private String position;
    
      /**
       * 教师所属教研室
       */
      private String office;
    }
    
    2.在controller层的方法的要校验的参数上添加@Valid注解
    @PostMapping("/action/register")
    public Result registerByForm(@Valid @RequestBody RegisterUser registerUser){
      return userService.register(registerUser);
    }
    
    3.编写全局异常捕捉类
    @Slf4j
    @ControllerAdvice
    public class GlobalExceptionHandler {
    
      private final static String EXCEPTION_MSG_KEY = "Exception message : ";
    
      @ResponseBody
      @ExceptionHandler(MethodArgumentNotValidException.class)
      public Result handleValidException(MethodArgumentNotValidException e){
                  //日志记录错误信息
                  log.error(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
                  //将错误信息返回给前台
                  return Result.error(103, Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
      }
    }
    

相关代码类

  • Result类
    @Data
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public class Result<T> {
      private Integer code;
      private String message;
      private T data;
    
      public Result() {
          this.code = 200;
          this.message = "操作成功";
      }
    
      /**
       * 用于错误处理
       * @param code 错误码
       * @param message 错误提示信息
       */
      public Result(Integer code,String message){
          this.code = code;
          this.message = message;
      }
    
      public static  Result<String> error(Integer code,String msg){
          return new Result<>(code,msg);
      }
    }
    
非法字段验证结果
{
  "code":103,
  "message":"邮箱不正确"
}

相关注解

@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(value) 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0)
@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式
需要注意每个注解对应的数据类型

Read more

Controller 数据处理示例(Model ModelAndView redirect)

GitHub源码

直接返回所需要的数据

//删除用户地址
    $(".delAddresslBtn").click(function () {
        var addressId = $(this).attr("addressId");
        $.ajax({
            url: "/user/userAddress/delete/" + addressId,
            success: function (result) {
                if(result.status=="SUCCESS"){
                    $("tr[addressId="+addressId+"]").remove();
                    toastr.info("地址信息删除成功...");
                } else {
                    toastr.error(result.message);
                }
            },
            error: function () {
                toastr.error("发生错误...");
            }
        })
    });
@RequestMapping(value = "/userAddress/delete/{id}", method = RequestMethod.GET)
    @ResponseBody
    public String delUserAddress(@PathVariable Integer id) {
        userAddressService.deleteById(id);
        log.debug("收货地址删除成功...");
        return "success";
    }

返回数据通过JsonResult封装

//保存用户地址
    $("#addAddressBtn").click(function () {
        var addressId = $(this).attr("addressId");
        $.ajax({
            url: "/user/userAddress/add/",
            method:"post",
            data:{
                "id":$("#id").val(),
                "address":$("#address").val(),
                "phone":$("#phone").val(),
                "zipcode":$("#zipcode").val(),
                "consignee":$("#consignee").val()
            },
            success: function (result) {
                if(result.status=="SUCCESS"){
                    $('#addAddressSuccess').show();
                    toastr.info("添加成功");
                } else {
                    toastr.error(result.message);
                }
            },
            error: function () {
                toastr.error("发生错误...");
            }
        })
    });
@RequestMapping(value = "/userAddress/add", method = RequestMethod.POST)
    @ResponseBody
    public JsonResult doAddUserAddress(HttpSession session, UserAddress userAddress) {
        userAddress.setUser(getUserFromSession(session));
        userAddressService.save(userAddress);
        log.debug("地址信息保存成功.");

        JsonResult result = new JsonResult();
        result.setToSuccess();
        return result;
    }

redirect 重定向到其他 controller 继续执行

@RequestMapping(value = "/reg", method = RequestMethod.POST)
    public String doReg(Admin admin) {
        adminService.save(admin);
        return "redirect:/";
    }

通过controller 实现页面跳转

<ul class="nav navbar-nav navbar-right" th:unless="${session.login_user != null}">
                <li><a href="/user/login">登陆</a></li>
                <li><a href="/user/reg">注册</a></li>
            </ul>
@RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "admin/adminLogin";
    }

跳转到 adminLogin.html 页面

通过 redirect 后面携带参数,返回数据到前端

@RequestMapping(value = "/login", method = RequestMethod.POST)
    public String doLogin(Admin admin, HttpSession session) {
        if (adminService.checkLogin(admin)) {
            AdminUtil.saveAdminToSession(session, adminService.findByUsernameAndPassword(admin.getUsername(), admin.getPassword()));
            log.debug("管理员[{}]登陆成功", admin.getUsername());
            return "redirect:/admin/product";
        }
        return "redirect:/admin/login?errorPwd=true";
    }
<form action="#" th:action="@{/user/login}" method="post"
          id="loginForm" class="form-signin">

        <h2 class="form-signin-heading">系统登录</h2>

         <div th:unless="${#httpServletRequest.getParameter('errorPwd')==null}">
             <div id="errorPwd" class="alert alert-success">用户密码错误...</div>
             <script type="text/javascript">
                 setTimeout(function () {
                     $('#errorPwd').hide('slow');
                 }, 3000);
             </script>
         </div>

通过 Model 传递数据到前端渲染

  • html 触发请求 ```html


- controller 接收请求,并将数据传递到前端     
1.通过model传递数据 userAddressList 到前端 userAddress.html 渲染       
2."userAddressList" 对应 html 中的 ${userAddressList}       
```java
@RequestMapping(value = "/userAddress", method = RequestMethod.GET)
    public String userAddress(Model model, HttpSession session) {
        model.addAttribute("title", "地址管理");
        List<UserAddress> userAddressList = userAddressService.findByUserId(UserUtil.getUserFromSession(session).getId());
        model.addAttribute("userAddressList", userAddressList);
        return "user/userAddress";
    }
  • html前端渲染 ```html
收件人 邮编 手机 地址 删除 修改

### 通过 ModelAndView 传递数据到前端渲染
```html
<ul class="nav nav-tabs">
        <!--<li id="sub-nav-admin"><a href="/user/profile">个人信息</a></li>-->
        <li id="sub-nav-product"><a href="/admin/product">商品管理</a></li>
        <li id="sub-nav-order"><a href="/admin/order">订单管理</a></li>
        <li id="sub-nav-news"><a href="/admin/news">公告管理</a></li>
    </ul>
@RequestMapping(method = RequestMethod.GET)
    public ModelAndView index(ModelAndView model, HttpServletRequest request) {
        Page<News> page = new Page<News>(request);
        newsService.findNews(page);
        model.addObject("page",page);
        model.setViewName("admin/news/newsAdmin");
        return model;
    }
<tbody>
            <tr th:each="news:${page.result}" th:pid="${news.id}">
                <td th:text="${news.createTime}"></td>
                <td th:text="${news.title}"></td>
                <td><a class="btn btn-info btn-xs">查看</a> <a class="btn btn-info btn-xs">修改 <a
                        class="btn btn-info btn-xs">删除</a></a></td>
            </tr>
            </tbody>

Read more

Springboot 使用BindingResult校验参数

配合@Valid一起使用

1、创建一个参数对象

import java.util.List;

import javax.validation.constraints.Min;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.NotEmpty;

public class Parameter {

	@NotEmpty(message="姓名不能为空")
	private String name;

	@Min(value = 18, message = "年龄必须大于18岁")
	private int age;

	@NotEmpty(message="hobbies不能为空")
	private List<String> hobbies;

	@NotBlank(message="账号不能为空")
	private String account;

	@Size(min=5,max=10,message="密码的长度应该在5和10之间")
	private String password;

	@Email(message="邮箱格式错误")
	private String email;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public List<String> getHobbies() {
		return hobbies;
	}
	public void setHobbies(List<String> hobbies) {
		this.hobbies = hobbies;
	}
	public String getAccount() {
		return account;
	}
	public void setAccount(String account) {
		this.account = account;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public String getEmail() {
		return email;
	}
	public void setEmail(String email) {
		this.email = email;
	}
}

2、controller控制层写参数接收的入口,需要注意的是@Valid 和 BindingResult 是一 一对应的,如果有多个@Valid,那么每个@Valid后面都需要添加BindingResult用于接收bean中的校验信息

@RequestMapping(value = "/test", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public @ResponseBody ResponseEntity<Pesponsibles> testBindingResult(@Valid @RequestBody Parameter parameter,BindingResult bindingResult)
			{
		log.info("test start");
		Pesponsibles pesponsibles=new Pesponsibles();
		if(bindingResult.hasErrors()){
			 List<FieldError> fieldErrors = bindingResult.getFieldErrors();
			 fieldErrors.forEach(fieldError -> {
                   //日志打印不符合校验的字段名和错误提示
                   log.error("error field is : {} ,message is : {}", fieldError.getField(), fieldError.getDefaultMessage());
                 });
			 for(int i=0;i<fieldErrors.size();i++){
                    //控制台打印不符合校验的字段名和错误提示
			System.out.println("error field is :"+fieldErrors.get(i).getField()+",message is :"+fieldErrors.get(i).getDefaultMessage());
			 }
			// pesponsibles.setError_msg(fieldErrors);
			return new ResponseEntity<>(pesponsibles, HttpStatus.BAD_REQUEST);
		}
		return new ResponseEntity<>(pesponsibles, HttpStatus.OK);
}


3、常用校验注解

@Null 只能是null
@NotNull 不能为null 注意用在基本类型上无效,基本类型有默认初始值
@AssertFalse 必须为false
@AssertTrue 必须是true

字符串/数组/集合检查:(字符串本身就是个数组)
@Pattern(regexp="reg") 验证字符串满足正则
@Size(max, min) 验证字符串、数组、集合长度范围
@NotEmpty 验证字符串不为空或者null
@NotBlank 验证字符串不为null或者trim()后不为空

数值检查:同时能验证一个字符串是否是满足限制的数字的字符串
@Max 规定值得上限int
@Min 规定值得下限
@DecimalMax("10.8") 以传入字符串构建一个BigDecimal,规定值要小于这个值 
@DecimalMin 可以用来限制浮点数大小
@Digits(int1, int2) 限制一个小数,整数精度小于int1;小数部分精度小于int2
@Digits 无参数,验证字符串是否合法
@Range(min=long1,max=long2) 检查数字是否在范围之间
这些都包括边界值

日期检查:Date/Calendar
@Post 限定一个日期,日期必须是过去的日期
@Future 限定一个日期,日期必须是未来的日期

其他验证:
@Vaild 递归验证,用于对象、数组和集合,会对对象的元素、数组的元素进行一一校验
@Email 用于验证一个字符串是否是一个合法的右键地址,空字符串或null算验证通过
@URL(protocol=,host=,port=,regexp=,flags=) 用于校验一个字符串是否是合法UR

Read more

控制类中,return的几种形态

redirect重定向流程

客户发送一个请求到服务器,服务器匹配servlet,这都和请求转发一样,servlet处理完之后调用了sendRedirect()这个方法,这个方法是response的方法,所以,当这个servlet处理完之后,看到response.senRedirect()方法,立即向客户端返回这个响应,响应行告诉客户端你必须要再发送一个请求,去访问test.jsp,紧接着客户端受到这个请求后,立刻发出一个新的请求,去请求test.jsp,这里两个请求互不干扰,相互独立,在前面request里面setAttribute()的任何东西,在后面的request里面都获得不了。可见,在sendRedirect()里面是两个请求,两个响应。

重定向场景

Spring MVC中做form表单功能提交时,防止用户客户端后退或者刷新时重复提交问题,需要在服务端进行重定向跳转,其中redirect是直接跳转到其他页面,有以下3种方法进行重定向。

1. response.sendRedirect重定向跳转

@RequestMapping(value="/testredirect",method = { RequestMethod.POST, RequestMethod.GET })  
public ModelAndView testredirect(HttpServletResponse response){  
    response.sendRedirect("/index");
    return null;
}

2. ViewResolver直接跳转

不带参数

@RequestMapping(value="/testredirect",method = { RequestMethod.POST, RequestMethod.GET })  
public  String testredirect(HttpServletResponse response){  
    return "redirect:/index";  
}

带参数

@RequestMapping("/testredirect")
public String testredirect(Model model, RedirectAttributes attr) {
	attr.addAttribute("test", "51gjie");//跳转地址带上test参数
    attr.addFlashAttribute("u2", "51gjie");//跳转地址不带上u2参数
	return "redirect:/user/users";
}

3. ModelAndView重定向

不带参数

@RequestMapping(value="/restredirect",method = { RequestMethod.POST, RequestMethod.GET })  
public  ModelAndView restredirect(String userName){  
    ModelAndView  model = new ModelAndView("redirect:/main/index");    
    return model;  
}

带参数

@RequestMapping(value="/toredirect",method = { RequestMethod.POST, RequestMethod.GET })  
public  ModelAndView toredirect(String userName){  
    ModelAndView  model = new ModelAndView("redirect:/main/index");   
    model.addObject("userName", userName);  //把userName参数带入到controller的RedirectAttributes
    return model;  
}

其他return 形态

带参数,跳转到admin/news/newsDetail页面
@RequestMapping(value = "/news/{id}")
    public ModelAndView newsView(@PathVariable("id") Integer id,ModelAndView model) {
        News news = newsService.findById(id);
        model.addObject("news", news);
        model.setViewName("admin/news/newsDetail");
        return model;
    }
不带参数,跳转到 admin/adminReg 页面
@RequestMapping(value = "/reg", method = RequestMethod.GET)
    public String reg() {
        return "admin/adminReg";
    }
返回指定数据到前端,JsonResult
@RequestMapping(value = "/news/delete/{id}")
    @ResponseBody
    public JsonResult newsDelete(@PathVariable("id") Integer id) {
        newsService.delNews(id);
        JsonResult result = new JsonResult();
        result.setToSuccess();
        return result;
    }

———2020/03/01 更新 ModelAndView 和 Map 及 Model

ModelAndView

概述

控制器处理方法的返回值如果为 ModelAndView, 则其既包含视图信息,也包含模型数据信息。

添加模型数据:

  • MoelAndView addObject(String attributeName, Object attributeValue)
  • ModelAndView addAllObject(Map< String, ?> modelMap )

####设置视图:

  • void setView(View view )
  • void setViewName(String viewName)

示例

  • register_form.jsp ```html <!DOCTYPE HTML>
Getting Started: Serving Web Content
username:
password:
email:
age:
province:
city:
- ModelController.java
```java
package com.ricky.codelab.webapp.ch3;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import com.ricky.codelab.webapp.ch2.model.User;

@Controller
@RequestMapping("model")
public class ModelController {

    @RequestMapping("register")
    public ModelAndView register(User user){

        ModelAndView mv = new ModelAndView("home");
        mv.addObject("user", user);

        return mv;
    }
}
  • home.jsp ```html <%@ page language=”java” contentType=”text/html; charset=UTF-8” pageEncoding=”UTF-8”%> <!DOCTYPE html PUBLIC “-//W3C//DTD HTML 4.01 Transitional//EN” “http://www.w3.org/TR/html4/loose.dtd”>

Welcome ${requestScope.user.username }!


## Map 及 Model

### 概述    
Spring MVC 在内部使用了一个org.springframework.ui.Model 接口存储模型数据。

Spring MVC 在调用方法前会创建一个–隐含的模型对象作为模型数据的存储容器。如果方法的入参为 Map 或 Model 类型,Spring MVC 会隐含模型的引用传递给这些入参。在方法体内,开发者可以通过这个入参对象访问到模型中的所有数据,也可以向模型中添加新的属性数据。

#### 示例
- 1、Map
```java
package com.ricky.codelab.webapp.ch3;

import java.util.Map;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import com.ricky.codelab.webapp.ch2.model.User;

@Controller
@RequestMapping("model")
public class ModelController {

    @RequestMapping("register")
    public String register(User user, Map<String, Object> map){

        map.put("user", user);

        return "home";
    }
}
  • 2、Model ```java package com.ricky.codelab.webapp.ch3;

import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping;

import com.ricky.codelab.webapp.ch2.model.User;

@Controller @RequestMapping(“model”) public class ModelController {

@RequestMapping("register")
public String register(User user, Model model){

    model.addAttribute("user", user);

    return "home";
} } ```

Read more

Controller和@RestController的区别?

知识点@RestController注解相当于@ResponseBody + @Controller合在一起的作用。

1) 如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,或者html,配置的视图解析器 InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。

2) 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。

例如:

1.使用@Controller 注解,在对应的方法上,视图解析器可以解析return 的jsp,html页面,并且跳转到相应页面

若返回json等内容到页面,则需要加@ResponseBody注解

@CrossOrigin
@Controller
public class FileUploadController {
  //跳转到上传文件的页面
  @RequestMapping(value="/gouploadimg", method = RequestMethod.GET)
  public String goUploadImg() {
    //跳转到 templates 目录下的 uploadimg.html
    return "uploadimg";
  }

  //处理文件上传
  @RequestMapping(value="/testuploadimg", method = RequestMethod.POST)
  public @ResponseBody String uploadImg(@RequestParam("file") MultipartFile file,
  HttpServletRequest request) {
    System.out.println("调用文件上传方法");
    String contentType = file.getContentType();
    String fileName = file.getOriginalFilename();
    。。。
  }
}

2.@RestController注解,相当于@Controller+@ResponseBody两个注解的结合,返回json数据不需要在方法前面加@ResponseBody注解了,但使用@RestController这个注解,就不能返回jsp,html页面,视图解析器无法解析jsp,html页面

@CrossOrigin
@RestController /* @Controller + @ResponseBody*/
public class HospitalController {

    //注入Service服务对象
    @Autowired
    private HospitalService hospitalService;

    /**
     * 查询所有医院信息(未分页)
     */

    @RequestMapping(value = "findAllHospital",method = RequestMethod.GET)
    public  List<Hospital> findAllHospital(){
        List<Hospital> hospitalList= hospitalService.findAllHospital();
        return hospitalList;
    }

Read more

建造模式

@Builder

###1. @Builder 实战使用

如果对建造者设计模式不太清楚的,可以先了解一下:建造这模式

@Builder注释为你的类生成相对略微复杂的构建器API。@Builder可以让你以下面显示的那样调用你的代码,来初始化你的实例对象:

Person.builder()
    .name("Adam Savage")
    .city("San Francisco")
    .job("Mythbusters")
    .job("Unchained Reaction")
    .build();

@Builder可以放在类,构造函数或方法上。 虽然放在类上和放在构造函数上这两种模式是最常见的用例,但@Builder最容易用放在方法的用例来解释。

使用@Builder注释的方法可以帮助我们完成一下一些事情:

  • 一个名为FooBuilder的内部静态类,并具有和实体类形同的属性(称为构建器)。
  • 在构建器中:对于目标类中的所有的属性和未初始化的final字段,都会在构建器中创建对应属性;
  • 在构建器中:创建一个无参的default构造函数。
  • 在构建器中:对于实体类中的每个参数,都会对应创建类似于“setter”的方法,只不多方法名与该参数名相同。 并且返回值是构建器本身(便于链式调用),如上例所示。
  • 在构建器中:一个build()方法,调用此方法,就会根据设置的值进行创建实体对象。
  • 在构建器中:同时也会生成一个toString()方法。
  • 在实体类中:会创建一个builder()方法,它的目的是用来创建构建器。

说的多了有点绕,我们还是先来使用一下试试,然后再详细说明:

@Builder
public class User {
    private String username;
    private String password;
}

// 编译后:
public class User {
    private String username;
    private String password;
    User(String username, String password) {
        this.username = username; this.password = password;
    }
    public static User.UserBuilder builder() {
        return new User.UserBuilder();
    }

    public static class UserBuilder {
        private String username;
        private String password;
        UserBuilder() {}

        public User.UserBuilder username(String username) {
            this.username = username;
            return this;
        }
        public User.UserBuilder password(String password) {
            this.password = password;
            return this;
        }
        public User build() {
            return new User(this.username, this.password);
        }
        public String toString() {
            return "User.UserBuilder(username=" + this.username + ", password=" + this.password + ")";
        }
    }
}

2. @Builder 中使用 @Singular 注释集合

2.1 @Singular 实战使用

@Builder也可以为集合类型的参数或字段生成一种特殊的方法。 它采用修改列表中一个元素而不是整个列表的方式,可以是增加一个元素,也可以是删除一个元素。 例如:

Person.builder()
    .job("Mythbusters")
    .job("Unchained Reaction")
    .build();

这样就可以轻松地将List 字段中包含2个字符串。 但是想要这样来操作集合,你需要使用@Singular来注释字段或参数。

在使用@Singular注释注释一个集合字段(使用@Builder注释类),lombok会将该构建器节点视为一个集合,并生成两个adder方法而不是setter方法。

  • 一个向集合添加单个元素,
  • 一个将另一个集合的所有元素添加到集合中。 将不生成仅设置集合(替换已添加的任何内容)的setter。 还生成了clear方法。 这些singular构建器相对而言是有些复杂的,主要是来保证以下特性:

  • 在调用build()时,生成的集合将是不可变的。
  • 在调用build()之后调用其中一个adder方法或clear方法不会修改任何已经生成的对象。如果对集合修改之后,再调用build(),则会创建一个基于上一个对象创建的对象实体。
  • 生成的集合将被压缩到最小的可行格式,同时保持高效。 @Singular只能应用于lombok已知的集合类型。目前,支持的类型有:

  • java.util:
    • Iterable, Collection, 和List (一般情况下,由压缩的不可修改的ArrayList支持).
    • Set, SortedSet, and NavigableSet (一般情况下,生成可变大小不可修改的HashSet或者TreeSet).
    • Map, SortedMap, and NavigableMap (一般情况下,生成可变大小不可修改的HashMap或者TreeMap).
  • Guava’s com.google.common.collect:
    • ImmutableCollection and ImmutableList
    • ImmutableSet and ImmutableSortedSet
    • ImmutableMap, ImmutableBiMap, and ImmutableSortedMap
    • ImmutableTable 再来看看使用了@Singular注解之后的编译情况: ```java @Builder public class User { private final Integer id; private final String zipCode = “215500”; private String username; private String password; @Singular private List hobbies; }

// 编译后: public class User { private final Integer id; private final String zipCode = “215500”; private String username; private String password; private List hobbies; User(Integer id, String username, String password, List hobbies) { this.id = id; this.username = username; this.password = password; this.hobbies = hobbies; }

public static User.UserBuilder builder() {return new User.UserBuilder();}

public static class UserBuilder {
    private Integer id;
    private String username;
    private String password;
    private ArrayList<String> hobbies;
    UserBuilder() {}
    public User.UserBuilder id(Integer id) { this.id = id; return this; }
    public User.UserBuilder username(String username) { this.username = username; return this; }
    public User.UserBuilder password(String password) { this.password = password; return this; }

    public User.UserBuilder hobby(String hobby) {
        if (this.hobbies == null) {
            this.hobbies = new ArrayList();
        }
        this.hobbies.add(hobby);
        return this;
    }

    public User.UserBuilder hobbies(Collection<? extends String> hobbies) {
        if (this.hobbies == null) {
            this.hobbies = new ArrayList();
        }
        this.hobbies.addAll(hobbies);
        return this;
    }

    public User.UserBuilder clearHobbies() {
        if (this.hobbies != null) {
            this.hobbies.clear();
        }
        return this;
    }

    public User build() {
        List hobbies;
        switch(this.hobbies == null ? 0 : this.hobbies.size()) {
        case 0:
            hobbies = Collections.emptyList();
            break;
        case 1:
            hobbies = Collections.singletonList(this.hobbies.get(0));
            break;
        default:
            hobbies = Collections.unmodifiableList(new ArrayList(this.hobbies));
        }
        return new User(this.id, this.username, this.password, hobbies);
    }
    public String toString() {
        return "User.UserBuilder(id=" + this.id + ", username=" + this.username + ", password=" + this.password + ", hobbies=" + this.hobbies + ")";
    }
} } ```

其实,lombok的创作者还是很用心的,在进行build()来床建实例对象时,并没有直接使用Collections.unmodifiableList(Collection)此方法来床架实例,而是分为三种情况,

  • 第一种,当集合中没有元素时,创建一个空list;
  • 第二种情况,当集合中存在一个元素时,创建一个不可变的单元素list;
  • 第三种情况,根据当前集合的元素数量创建对应合适大小的list; 当然我们看编译生成的代码,创建了三个关于集合操作的方法:

  • hobby(String hobby):向集合中添加一个元素
  • hobbies(Collection<? extends String> hobbies):添加一个集合所有的元素
  • clearHobbies():清空当前集合数据 2.2 @Singular 注解配置详解

我们先来看看 @Singular 注解的详情:

@Target({FIELD, PARAMETER})
@Retention(SOURCE)
public @interface Singular {
	// 修改添加集合元素的方法名
	String value() default "";
}

测试如何使用注解属性vlaue

@Builder
public class User {
    private final Integer id;
    private final String zipCode = "215500";
    private String username;
    private String password;
    @Singular(value = "testHobbies")
    private List<String> hobbies;
}

// 测试类
public class BuilderTest {
    public static void main(String[] args) {
        User user = User.builder()
                .testHobbies("reading")
                .testHobbies("chatting")
                .id(1)
                .password("jdkong")
                .username("jdkong")
                .build();
        System.out.println(user);
    }
}

说明,当我们使用了注解属性value之后,我们在使用添加集合元素时的方法名发生相应的改变。但是,同时生成的添加整个集合的方法名发生改变了吗?我们再来看看编译后的代码:

// 编译后:
public class User {
	// 省略部分代码,只看关键部分
    public static class UserBuilder {
		public User.UserBuilder testHobbies(String testHobbies) {
            if (this.hobbies == null) {
                this.hobbies = new ArrayList();
            }
            this.hobbies.add(testHobbies);
            return this;
        }

        public User.UserBuilder hobbies(Collection<? extends String> hobbies) {
            if (this.hobbies == null) {
                this.hobbies = new ArrayList();
            }
            this.hobbies.addAll(hobbies);
            return this;
        }

        public User.UserBuilder clearHobbies() {
            if (this.hobbies != null) {
                this.hobbies.clear();
            }
            return this;
        }
    }
}

可以看到,只有添加一个元素的方法名发生了改变。

3. @Builder.Default 使用

比如有这样一个实体类:

@Builder
@ToString
public class User {
    @Builder.Default
    private final String id = UUID.randomUUID().toString();
    private String username;
    private String password;
    @Builder.Default
    private long insertTime = System.currentTimeMillis();
}

在类中我在id和insertTime上都添加注解@Builder.Default,当我在使用这个实体对象时,我就不需要在为这两个字段进行初始化值,如下面这样:

public class BuilderTest {
    public static void main(String[] args) {
        User user = User.builder()
                .password("jdkong")
                .username("jdkong")
                .build();
        System.out.println(user);
    }
}

// 输出内容:
User(id=416219e1-bc64-43fd-b2c3-9f8dc109c2e8, username=jdkong, password=jdkong, insertTime=1546869309868)

lombok在实例化对象时就为我们初始化了这两个字段值。

当然,你如果再对这两个字段进行设值的话,那么默认定义的值将会被覆盖掉,如下面这样:

public class BuilderTest {
    public static void main(String[] args) {
        User user = User.builder()
                .id("jdkong")
                .password("jdkong")
                .username("jdkong")
                .build();
        System.out.println(user);
    }
}
// 输出内容
User(id=jdkong, username=jdkong, password=jdkong, insertTime=1546869642151)

4. @Builder 详细配置

下面我们再来详细看看@Builder这个注解类地详细实现:

@Target({TYPE, METHOD, CONSTRUCTOR})
@Retention(SOURCE)
public @interface Builder {
	// 如果@Builder注解在类上,可以使用 @Builder.Default指定初始化表达式
	@Target(FIELD)
	@Retention(SOURCE)
	public @interface Default {}
	// 指定实体类中创建 Builder 的方法的名称,默认为: builder (个人觉得没必要修改)
	String builderMethodName() default "builder";
	// 指定 Builder 中用来构件实体类的方法的名称,默认为:build (个人觉得没必要修改)
	String buildMethodName() default "build";
	// 指定创建的建造者类的名称,默认为:实体类名+Builder
	String builderClassName() default "";
	// 使用toBuilder可以实现以一个实例为基础继续创建一个对象。(也就是重用原来对象的值)
	boolean toBuilder() default false;

	@Target({FIELD, PARAMETER})
	@Retention(SOURCE)
	public @interface ObtainVia {
		// 告诉lombok使用表达式获取值
		String field() default "";
		// 告诉lombok使用表达式获取值
		String method() default "";

		boolean isStatic() default false;
	}
}

以上注解属性,我只测试一个比较常用的toBuilder,因为我们在对实体对象进行操作时,往往会存在对某些实体对象的某个字段进行二次赋值,这个时候就会用到这一属性。但是,这会创建一个新的对象,而不是原来的对象,原来的对象属性是不可变的,除非你自己想要给这个实体类再添加上@Data或者@setter方法(我是这么干过,哈哈)。下面就来测试一下:

@Builder(toBuilder = true)
@ToString
public class User {
    private String username;
    private String password;
}
// 测试类
public class BuilderTest {
    public static void main(String[] args) {
        User user1 = User.builder()
                .password("jdkong")
                .username("jdkong")
                .build();
        System.out.println(user1);

        User user2 = user1.toBuilder().username("jdkong2").build();
        // 验证user2是否是基于user1的现有属性创建的
        System.out.println(user2);
        // 验证对象是否是同一对象
        System.out.println(user1 == user2);
    }
}
// 输出内容
User(username=jdkong, password=jdkong)
User(username=jdkong2, password=jdkong)
false
  1. @Builder 全局配置
    # 是否禁止使用@Builder
    lombok.builder.flagUsage = [warning | error] (default: not set)
    #是否使用Guaua
    lombok.singular.useGuava = [true | false] (default: false)
    # 是否自动使用singular默认是使用
    lombok.singular.auto = [true | false] (default: true)
    

Read more