Skip to content

Commit fbdf186

Browse files
authored
Merge pull request #15800 from apache/fix/15374-codec-metaclass-registration-overhead
Reduce cached codec metaclass registration overhead
2 parents fcc8f25 + 19bcf52 commit fbdf186

8 files changed

Lines changed: 513 additions & 13 deletions

File tree

grails-codecs/src/main/groovy/org/grails/plugins/codecs/CodecsConfiguration.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ public class CodecsConfiguration {
3636

3737
@Bean("codecLookup")
3838
@Primary
39-
public CodecLookup codecLookup(GrailsApplication grailsApplication) throws Exception {
40-
final DefaultCodecLookup defaultCodecLookup = new DefaultCodecLookup(grailsApplication);
41-
defaultCodecLookup.reInitialize();
42-
return defaultCodecLookup;
39+
public CodecLookup codecLookup(GrailsApplication grailsApplication) {
40+
return new DefaultCodecLookup(grailsApplication);
4341
}
4442
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* https://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.grails.plugins.codecs
20+
21+
import grails.core.DefaultGrailsApplication
22+
import grails.core.GrailsApplication
23+
import org.grails.encoder.CodecLookup
24+
import org.springframework.context.annotation.AnnotationConfigApplicationContext
25+
import spock.lang.Specification
26+
27+
class CodecsConfigurationSpec extends Specification {
28+
29+
void 'registers codec artefacts during Spring context initialization'() {
30+
given:
31+
DefaultGrailsApplication grailsApplication = new DefaultGrailsApplication(HTMLCodec)
32+
grailsApplication.initialise()
33+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()
34+
context.beanFactory.registerSingleton(GrailsApplication.APPLICATION_ID, grailsApplication)
35+
context.register(CodecsConfiguration)
36+
37+
when:
38+
context.refresh()
39+
CodecLookup codecLookup = context.getBean(CodecLookup)
40+
41+
then:
42+
codecLookup.lookupEncoder('HTML') != null
43+
codecLookup.lookupEncoder('HTML').encode('<tag>') == '&lt;tag&gt;'
44+
45+
cleanup:
46+
context.close()
47+
}
48+
}

grails-encoder/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ dependencies {
3939
implementation platform(project(':grails-bom'))
4040

4141
api project(':grails-core')
42+
implementation 'com.github.ben-manes.caffeine:caffeine'
4243
api 'org.apache.groovy:groovy'
4344
api 'org.apache.groovy:groovy-json'
4445
api 'org.springframework:spring-web', {
@@ -63,4 +64,4 @@ dependencies {
6364
apply {
6465
from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
6566
from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
66-
}
67+
}

grails-encoder/src/main/groovy/org/grails/encoder/CodecMetaClassSupport.groovy

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
*/
1919
package org.grails.encoder
2020

21+
import java.util.concurrent.ConcurrentHashMap
22+
23+
import com.github.benmanes.caffeine.cache.Cache
24+
import com.github.benmanes.caffeine.cache.Caffeine
25+
2126
import groovy.transform.CompileStatic
27+
import groovy.transform.EqualsAndHashCode
2228
import org.codehaus.groovy.runtime.GStringImpl
2329
import org.codehaus.groovy.runtime.NullObject
2430

@@ -38,6 +44,9 @@ class CodecMetaClassSupport {
3844
static final Object[] EMPTY_ARGS = []
3945
static final String ENCODE_AS_PREFIX = 'encodeAs'
4046
static final String DECODE_PREFIX = 'decode'
47+
private static final Cache<CodecFactory, Set<MetaMethodRegistrationKey>> REGISTERED_META_METHODS = Caffeine.newBuilder()
48+
.weakKeys()
49+
.build()
4150

4251
/**
4352
* Adds "encodeAs*" and "decode*" metamethods for given codecClass
@@ -101,14 +110,15 @@ class CodecMetaClassSupport {
101110
}
102111
}
103112

104-
addMetaMethod(targetMetaClasses, encodeMethodName, encoderClosure)
113+
Set<MetaMethodRegistrationKey> registeredMetaMethodKeys = cacheLookup ? registeredMetaMethodKeys(codecFactory) : null
114+
registerMetaMethod(targetMetaClasses, encodeMethodName, encoderClosure, cacheLookup, registeredMetaMethodKeys)
105115
if (codecFactory.encoder) {
106-
addAliasMetaMethods(targetMetaClasses, codecFactory.encoder.codecIdentifier.codecAliases, encodeMethodNameClosure, encoderClosure)
116+
addAliasMetaMethods(targetMetaClasses, codecFactory.encoder.codecIdentifier.codecAliases, encodeMethodNameClosure, encoderClosure, cacheLookup, registeredMetaMethodKeys)
107117
}
108118

109-
addMetaMethod(targetMetaClasses, decodeMethodName, decoderClosure)
119+
registerMetaMethod(targetMetaClasses, decodeMethodName, decoderClosure, cacheLookup, registeredMetaMethodKeys)
110120
if (codecFactory.decoder) {
111-
addAliasMetaMethods(targetMetaClasses, codecFactory.decoder.codecIdentifier.codecAliases, decodeMethodNameClosure, decoderClosure)
121+
addAliasMetaMethods(targetMetaClasses, codecFactory.decoder.codecIdentifier.codecAliases, decodeMethodNameClosure, decoderClosure, cacheLookup, registeredMetaMethodKeys)
112122
}
113123
}
114124

@@ -129,13 +139,14 @@ class CodecMetaClassSupport {
129139
}
130140

131141
@CompileStatic
132-
private addAliasMetaMethods(List<ExpandoMetaClass> targetMetaClasses, Set<String> aliases, Closure<String> methodNameClosure, Closure methodClosure) {
142+
private addAliasMetaMethods(List<ExpandoMetaClass> targetMetaClasses, Set<String> aliases, Closure<String> methodNameClosure, Closure methodClosure,
143+
boolean cacheLookup, Set<MetaMethodRegistrationKey> registeredMetaMethodKeys) {
133144
aliases?.each { String aliasName ->
134-
addMetaMethod(targetMetaClasses, methodNameClosure(aliasName), methodClosure)
145+
registerMetaMethod(targetMetaClasses, methodNameClosure(aliasName), methodClosure, cacheLookup, registeredMetaMethodKeys)
135146
}
136147
}
137148

138-
private String resolveCodecName(CodecFactory codecFactory) {
149+
private static String resolveCodecName(CodecFactory codecFactory) {
139150
codecFactory.encoder?.codecIdentifier?.codecName ?: codecFactory.decoder?.codecIdentifier?.codecName
140151
}
141152

@@ -156,4 +167,53 @@ class CodecMetaClassSupport {
156167
emc."${methodName}" << closure
157168
}
158169
}
170+
171+
private void registerMetaMethod(List<ExpandoMetaClass> targetMetaClasses, String methodName, Closure closure, boolean cacheLookup,
172+
Set<MetaMethodRegistrationKey> registeredMetaMethodKeys) {
173+
if (!cacheLookup) {
174+
addMetaMethod(targetMetaClasses, methodName, closure)
175+
return
176+
}
177+
178+
synchronized (registeredMetaMethodKeys) {
179+
List<ExpandoMetaClass> metaClassesToRegister = targetMetaClasses.findAll { ExpandoMetaClass emc ->
180+
shouldRegisterMetaMethod(emc, methodName, registeredMetaMethodKeys)
181+
}
182+
if (metaClassesToRegister) {
183+
addMetaMethod(metaClassesToRegister, methodName, closure)
184+
}
185+
}
186+
}
187+
188+
@CompileStatic
189+
private static boolean shouldRegisterMetaMethod(ExpandoMetaClass emc, String methodName, Set<MetaMethodRegistrationKey> registeredMetaMethodKeys) {
190+
MetaMethodRegistrationKey key = registrationKey(emc, methodName)
191+
registeredMetaMethodKeys.add(key) || emc.getMetaMethod(methodName, EMPTY_ARGS) == null
192+
}
193+
194+
@CompileStatic
195+
private static MetaMethodRegistrationKey registrationKey(ExpandoMetaClass emc, String methodName) {
196+
new MetaMethodRegistrationKey(emc.getTheClass(), methodName)
197+
}
198+
199+
@CompileStatic
200+
private static Set<MetaMethodRegistrationKey> registeredMetaMethodKeys(CodecFactory codecFactory) {
201+
REGISTERED_META_METHODS.get(codecFactory) { CodecFactory ignored ->
202+
Collections.newSetFromMap(new ConcurrentHashMap<MetaMethodRegistrationKey, Boolean>())
203+
}
204+
}
205+
206+
@CompileStatic
207+
@EqualsAndHashCode(includeFields = true)
208+
private static class MetaMethodRegistrationKey {
209+
210+
private final Class<?> targetClass
211+
private final String methodName
212+
213+
MetaMethodRegistrationKey(Class<?> targetClass, String methodName) {
214+
this.targetClass = targetClass
215+
this.methodName = methodName
216+
}
217+
218+
}
159219
}

0 commit comments

Comments
 (0)