code generating beans in java

74
Bean generation Stop writing getters and setters Stephen Colebourne, @jodastephen Engineering Lead, OpenGamma September 2016 http://blog.joda.org

Upload: stephen-colebourne

Post on 07-Apr-2017

745 views

Category:

Software


2 download

TRANSCRIPT

Bean generationStop writing getters and settersStephen Colebourne, @jodastephenEngineering Lead, OpenGammaSeptember 2016http://blog.joda.org

Stephen Colebourne● Java Champion, regular conference speaker● Best known for date & time - Joda-Time and JSR-310● More Joda projects - http://www.joda.org ● Major contributions in Apache Commons● Blog - http://blog.joda.org ● Worked at OpenGamma for 6 years

Strata, from OpenGamma● Open Source market risk library● Valuation and risk calcs for finance

○ interest rate swap, FRA, CDS

● Great example of Java SE 8 coding stylehttp://strata.opengamma.io/

Introduction

Why use beans?● Beans are used in most applications● Common denominator between applications & libraries● ORMs (Hibernate, JPA, etc.)● Serialization (Binary, JSON, XML, etc.)● Mappers/configuration (Spring, Dozer, etc.)

What is a bean?● JavaBean specification v1.01, from 1997● Focus on software components, COM/DCOM● Manipulated visually in GUIs● Java components within MS Word/Excel !!!● References to floppy disks !!!

What is a bean?● JavaBeans must extend java.awt.Component● Created via standard factories● No use of casts of instanceof checks● Checked exceptions, not unchecked● Communication via events● Very specific rules around capitalization● Use of BeanInfo and PropertyEditor

What is a bean?

"Bean" != JavaBean

What is a mutable bean?● Each tool has subtly different definition● Most agree on

○ no-args constructor○ getters match getXxx()○ setters match setXxx()○ equals() / hashCode() / toString()

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Pattern for mutable bean/** Represents a person. */

public class Person {

/** The forename of the person. */

private String forename;

/** The surname of the person. */

private String surname;

/** The birth date of the person. */

private LocalDate birthDate;

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Pattern for mutable bean /** Creates an empty instance of Person. */

public Person() {}

/** Gets the forename of the person. */

public String getForename() { … }

/** Sets the forename of the person. */

public void setForename(String forename) { … }

// same for surname/birthDate

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Pattern for mutable bean /** Compares this person to another. */

public boolean equals(Object obj) { … }

/** Returns a suitable hash code. */

public int hashCode() { … }

/** Returns a string summary of this object. */

public String toString() { … }

What is an immutable bean?● "Beans" has traditionally implied mutability● Many tools can't handle immutable beans● No setters, may have "withers"● Class must be final, with final fields● Factory or builder instead of constructor

Why immutable?● Thread-safe● Java Memory Model guarantees● No need to trust other methods not to modify● State checked and valid on construction● Nulls can be eliminated

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Pattern for immutable bean/** Represents a person. */

public final class Person {

/** The forename of the person. */

private final String forename;

/** The surname of the person. */

private final String surname;

/** The birth date of the person. */

private final LocalDate birthDate;

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Pattern for immutable bean /** Obtains an instance of Person. */

public Person of(

String surname, String forename, LocalDate date) { … }

/** Gets the forename of the person. */

public String getForename() { … }

/** Returns a copy with the specified forename. */

public Person withForename(String forename) { … }

// same for surname/birthDate

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Pattern for immutable bean /** Compares this person to another. */

public boolean equals(Object obj) { … }

/** Returns a suitable hash code. */

public int hashCode() { … }

/** Returns a string summary of this object. */

public String toString() { … }

Pattern for immutable bean● May prefer to have a builder instead of a factory

// factory

Person person = Person.of("Stephen", "Colebourne", date);

// or builder

Person person = Person.builder()

.forename("Stephen")

.surname("Colebourne")

.birthDate(date)

.build();

What is a VALJO?● POJO - Plain Old Java Object● VALJO - Value Java Object

○ http://blog.joda.org/2014/03/valjos-value-java-objects.html

● No use of identity - equal is interchangeable● Immutable bean with extra restrictions

○ may be suitable for conversion to value types in Java 10/11○ logical state clearly defined and used in equals()/hashCode()○ comparable consistent with equals()○ constructor must be private○ formal string representation that can be parsed

Creating beans

How to create beans● Lots of different ways to create beans● Best option depends on use case● Mutable vs Immutable

Option 1 - Manual● Write each bean manually● Deathly boring● Double deathly boring for immutable beans● Error-prone● Probably not tested or code reviewed

Option 2 - Another JVM language● Kotlin code much shorter

// Kotlin immutable bean

data class Person(

val forename: String,

val surname: String,

val birthDate: LocalDate)

Option 2 - Another JVM language● Groovy code much shorter

// Groovy mutable bean

class Customer {

String forename

String surname

LocalDate birthDate

Option 3 - Language change● New language feature in Java● Perhaps like Kotlin/Groovy● Doesn't help us now● Likely to be restrictive

Option 4 - IDE generation● Eclipse / IntelliJ / NetBeans can generate the code

○ Eclipse uses Ctrl+Alt+S followed by R / O / H / S○ IntelliJ uses Alt+Insert

● One time generation, doesn't handle change● Can you express field is not null?● Can you generate immutable builders?● Still not tested or code reviewed

Option 5 - Use a tool● AutoValue● Immutables● Lombok● Joda-Beans

● (VALJOGen)● (POJOmatic)

AutoValue

AutoValue● Annotation processor● Open Source, from Google

○ https://github.com/google/auto/tree/master/value

Annotation processing● Additional step during compilation● Compiler calls annotation processor● Processor generates additional files● If generated file is a .java file then it is compiled

Annotation processing

Person(abstract)

PersonImpl(concrete)

Compilation findsannotations andcode generates

You writeabstract class

Annotation processing● Maven

○ just add the dependency

● Eclipse with Maven○ install m2e-apt plugin○ turn it on in the preferences

● IntelliJ with Maven○ turn it on in the preferences

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

AutoValue: your code@AutoValuepublic abstract class Person {

public static Person of(String name, LocalDate date) {

return new AutoValue_Person(name, date); }

public abstract String getName();

public abstract LocalDate getBirthDate();

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

AutoValue: generated@Generated("com.google.auto.value.processor.AutoValueProcessor")

final class AutoValue_Person extends Person {

private final String name;

private final LocalDate birthDate;

// constructor, getters, equals, hashCode, toString

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

AutoValue builder: your code@AutoValuepublic abstract class Person {

public static Builder builder() {

return new AutoValue_Person.Builder(); }

@AutoValue.Builder

public static abstract class Builder {

public abstract Builder name(String name);

public abstract Builder birthDate(LocalDate date);

public abstract Person build(); } public abstract Builder toBuilder();

AutoValue options● Handles name() or getName() convention● Can override (underride) equals/hashCode/toString● Can add any other method● Can change fields to nullable using @Nullable● Supports memoized fields● Builder can have sub-builders for collections● Pattern to handle builder validation● Extensions API, but undocumented

AutoValue pros/cons● Callers use Person like a normal bean

○ but they can see class is abstract

● Generated code is package scoped○ you must write outline builder and/or static factory method○ quite a lot of code still to write

● Simple, does sensible thing for most use cases○ not that many options

● Only for immutable beans

Immutables

Immutables● Annotation processor● Open Source

○ https://immutables.github.io/

● Reacts to use Guava if available

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Immutables: your [email protected] interface Person {

String getName();

LocalDate getBirthDate();

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Immutables: generated@SuppressWarnings("all")

@Generated({"Immutables.generator", "ImmutablePerson"})

public final class ImmutablePerson implements Person {

private final String name;

private final LocalDate birthDate;

private ImmutablePerson(String name, LocalDate birthDate) {

this.name = name;

this.birthDate = birthDate;

}

// getters, withers, equals, hashCode, toString, builder

Immutables options● Can generate from abstract class or interface● Handles name() or getName() convention● Can override (underride) equals/hashCode/toString● Can add any other method● Pre-computed hash code● Instance interning● and lots more!

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Immutables: hide [email protected]@Value.Style(visibility = PACKAGE)public abstract class Person extends WithPerson {

public String getName();

public LocalDate getBirthDate();

public static class Builder

implements ImmutablePerson.Builder {}

}

Immutables pros/cons● Many options and ways to generate

○ takes time to choose best option

● Callers see and use generated class (by default)○ hiding generated class possible if you write more code

● Mutable beans supported○ more like builders, do not follow JavaBeans spec

Lombok

Lombok● Internal APIs● Open Source

○ https://projectlombok.org/

● Uses agents and annotation processors● Works best with Eclipse, but requires installing

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Lombok: your code - mutable@Datapublic class Person {

private String name;

private LocalDate birthDate;

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Lombok: generated- mutable@Datapublic class Person {

private String name;

private LocalDate birthDate;

// constructor, getters, setters, equals, hashCode, toString

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Lombok: your code - immutable@Valuepublic class Person {

private String name;

private LocalDate birthDate;

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Lombok: generated - immutable@Valuepublic final class Person {

private final String name;

private final LocalDate birthDate;

// constructor, getters, equals, hashCode, toString, builder

}

Lombok options● Over 15 annotations and tweaks● Use other annotations for more control● Can add any other method

Lombok pros/cons● No second class, proper immutable bean

○ resulting bean is exactly the same as manually written

● Works best with Maven and Eclipse○ installation in Eclipse not via a standard plugin

● Uses internal API hackery○ higher risk option

● Generated code is invisible○ cannot step in when debugging○ can "delombok" to code generated code

Joda-Beans

Joda-Beans● Same-file regenerator● Open Source, by me @jodastephen

○ http://www.joda.org/joda-beans/

● Adds autogenerated block to your code● Generation using Maven/Gradle, on-save in Eclipse

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Joda-Beans: your code - mutable@BeanDefinitionpublic class Person implements Bean {

@PropertyDefinition

private String name;

@PropertyDefinition

private LocalDate birthDate;

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Joda-Beans: generated - mutable@BeanDefinitionpublic class Person implements Bean {

@PropertyDefinition

private String name;

@PropertyDefinition

private LocalDate birthDate; // ---- AUTOGENERATED START ----

// getters, setters, equals, hashCode, toString, properties

// ----- AUTOGENERATED END -----

}

Properties● C# and most other languages have properties● Higher level than a field● Bean is a set of properties● Can list, get and set properties

○ like reflection

● Very useful abstraction for frameworks

Joda-Beans properties● Bean provides abstraction for properties● MetaBean acts like Class● Can loop over MetaProperty for each property

○ meta bean acts as a hash-map of property name to property○ no reflection

● BeanBuilder allows a bean to be created○ this allows immutable beans to be created one property at a time

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Joda-Beans: your code immutable@BeanDefinitionpublic class Person implements ImmutableBean {

@PropertyDefinition(validate = "notNull")

private String name;

@PropertyDefinition(validate = "notNull")

private LocalDate birthDate;

}

// Java 7List<Person> people = loadPeople();Collections.sort(people, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.name.compareTo(p2.name); }});

Joda-Beans: generated immutable@BeanDefinitionpublic class Person implements ImmutableBean {

@PropertyDefinition(validate = "notNull")

private String name;

@PropertyDefinition(validate = "notNull")

private LocalDate birthDate; // ---- AUTOGENERATED START ----

// getters, equals, hashCode, toString, properties, builder

// ----- AUTOGENERATED END -----

}

Joda-Beans options● Annotations allow field-level and bean-level control● Control scope/style for getters/setters● Supports pragmatic optional usage● Can add any validation code● Defaults/cross-validation for immutable bean builders● Can default one property from another in builder● Can fully override constructor

Joda-Beans pros/cons● No second class, proper immutable bean

○ resulting bean is same as manually written

● Adds abstraction for properties without reflection○ key reason to use Joda-Beans, but requires runtime dependency

● Built in XML, JSON, Binary serialization● Generated code is visible for debugging

○ more code to checkin, but easy to ignore in review○ proper Javadoc

● Plugin for Maven, Gradle and Eclipse with M2E○ anyone want to volunteer to write an IntelliJ one?

Comparisons

Comparisons● Annotation processor

○ AutoValue○ Immutables

● Internal APIs○ Lombok

● Same file regenerator○ Joda-Beans

Comparisons● All can generate a lot of useful code

○ getters, factories, builders, equals, hashCode, toString

● Some can generate mutable beans● Only Joda-Beans generates C# style properties● Each project has a different trade-off

Same class vs Generated class● Same class (Joda-Beans, Lombok)

○ your code involves writing fields○ caller sees concrete class○ properly immutable

● Generated class (AutoValue, Immutables)○ your code involves writing methods (more code)○ caller sees abstract class or interface○ is it really immutable?○ IDE rename class typically breaks code

Collections● Immutable beans should use Guava ImmutableList● Builder needs to take List and convert internally● All except Lombok do this correctly

Installation requirements● AutoValue & Immutables use annotation processor

○ must be configured in IDE, extra plugin for Eclipse

● Lombok uses internal API hackery○ requires specific Eclipse installation

● Joda-Beans runs as separate code regenerator○ for Eclipse, just needs standard M2E

AutoValue vs Immutables● AutoValue

○ you write abstract class○ generated class is package-scoped ○ cannot generate static factory○ must write builder outline manually

● Immutables○ you write interface or abstract class○ generated class is public (can be made private or package-scoped)○ static factory and builder in generated class○ lots of flexibility

Valid on checkout● AutoValue, Immutables, Lombok

○ code invalid in IDE on checkout unless configured○ only your code checked in

● Joda-Beans○ code valid in IDE on checkout, even if not configured○ your code and generated code checked in

Evaluate yourself● GitHub project with everything setup for comparison

○ https://github.com/jodastephen/compare-beangen

● Includes 4 tools discussed○ plus VALJOGen, POJOmatic, Eclipse wizards and IntelliJ wizards

● Good project to play with each tool

Summary

Summary● All four tools have their sweet spot and trade off

○ AutoValue - simplicity, abstract class○ Immutables - comprehensive, abstract class or interface○ Lombok - almost a language extension, hacky implementation○ Joda-Beans - C# style properties, runtime dependency

Summary - personal view● Use Joda-Beans if you like properties

○ Code should be valid on checkout○ Immutable beans should be final○ Want C# style properties○ Hence I wrote and use Joda-Beans

● Otherwise, use Immutables

@jodastephenhttp://blog.joda.org