Upload
burt-beckwith
View
796
Download
4
Tags:
Embed Size (px)
Citation preview
Little Did He Know ...
Burt Beckwith
Did you say 'Little did he know ...'?
Dear god, I've written papers on 'Little did he know …'
I used to teach a class based on 'Little did he know ...'
I mean, I once gave an entire seminar on 'Little did he know ...'
The Groovy “Map” Constructor
● Not a real constructor (i.e. it's not in the bytecode)
● Implemented in
MetaClassImpl.invokeConstructor() or
o.c.g.runtime.callsite.ConstructorSite.
callConstructor()
The Groovy “Map” Constructor
● Not a real constructor (i.e. it's not in the bytecode)
● Implemented in
MetaClassImpl.invokeConstructor() or
o.c.g.runtime.callsite.ConstructorSite.
callConstructor()
● Calls the default constructor, then calls
MetaClassImpl.setProperty() for each map
key/value pair to invoke setters
The Groovy “Map” Constructor
● Depends on an existing default (no-arg) constructor,
which is always created by javac/groovyc if
there are no constructors in the code
The Groovy “Map” Constructor
● Depends on an existing default (no-arg) constructor,
which is always created by javac/groovyc if
there are no constructors in the code
● It's a bit more complicated in Grails domain classes
The Groovy “Map” Constructor
● Depends on an existing default (no-arg) constructor,
which is always created by javac/groovyc if
there are no constructors in the code
● It's a bit more complicated in Grails domain classes
● Grails uses an AST transformation to add a default
constructor to support dependency injection
The Groovy “Map” Constructor
● So what if I want to add a parameterized
constructor in a domain class?
package little
class ConstructedDomain { String name}
The Groovy “Map” Constructor
● So what if I want to add a parameterized
constructor in a domain class?
package little
class ConstructedDomain { String name
ConstructedDomain(String name) { this.name = name }}
The Groovy “Map” Constructor
● So what if I want to add a parameterized
constructor in a domain class?
package little
class ConstructedDomain { String name
ConstructedDomain(String name) { this.name = name }
ConstructedDomain() { // default }}
Need to explicitlyadd a no-arg
constructor forthe Map
constructor, right?
The Groovy “Map” Constructor
● So what if I want to add a parameterized
constructor in a domain class?
package little
class ConstructedDomain { String name
ConstructedDomain(String name) { this() this.name = name }}
Not in domainclasses - the AST
adds a no-argconstructor!
So call this() toretain DI
new Date().getTime()
● Please stop using new Date().getTime() to get the
current time in milliseconds, e.g.
long start = new Date().getTime()// do some timed stufflong end = new Date().getTime()long timeDelta = end - start
new Date().getTime()
● This isn't particularly expensive - creating and discarding
a Date instance is lightweight, but it's best to use
System.currentTimeMillis() (which is where Date
gets its value btw):
long start = System.currentTimeMillis()// do some timed stufflong end = System.currentTimeMillis()long timeDelta = end - start
fooId
● If you have a many-to-one in a domain class, e.g.
there is an authorId dynamic property that can be
used to access the id of the Author instance without
retrieving the instance
class Book { String title Date published Author author}
fooId● Run this with SQL logging enabled:
new Author(name: 'a1').save()new Author(name: 'a2').save()new Book(author: Author.load(2), published: new Date(), title: 'b1').save()
Book.withSession {it.flush()it.clear()
}
def b = Book.get(1)println b.titleprintln b.publishedprintln b.authorIdprintln "calling b.author.id"println b.author.id
Primitives in domain classes
● Think before using primitive types (boolean, int,
long, etc.)
● They are initialized to 0 for numbers and false for
boolean
● This means that it can be difficult to know if a
user chose that value, or if they didn't make a
choice at all
Primitives in domain classes
● If the database column is nullable, expect
NullPointerExceptions
● Your business rules may consider null and 0 (or
false) to be equivalent, this is not a general
default – null means no value and 0 and false
are values
Primitives in domain classes
● Hibernate proactively tries to keep you from
shooting yourself in the foot and always forces
database columns for primitives to be not-null
● This is great, but you end up with an inconsistency
between your constraints and the database
● So use the wrapper classes (Integer, Long,
Boolean, etc.) to detect that no value was chosen
or to properly support nullable columns
findById vs get
● Both seem equivalent, but they are not
● get is a special query method to retrieve a single
instance by id, but findById is a dynamic finder
● They are cached very differently, and query caching
can be quite brittle – see
Hibernate query cache considered harmful?
● There doesn't appear to be any reason to ever use
findById over get
● But findByIdAndSomeOtherProperty can be
useful
License weirdness
● Very interesting Twitter thread about iText and Craig
Burke's Groovy Document Builder here
● It turns out that you cannot use MPL/LGPL libraries
in an ASL2-licenced project, which sounds crazy, but
it's very true:
● https://www.apache.org/legal/resolved.html#cate
gory-x
License weirdness
● “We avoid GPLv3 software because merely linking to
it is considered by the GPLv3 authors to create a
derivative work” -
https://www.apache.org/licenses/GPL-compatibility
.html● Bruno Lowagie (iText author): It's not even ok to use
pre-AGPL versions:
https://stackoverflow.com/questions/25696851/can-
itext-2-1-7-or-earlier-can-be-used-commercially
Log4j vs Slf4j
● Slf4j is an excellent logger API wrapper library
● It's a good idea especially in plugins to use Slf4j in
case the containing app switched to Logback/Log4j
2/etc.
● Unfortunately the type of the message in Slf4j
methods is String, whereas it's Object in Log4j
Log4j vs Slf4j
● Log4j 1.2 and Log4j 2 both accept Object
● Commons Logging also accepts Object
● Logback is similar to Slf4j and accepts String● Slf4j Javadoc
● Log4j 1.2 Javadoc
● Log4j 2 Javadoc
● Logback Javadoc
● Commons Logging Javadoc
Log4j vs Slf4j
● Why is that a problem? GStrings:
● log.debug(“The huge collection
contains ${things}”)
● In Log4j, if the logger level is higher than DEBUG,
the message isn't logged, and there's ~0 cost
● But in Slf4j, the GString is coerced to a String
(expensive for large embedded expressions) and
then discarded
Log4j vs Slf4j
● The solution is to use the Slf4j placeholder support:
● log.debug(“The huge collection
contains {}”, things)
Use “public” for constants
● The public keyword is usually unnecessary noise in
Groovy classes – it's the default scope
● But weirdness happens when you omit it for static
properties, e.g. constants
● static final String FOO = 'FOO'
● public static final String BAR = 'BAR'
Use “public” for constants
● Groovy auto-creates getters and setters for
properties, both instance and static (although no
setters for final properties)
● This means that you'll have a getFOO method:
private static final String FOO = "FOO";public static final String BAR = "BAR";
public static final String getFOO() {return FOO;
}
Hat tip to Ken Kousenfor originally pointing
this out
Domain class transient methods
● Is the transients property needed in this domain?
class Thing { String name
int getClickCount() { … }
static transients = ['clickCount']}
Domain class transient methods
● How about now?
class Thing { String name
void setClickCount(int count) { … }
static transients = ['clickCount']}
Domain class transient methods
● And now?
class Thing { String name
int getClickCount() { … } void setClickCount(int count) { … }
static transients = ['clickCount']}
Domain class transient methods
● It's only needed when there is a matched
getter/setter pair
● This is because the pair creates a property
● Recall that Groovy generates a getter and setter for
unscoped properties and converts the declared
property to a private field
Domain class transient methods
● So for this domain class (or any POGO)
If you decompile the .class file you'll see code like
class Thing { String name}
class Thing { private String name public String() getName() { this.name } public void setName(String s) { name = s}}
Domain class transient methods
● Hibernate knows nothing about Groovy, and Grails
doesn't auto-register declared properties as persistent –
Hibernate sees the getter and setter and considers it a
property
● So whether groovyc creates the getter/setter pair or you
do, either way it's a property
→ only add the property name to transients for the
third example
Custom application templates
● We all know that it's easy to customize artifact
templates by running grails install-
templates and editing the files under
src/templates
● But what about application templates
(BuildConfig.groovy, DataSource.groovy,
etc.)?
Custom application templates
● It's possible for Grails 2.3/2.4/2.5 – see
this StackOverflow answer for details
● Basically it involves extracting the jars from
dist/grails-resources-x.x.x.jar and
extracting the templates from those jars, editing,
then repackaging and storing in the correct
$HOME/.grails subfolder
Exceptions are very expensive
● Don't be careless about exceptions
● We've all seen Groovy stacktraces as tall as Mount
Everest – there's a nontrivial cost to fill in the stack
● Use them for exceptional cases
● When a user makes a mistake and there's a
validation problem, that's not exceptional – it's
entirely expected
Exceptions are very expensive
● This implies that save(failOnError:true) is a
really really bad idea except for rare cases
● This includes test data and creating instances in
BootStrap.groovy; in both cases you're hard-
coding values that you expect to be correct – use
failOnError only here, as a fail-safe
● In general do not use failOnError in app code
Exceptions are very expensive
● Consider failOnError vs. checking hasErrors():
def thing = new Thing(foo: 'bar')thing.save()if (thing.hasErrors()) { // handle errors}else { // handle success}
def thing = new Thing(foo: 'bar')try { thing.save(failOnError: true) // handle success}catch (ValidationException e) { // handle errors}
Pretty similar –can someone tellme the value of
failOnError?Munchausen by
Proxy Syndrome?
Exceptions are very expensive
● Let's stop sabotaging the performance of our
Groovy-based applications by throwing or triggering
dumb exceptions, ok?
● The Spring Security plugin has a no-stack Exception:
package grails.plugin.springsecurity.userdetails;
public class NoStackUsernameNotFoundException ... { ... @Override public synchronized Throwable fillInStackTrace() { // do nothing return this; }}
Exceptions are very expensive
● As of Java 7 you can also call this subclass
constructor from yours:
protected Exception(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace);}
Exceptions are very expensive
● This trick was in the comments of this very informative
blog post:
The hidden performance costs of instantiating Throwables
● Also see “The Exceptional Performance of Lil' Exception”
http://cuteoverload.files.wordpress.com/2014/03/cute-smiling-animals-251.jpg