java面向对象的设计在开发过程中体现最深刻的莫过于各种属性的访问方式,在java对象属性的访问上,基于安全考虑我们几乎都采用getter和setter方式,在对象的比较上使用equals以及hashcode,在对象的简单封装输出上使用toString,就这些最基础的功能上,思考一下,一个简单的对象需要多少行代码呢?
2016-11-24 更新了j2se6中关于注解的描述。
抄来一段代码
public class Story{
private int id;
private String title;
public Story(int id, String title){
this.id = id;
this.title = title;
}
public int id(){
return this.id;
}
public int title(){
return this.title;
}
@Override
public String toString(){
return id + title;
}
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + this.id;
hash = 31 * hash + (null == title ? 0 : title.hashCode());
return hash;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof Stroy)) return false;
Story s = (Story) o;
if (s.id != s.id) return false;
return s.title.equals(s.title);
}
}
是的,都还没开始已经篇幅不小了。一个对象这么多,而且这些都是具备模板特性一样的,所以这个时候工具能帮我们了,简单介绍下两个工作中常用的工具:
- lombok https://projectlombok.org/features/index.html
- autovalue https://github.com/google/auto/tree/master/value
lombok比较老牌了,在很多框架中经常中经常看到他们的声影,比如ddframework中的那些框架就高度依赖并使用了它的各种特性,lombok需要依赖IDE对功能的支持,也就是说需要外部提供jdk之外的工具类的支持。
autovalue平常见的真不多,不过在安卓开发中使用的频率明显高于服务端的java开发,因为提供的部分扩展是安卓的java特性,在依赖上使用的是jdk的java interfate注解编译特性,不需要IDE的特殊支持,比如在IDEA上只需要勾选是否支持java annotation process选项。在brave框架中用的非常普遍,从书上了解到这里应该是属于j2se6中引入的新的注解处理机制,通过JSR269对注解机制进行了标准化。这里称之为可插播的注解的处理方式,可以通过注解处理器生成对应的源文件、字节代码文件和资源文件,这里的生成过程则有IDE的内部编译api完成,这里摘自jdk7的笔记。
注解的三种保留策略RetentionPolicy分别是SOURCE/CLASS/RUNTIME,其中SOURCE意思是仅仅在源码中有效,这里的lombok和autovalue就是这种类型,并且编译时唯一的处理方式,如果注解是RUNTIME类型,则在反射中可以被用到,比如aop经常用到的注解的形态。
看一下lombok的保留策略可以看到,和上面提到的一致。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface ToString {
boolean includeFieldNames() default true;
String[] exclude() default {};
String[] of() default {};
boolean callSuper() default false;
boolean doNotUseGetters() default false;
}
究竟这些是怎么做到的呢? lombok依赖像asm这种class文件二进制编译修改作用修改了原class文件,并生成对应的包含上述完整代码的class文件,而java文件只需要简单的几句注解即可,非常方便。
autovalue则是生成了另外一个class类,这个class文件可以看到它的java源文件,也就是说可以看到两个java文件和两个class文件,完全遵循java的规范,不侵入原有的java代码。
说了这么多他们都是怎么工作的呢?
lombok
先看看工作中的一个例子,用aws s3保存一张图片并返回一个图片对象
@Getter
@Builder
@ToString
public class AwsImg {
private int width;
private int height;
private String folder;
private String name;
private String suffix;
}
在这里使用了builder建造方式来生成一个对象,同时在使用中对异常输出是需要把记录记下来,所以使用了@ToString注解,在正常情况下需要得到获取的对象的值,所以加了Getter参数。
上述情况其实是定义了一个final对象,那在普通场景下如何修改一个对象呢?看下面
@Data
public class FollowBean {
private Long follower;
private Long followed;
}
这是个SNS的标准类,关注的人或者粉丝,这里的@Data默认给下面的private对象创建getter setter,对比上面的类,可以猜到,@Data=@Getter+@Setter
如果是构造方法如何处理呢,这里也有对应的解决方案。
- @NoArgsConstructor 无参构造
- @RequiredArgsConstructor 按需构造
- @AllArgsConstructor 全参构造
如果是比较的场景怎么办,也有,同时override了equals和hashcode的方法 @EqualsAndHashCode
最后再介绍一个用的很频繁的注解 @Log 工作当中用的最多的是slf4j的接口类,所以@Slf4j注解便是首选了,使用的时候
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class);
log.info("j360");
lombok还提供了其他一些不怎么常用的注解,比如
- @Cleanup 相当于jdk7的autoclose接口
- val 相当于定义了一个局部final变量,可以无视类型
- @Value 相当于偷懒一口气final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter,相当霸气
- @NonNull 没错非null校验,没怎么用,用的其他的框架的注解,当然完全可以替换,抛出的异常可以去设置
- java -jar lombok.jar config -g --verbose 设置对应的一些配置,因为这样子启动比较麻烦,所以就用了常用的那些功能。
缺点:对于提供的诸多功能如果对于父类子类、接口类抽象类上面的使用门槛太高,导致都手写这方面的代码,只在部分场合使用lombok注解。
AutoValue
autovalue大名鼎鼎google出品,对,就是@AutoValue一个注解,解决lombok的equals+hashcode问题,比如
@AutoValue
public abstract class Story{
public abstract int id();
public abstract String title();
public static Story create(int id, String title){
new AutoValue_Story(id,title);
}
}
这里定义的对象都是抽象类,因为autovalue帮你实现了后面的方法,理解上和lombok有着非常大的区别,按照上面lombok的几个场景怎么整呢?
import com.google.auto.value.AutoValue;
@AutoValue
abstract class Animal {
abstract String name();
abstract int numberOfLegs();
static Builder builder() {
return new AutoValue_Animal.Builder();
}
@AutoValue.Builder
abstract static class Builder {
abstract Builder setName(String value);
abstract Builder setNumberOfLegs(int value);
abstract Animal build();
}
}
没错,可没那么方便了,需要自己定义Builder静态内部类,同时最终生成对应的Animal类,使用上没有其他区别。
Animal.builder().setName("dog").setNumberOfLegs(4).build()
对于空校验,则使用@Nullable注解,guava提供了很多很多很好的校验工具Preconditions包下面的类。在这里经常用这个类替换lombok的@NonNull
autovalue在使用上确实没有lombok来的那么方便,但是在更加复杂的场景(接口类、抽象类、实现类)上面的可扩展比lombok要很多,不过在这些场景下,建议还是自己手写全部的代码,毕竟这些重要场景还是自己来的好。
比如:这里定义的Api的restfull返回的json对象简单抽象封装code和result
public abstract class BaseResponse extends BaseEntity {
@Getter
protected int status = ApiStatus.SUCCESS;
@Getter
protected String error = "";
protected BaseResponse(int status, String error) {
this.status = status;
this.error = error;
}
public abstract static class Builder<R extends BaseResponse, B extends Builder<R, B>> {
private B theBuilder;
protected Integer status;
protected String error;
public Builder () {
theBuilder = getThis();
}
protected abstract B getThis();
public B status(Integer status) {
this.status = status;
return theBuilder;
}
public B error(String error) {
this.error = error;
return theBuilder;
}
public abstract R build();
}
}
@Slf4j
public class ApiResponse<D> extends BaseResponse {
@Getter
protected D data;
private static Builder newBuilder() {
return new Builder();
}
public static ApiResponse createResponse() {
return newBuilder().data(null).status(ApiStatus.SUCCESS).error("").build();
}
public static <D> ApiResponse createResponse(D data) {
return newBuilder().data(data).status(ApiStatus.SUCCESS).error("").build();
}
protected ApiResponse(D data, int status, String error) {
super(status, error);
this.data = data;
}
public static class Builder<D> extends BaseResponse.Builder {
private D data;
@Override
protected Builder getThis() {
return this;
}
@Override
public ApiResponse build() {
return new ApiResponse(data, status, error);
}
public Builder data(D data) {
this.data = data;
return this;
}
@Override
public Builder status(Integer status) {
this.status = status;
return this;
}
@Override
public Builder error(String error) {
this.error = error;
return this;
}
}
/**
* web端异常返回结果
* @param exception
* @param request
* @return
*/
public static ApiResponse createUncheckedExceptionResponse(UnCheckedWebException exception, HttpServletRequest request) {
Builder builder = newBuilder();
if (null != exception) {
builder.status(exception.getExceptionCode());
builder.error(getMessage(String.valueOf(builder.getThis().status), request));
}
return builder.data(null).build();
}
/**
* 参数绑定错误响应
*
* @param exception 异常
* @return 响应
*/
public static ApiResponse createBindExceptionResponse(BindException exception, HttpServletRequest request) {
Builder builder = newBuilder();
if (null != exception && exception.hasErrors()) {
StringBuilder error = new StringBuilder("");
for (ObjectError objectError : exception.getAllErrors()) {
error.append(objectError.getDefaultMessage() + ";");
}
log.error(error.toString());
builder.status(ApiStatus.SYST_SERVICE_UNAVAILABLE);
builder.error(getMessage(String.valueOf(builder.getThis().status),request));
}
return builder.data(null).build();
}
private static String getMessage(String code,HttpServletRequest request){
RequestContext requestContext = new RequestContext(request);
return requestContext.getMessage(String.valueOf(code));
}
}
如果使用@AutoValue,上面的工作就要变一个样子了,@AutoValue对于类以及getter和setter的对象都需要定义成抽象方法:
@AutoValue
public abstract class TraceData {
public static Builder builder(){
return new AutoValue_TraceData.Builder();
}
/**
* Span id.
*
* @return Nullable Span id.
*/
@Nullable
public abstract SpanId getSpanId();
/**
* Indication of request should be sampled or not.
*
* @return Nullable Indication if request should be sampled or not.
*/
@Nullable
public abstract Boolean getSample();
@AutoValue.Builder
public interface Builder {
Builder spanId(@Nullable SpanId spanId);
Builder sample(@Nullable Boolean sample);
TraceData build();
}
}
@AutoValue.Builder 解决类的创建方式问题,这里只是一个补充,和lombok@Builder相似,对于需要集成和补充的类
对于@AutoValue生成的实现类,需要 mvn package编译一次 才能生效,所以编译之前会有找不到类的提示。
- 源代码如下
@AutoValue
public abstract class ClientTracer extends AnnotationSubmitter {
public static Builder builder() {
return new AutoValue_ClientTracer.Builder();
}
@Override
abstract ClientSpanAndEndpoint spanAndEndpoint();
abstract Random randomGenerator();
abstract Reporter<zipkin.Span> reporter();
abstract Sampler traceSampler();
@Override
abstract AnnotationSubmitter.Clock clock();
abstract boolean traceId128Bit();
@AutoValue.Builder
public abstract static class Builder {
public Builder state(ServerClientAndLocalSpanState state) {
return spanAndEndpoint(ClientSpanAndEndpoint.create(state));
}
abstract Builder spanAndEndpoint(ClientSpanAndEndpoint spanAndEndpoint);
/**
* Used to generate new trace/span ids.
*/
public abstract Builder randomGenerator(Random randomGenerator);
public abstract Builder reporter(Reporter<zipkin.Span> reporter);
/**
* @deprecated use {@link #reporter(Reporter)}
*/
@Deprecated
public final Builder spanCollector(SpanCollector spanCollector) {
return reporter(new SpanCollectorReporterAdapter(spanCollector));
}
public abstract Builder traceSampler(Sampler sampler);
public abstract Builder clock(AnnotationSubmitter.Clock clock);
abstract Builder traceId128Bit(boolean traceId128Bit);
public abstract ClientTracer build();
}
/**
* Sets 'client sent' event for current thread.
*/
public void setClientSent() {
submitStartAnnotation(Constants.CLIENT_SEND);
}
/**
* Like {@link #setClientSent()}, except you can log the network context of the destination.
*
* @param server represents the server (peer). Set {@link Endpoint#service_name} to
* "unknown" if unknown.
*/
public void setClientSent(Endpoint server) {
submitAddress(Constants.SERVER_ADDR, server);
submitStartAnnotation(Constants.CLIENT_SEND);
}
/**
* Like {@link #setClientSent()}, except you can log the network context of the destination.
*
* @param ipv4 ipv4 of the server as an int. Ex for 1.2.3.4, it would be (1 << 24) | (2 << 16) | (3 << 8) | 4
* @param port listen port the client is connecting to, or 0 if unknown
* @param serviceName lowercase {@link Endpoint#service_name name} of the service being called
* or null if unknown
*
* @deprecated use {@link #setClientSent(Endpoint)}
*/
@Deprecated
public void setClientSent(int ipv4, int port, @Nullable String serviceName) {
if (serviceName == null) serviceName = "unknown";
setClientSent(Endpoint.builder().ipv4(ipv4).port(port).serviceName(serviceName).build());
}
/**
* Sets the 'client received' event for current thread. This will also submit span because setting a client received
* event means this span is finished.
*/
public void setClientReceived() {
if (submitEndAnnotation(Constants.CLIENT_RECV, reporter())) {
spanAndEndpoint().state().setCurrentClientSpan(null);
}
}
/**
* Start a new span for a new client request that will be bound to current thread. The ClientTracer can decide to return
* <code>null</code> in case this request should not be traced (eg sampling).
*
* @param requestName Request name. Should be lowercase and not <code>null</code> or empty.
* @return Span id for new request or <code>null</code> in case we should not trace this new client request.
*/
public SpanId startNewSpan(String requestName) {
Boolean sample = spanAndEndpoint().state().sample();
if (Boolean.FALSE.equals(sample)) {
spanAndEndpoint().state().setCurrentClientSpan(null);
return null;
}
SpanId newSpanId = getNewSpanId();
if (sample == null) {
// No sample indication is present.
if (!traceSampler().isSampled(newSpanId.traceId)) {
spanAndEndpoint().state().setCurrentClientSpan(null);
return null;
}
}
Span newSpan = newSpanId.toSpan();
newSpan.setName(requestName);
spanAndEndpoint().state().setCurrentClientSpan(newSpan);
return newSpanId;
}
private SpanId getNewSpanId() {
Span parentSpan = spanAndEndpoint().state().getCurrentLocalSpan();
if (parentSpan == null) {
ServerSpan serverSpan = spanAndEndpoint().state().getCurrentServerSpan();
if (serverSpan != null) {
parentSpan = serverSpan.getSpan();
}
}
long newSpanId = randomGenerator().nextLong();
SpanId.Builder builder = SpanId.builder().spanId(newSpanId);
if (parentSpan == null) { // new trace
if (traceId128Bit()) builder.traceIdHigh(randomGenerator().nextLong());
return builder.build();
}
return builder.traceIdHigh(parentSpan.getTrace_id_high())
.traceId(parentSpan.getTrace_id())
.parentId(parentSpan.getId()).build();
}
ClientTracer() {
}
}
对应会生成的@AutoValue代码如下,调用代码并不直接接触该类,AutoValue_ClientTracer是默认生成的代码类的名称规范(前缀+抽象类)
@Generated("com.google.auto.value.processor.AutoValueProcessor")
final class AutoValue_ClientTracer extends ClientTracer {
private final SpanAndEndpoint.ClientSpanAndEndpoint spanAndEndpoint;
private final Random randomGenerator;
private final Reporter<Span> reporter;
private final Sampler traceSampler;
private final AnnotationSubmitter.Clock clock;
private final boolean traceId128Bit;
private AutoValue_ClientTracer(
SpanAndEndpoint.ClientSpanAndEndpoint spanAndEndpoint,
Random randomGenerator,
Reporter<Span> reporter,
Sampler traceSampler,
AnnotationSubmitter.Clock clock,
boolean traceId128Bit) {
if (spanAndEndpoint == null) {
throw new NullPointerException("Null spanAndEndpoint");
}
this.spanAndEndpoint = spanAndEndpoint;
if (randomGenerator == null) {
throw new NullPointerException("Null randomGenerator");
}
this.randomGenerator = randomGenerator;
if (reporter == null) {
throw new NullPointerException("Null reporter");
}
this.reporter = reporter;
if (traceSampler == null) {
throw new NullPointerException("Null traceSampler");
}
this.traceSampler = traceSampler;
if (clock == null) {
throw new NullPointerException("Null clock");
}
this.clock = clock;
this.traceId128Bit = traceId128Bit;
}
@Override
SpanAndEndpoint.ClientSpanAndEndpoint spanAndEndpoint() {
return spanAndEndpoint;
}
@Override
Random randomGenerator() {
return randomGenerator;
}
@Override
Reporter<Span> reporter() {
return reporter;
}
@Override
Sampler traceSampler() {
return traceSampler;
}
@Override
AnnotationSubmitter.Clock clock() {
return clock;
}
@Override
boolean traceId128Bit() {
return traceId128Bit;
}
@Override
public String toString() {
return "ClientTracer{"
+ "spanAndEndpoint=" + spanAndEndpoint + ", "
+ "randomGenerator=" + randomGenerator + ", "
+ "reporter=" + reporter + ", "
+ "traceSampler=" + traceSampler + ", "
+ "clock=" + clock + ", "
+ "traceId128Bit=" + traceId128Bit
+ "}";
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o instanceof ClientTracer) {
ClientTracer that = (ClientTracer) o;
return (this.spanAndEndpoint.equals(that.spanAndEndpoint()))
&& (this.randomGenerator.equals(that.randomGenerator()))
&& (this.reporter.equals(that.reporter()))
&& (this.traceSampler.equals(that.traceSampler()))
&& (this.clock.equals(that.clock()))
&& (this.traceId128Bit == that.traceId128Bit());
}
return false;
}
@Override
public int hashCode() {
int h = 1;
h *= 1000003;
h ^= spanAndEndpoint.hashCode();
h *= 1000003;
h ^= randomGenerator.hashCode();
h *= 1000003;
h ^= reporter.hashCode();
h *= 1000003;
h ^= traceSampler.hashCode();
h *= 1000003;
h ^= clock.hashCode();
h *= 1000003;
h ^= traceId128Bit ? 1231 : 1237;
return h;
}
static final class Builder extends ClientTracer.Builder {
private SpanAndEndpoint.ClientSpanAndEndpoint spanAndEndpoint;
private Random randomGenerator;
private Reporter<Span> reporter;
private Sampler traceSampler;
private AnnotationSubmitter.Clock clock;
private Boolean traceId128Bit;
Builder() {
}
Builder(ClientTracer source) {
this.spanAndEndpoint = source.spanAndEndpoint();
this.randomGenerator = source.randomGenerator();
this.reporter = source.reporter();
this.traceSampler = source.traceSampler();
this.clock = source.clock();
this.traceId128Bit = source.traceId128Bit();
}
@Override
public ClientTracer.Builder spanAndEndpoint(SpanAndEndpoint.ClientSpanAndEndpoint spanAndEndpoint) {
this.spanAndEndpoint = spanAndEndpoint;
return this;
}
@Override
public ClientTracer.Builder randomGenerator(Random randomGenerator) {
this.randomGenerator = randomGenerator;
return this;
}
@Override
public ClientTracer.Builder reporter(Reporter<Span> reporter) {
this.reporter = reporter;
return this;
}
@Override
public ClientTracer.Builder traceSampler(Sampler traceSampler) {
this.traceSampler = traceSampler;
return this;
}
@Override
public ClientTracer.Builder clock(AnnotationSubmitter.Clock clock) {
this.clock = clock;
return this;
}
@Override
public ClientTracer.Builder traceId128Bit(boolean traceId128Bit) {
this.traceId128Bit = traceId128Bit;
return this;
}
@Override
public ClientTracer build() {
String missing = "";
if (spanAndEndpoint == null) {
missing += " spanAndEndpoint";
}
if (randomGenerator == null) {
missing += " randomGenerator";
}
if (reporter == null) {
missing += " reporter";
}
if (traceSampler == null) {
missing += " traceSampler";
}
if (clock == null) {
missing += " clock";
}
if (traceId128Bit == null) {
missing += " traceId128Bit";
}
if (!missing.isEmpty()) {
throw new IllegalStateException("Missing required properties:" + missing);
}
return new AutoValue_ClientTracer(
this.spanAndEndpoint,
this.randomGenerator,
this.reporter,
this.traceSampler,
this.clock,
this.traceId128Bit);
}
}
}
尝鲜之旅就到这里了,平常使用中碰到更深入的地方不定期更新。