Date post: | 17-Jul-2015 |
Category: |
Software |
Upload: | burt-beckwith |
View: | 796 times |
Download: | 4 times |
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