I'm trying to make a maven plugin that needs to use reflection. I want a project to run the plugin, and give it the full name of a class in the project, and the plugin will load it by reflection to get info from it.
There's something strange with the classloader though, because it can't find the class when I use
Class.forName("package.MyClass");
Looking at the "Guide to Maven Classloading", I can't quite figure out if my plugin's classloader, when being run in a different project, has access to that project's classes.
I'm sure there's a better way, but here's how I got it to work:
Add the following to the javadoc at the top of your mojo:
#requiresDependencyResolution runtime
Add a MavenProject parameter:
/**
* #parameter expression="${project}"
* #required
* #readonly
*/
private MavenProject project;
Then you can get the dependencies at runtime, and make your own classloader:
List runtimeClasspathElements = project.getRuntimeClasspathElements();
URL[] runtimeUrls = new URL[runtimeClasspathElements.size()];
for (int i = 0; i < runtimeClasspathElements.size(); i++) {
String element = (String) runtimeClasspathElements.get(i);
runtimeUrls[i] = new File(element).toURI().toURL();
}
URLClassLoader newLoader = new URLClassLoader(runtimeUrls,
Thread.currentThread().getContextClassLoader());
Then you can load your class using this new classloader:
Class bundle = newLoader.loadClass("package.MyClass");
You should consider using this to add the runtime class path elements to the current class realm. (You can use the PluginDescriptor to retrieve the class realm.
List<String> runtimeClasspathElements = project.getRuntimeClasspathElements();
ClassRealm realm = descriptor.getClassRealm();
for (String element : runtimeClasspathElements)
{
File elementFile = new File(element);
realm.addURL(elementFile.toURI().toURL());
}
This worked perfectly for me!
As Dave asked, here is the way to get the PluginDescriptor:
/**
* The plugin descriptor
*
* #parameter default-value="${descriptor}"
*/
private PluginDescriptor descriptor;
I ran across this exact issue, today. The above suggestions didn't work for me, thought I would submit my solution to the list. I used the HibernateExporter mojo source which can be viewed at: http://grepcode.com/file/repo1.maven.org/maven2/org.codehaus.mojo/hibernate3-maven-plugin/2.2/org/codehaus/mojo/hibernate3/HibernateExporterMojo.java?av=f
/**
* #parameter expression="${project}"
* #required
* #readonly
*/
private MavenProject project;
private ClassLoader getClassLoader() throws MojoExecutionException
{
try
{
List<String> classpathElements = project.getCompileClasspathElements();
classpathElements.add(project.getBuild().getOutputDirectory() );
classpathElements.add(project.getBuild().getTestOutputDirectory() );
URL urls[] = new URL[classpathElements.size()];
for ( int i = 0; i < classpathElements.size(); ++i )
{
urls[i] = new File( (String) classpathElements.get( i ) ).toURI().toURL();
}
return new URLClassLoader(urls, getClass().getClassLoader() );
}
catch (Exception e)//gotta catch em all
{
throw new MojoExecutionException("Couldn't create a classloader.", e);
}
}
public void execute() throws MojoExecutionException
{
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClassLoader());
//... your code here ...
}
Also make sure you are using the right MavenProject class. add this to your pom
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
<version>3.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.0.3</version>
</dependency>
This worked for me and maven3 to get dependencies into the plugin classpath.
The trick is to use #Component to inject the PluginDescriptor. Otherwise it will not be set up correctly.
#Component
private MavenProject project;
#Component
private PluginDescriptor descriptor;
private void addDependenciesToClasspath(String artifactId) {
for (Artifact artifact : project.getDependencyArtifacts()) {
if (artifact.getArtifactId().equals(artifactId)) {
try {
final URL url = artifact.getFile().toURI().toURL();
final ClassRealm realm = descriptor.getClassRealm();
realm.addURL(url);
}
catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
}
}
It's ok, we need to make our own classloaders
Custom Classloaders
Related
I am trying to develop a IntelliJ plugin which provides a Language Server with help of lsp4intellij by ballerina.
Thing is, i've got a special condition: The list of completion items should be editable in runtime.
But I've not found any way to communicate new completionItems to the LanguageServer process once its running.
My current idea is to add an action to the plugin which builds a new jar and then restarts the server with the new jar, using the Java Compiler API.
The problem with that is, i need to get the source code from the plugin project including the gradle dependencies accessable from the running plugin... any ideas?
If your requirement is to modify the completion items (coming from the language server) before displaying them in the IntelliJ UI, you can do that by implementing the LSP4IntelliJ's
LSPExtensionManager in your plugin.
Currently, we do not have proper documentation for the LSP4IntelliJ's extension points but you can refer to our Ballerina IntelliJ plugin as a reference implementation, where it has implemented Ballerina LSP Extension manager to override/modify completion items at the client runtime in here.
For those who might stumble upon this - it is indeed possible to change the amount of CompletionItems the LanguageServer can provide during runtime.
I simply edited the TextDocumentService.java (the library I used is LSP4J).
It works like this:
The main function of the LanguageServer needs to be started with an additional argument, which is the path to the config file in which you define the CompletionItems.
Being called from LSP4IntelliJ it would look like this:
String[] command = new String[]{"java", "-jar",
"path\\to\\LangServer.jar", "path\\to\\config.json"};
IntellijLanguageClient.addServerDefinition(new RawCommandServerDefinition("md,java", command));
The path String will then be passed through to the Constructor of your CustomTextDocumentServer.java, which will parse the config.json in a new Timer thread.
An Example:
public class CustomTextDocumentService implements TextDocumentService {
private List<CompletionItem> providedItems;
private String pathToConfig;
public CustomTextDocumentService(String pathToConfig) {
this.pathToConfig = pathToConfig;
Timer timer = new Timer();
timer.schedule(new ReloadCompletionItemsTask(), 0, 10000);
loadCompletionItems();
}
#Override
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(CompletionParams completionParams) {
return CompletableFuture.supplyAsync(() -> {
List<CompletionItem> completionItems;
completionItems = this.providedItems;
// Return the list of completion items.
return Either.forLeft(completionItems);
});
}
#Override
public void didOpen(DidOpenTextDocumentParams didOpenTextDocumentParams) {
}
#Override
public void didChange(DidChangeTextDocumentParams didChangeTextDocumentParams) {
}
#Override
public void didClose(DidCloseTextDocumentParams didCloseTextDocumentParams) {
}
#Override
public void didSave(DidSaveTextDocumentParams didSaveTextDocumentParams) {
}
private void loadCompletionItems() {
providedItems = new ArrayList<>();
CustomParser = new CustomParser(pathToConfig);
ArrayList<String> variables = customParser.getTheParsedItems();
for(String variable : variables) {
String itemTxt = "$" + variable + "$";
CompletionItem completionItem = new CompletionItem();
completionItem.setInsertText(itemTxt);
completionItem.setLabel(itemTxt);
completionItem.setKind(CompletionItemKind.Snippet);
completionItem.setDetail("CompletionItem");
providedItems.add(completionItem);
}
}
class ReloadCompletionItemsTask extends TimerTask {
#Override
public void run() {
loadCompletionItems();
}
}
}
My (gradle 1.10 and gradle plugin 0.8)-based android project consists of a big android-library that is a dependency for 3 different android-apps
In my library, I would love to be able to use a structure like this
if (BuildConfig.SOME_FLAG) {
callToBigLibraries()
}
as proguard would be able to reduce the size of the produced apk, based on the final value of SOME_FLAG
But I can't figure how to do it with gradle as :
* the BuildConfig produced by the library doesn't have the same package name than the app
* I have to import the BuildConfig with the library package in the library
* The apk of an apps includes the BuildConfig with the package of the app but not the one with the package of the library.
I tried without success to play with BuildTypes and stuff like
release {
// packageNameSuffix "library"
buildConfigField "boolean", "SOME_FLAG", "true"
}
debug {
//packageNameSuffix "library"
buildConfigField "boolean", "SOME_FLAG", "true"
}
What is the right way to builds a shared BuildConfig for my library and my apps whose flags will be overridden at build in the apps?
As a workaround, you can use this method, which uses reflection to get the field value from the app (not the library):
/**
* Gets a field from the project's BuildConfig. This is useful when, for example, flavors
* are used at the project level to set custom fields.
* #param context Used to find the correct file
* #param fieldName The name of the field-to-access
* #return The value of the field, or {#code null} if the field is not found.
*/
public static Object getBuildConfigValue(Context context, String fieldName) {
try {
Class<?> clazz = Class.forName(context.getPackageName() + ".BuildConfig");
Field field = clazz.getField(fieldName);
return field.get(null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
To get the DEBUG field, for example, just call this from your Activity:
boolean debug = (Boolean) getBuildConfigValue(this, "DEBUG");
I have also shared this solution on the AOSP Issue Tracker.
Update: With newer versions of the Android Gradle plugin publishNonDefault is deprecated and has no effect anymore. All variants are now published.
The following solution/workaround works for me. It was posted by some guy in the google issue tracker:
Try setting publishNonDefault to true in the library project:
android {
...
publishNonDefault true
...
}
And add the following dependencies to the app project that is using the library:
dependencies {
releaseCompile project(path: ':library', configuration: 'release')
debugCompile project(path: ':library', configuration: 'debug')
}
This way, the project that uses the library includes the correct build type of the library.
You can't do what you want, because BuildConfig.SOME_FLAG isn't going to get propagated properly to your library; build types themselves aren't propagated to libraries -- they're always built as RELEASE. This is bug https://code.google.com/p/android/issues/detail?id=52962
To work around it: if you have control over all of the library modules, you could make sure that all the code touched by callToBigLibraries() is in classes and packages that you can cleave off cleanly with ProGuard, then use reflection so that you can access them if they exist and degrade gracefully if they don't. You're essentially doing the same thing, but you're making the check at runtime instead of compile time, and it's a little harder.
Let me know if you're having trouble figuring out how to do this; I could provide a sample if you need it.
I use a static BuildConfigHelper class in both the app and the library, so that I can have the packages BuildConfig set as final static variables in my library.
In the application, place a class like this:
package com.yourbase;
import com.your.application.BuildConfig;
public final class BuildConfigHelper {
public static final boolean DEBUG = BuildConfig.DEBUG;
public static final String APPLICATION_ID = BuildConfig.APPLICATION_ID;
public static final String BUILD_TYPE = BuildConfig.BUILD_TYPE;
public static final String FLAVOR = BuildConfig.FLAVOR;
public static final int VERSION_CODE = BuildConfig.VERSION_CODE;
public static final String VERSION_NAME = BuildConfig.VERSION_NAME;
}
And in the library:
package com.your.library;
import android.support.annotation.Nullable;
import java.lang.reflect.Field;
public class BuildConfigHelper {
private static final String BUILD_CONFIG = "com.yourbase.BuildConfigHelper";
public static final boolean DEBUG = getDebug();
public static final String APPLICATION_ID = (String) getBuildConfigValue("APPLICATION_ID");
public static final String BUILD_TYPE = (String) getBuildConfigValue("BUILD_TYPE");
public static final String FLAVOR = (String) getBuildConfigValue("FLAVOR");
public static final int VERSION_CODE = getVersionCode();
public static final String VERSION_NAME = (String) getBuildConfigValue("VERSION_NAME");
private static boolean getDebug() {
Object o = getBuildConfigValue("DEBUG");
if (o != null && o instanceof Boolean) {
return (Boolean) o;
} else {
return false;
}
}
private static int getVersionCode() {
Object o = getBuildConfigValue("VERSION_CODE");
if (o != null && o instanceof Integer) {
return (Integer) o;
} else {
return Integer.MIN_VALUE;
}
}
#Nullable
private static Object getBuildConfigValue(String fieldName) {
try {
Class c = Class.forName(BUILD_CONFIG);
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
return f.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Then, anywhere in your library where you want to check BuildConfig.DEBUG, you can check BuildConfigHelper.DEBUG and access it from anywhere without a context, and the same for the other properties. I did it this way so that the library will work with all my applications, without needing to pass a context in or set the package name some other way, and the application class only needs the import line changed to suit when adding it into a new application
Edit: I'd just like to reiterate, that this is the easiest (and only one listed here) way to get the values to be assigned to final static variables in the library from all of your applications without needing a context or hard coding the package name somewhere, which is almost as good as having the values in the default library BuildConfig anyway, for the minimal upkeep of changing that import line in each application.
For the case where the applicationId is not the same as the package (i.e. multiple applicationIds per project) AND you want to access from a library project:
Use Gradle to store the base package in resources.
In main/AndroidManifest.xml:
android {
applicationId "com.company.myappbase"
// note: using ${applicationId} here will be exactly as above
// and so NOT necessarily the applicationId of the generated APK
resValue "string", "build_config_package", "${applicationId}"
}
In Java:
public static boolean getDebug(Context context) {
Object obj = getBuildConfigValue("DEBUG", context);
if (obj instanceof Boolean) {
return (Boolean) o;
} else {
return false;
}
}
private static Object getBuildConfigValue(String fieldName, Context context) {
int resId = context.getResources().getIdentifier("build_config_package", "string", context.getPackageName());
// try/catch blah blah
Class<?> clazz = Class.forName(context.getString(resId) + ".BuildConfig");
Field field = clazz.getField(fieldName);
return field.get(null);
}
use both
my build.gradle
// ...
productFlavors {
internal {
// applicationId "com.elevensein.sein.internal"
applicationIdSuffix ".internal"
resValue "string", "build_config_package", "com.elevensein.sein"
}
production {
applicationId "com.elevensein.sein"
}
}
I want to call like below
Boolean isDebug = (Boolean) BuildConfigUtils.getBuildConfigValue(context, "DEBUG");
BuildConfigUtils.java
public class BuildConfigUtils
{
public static Object getBuildConfigValue (Context context, String fieldName)
{
Class<?> buildConfigClass = resolveBuildConfigClass(context);
return getStaticFieldValue(buildConfigClass, fieldName);
}
public static Class<?> resolveBuildConfigClass (Context context)
{
int resId = context.getResources().getIdentifier("build_config_package",
"string",
context.getPackageName());
if (resId != 0)
{
// defined in build.gradle
return loadClass(context.getString(resId) + ".BuildConfig");
}
// not defined in build.gradle
// try packageName + ".BuildConfig"
return loadClass(context.getPackageName() + ".BuildConfig");
}
private static Class<?> loadClass (String className)
{
Log.i("BuildConfigUtils", "try class load : " + className);
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
private static Object getStaticFieldValue (Class<?> clazz, String fieldName)
{
try { return clazz.getField(fieldName).get(null); }
catch (NoSuchFieldException e) { e.printStackTrace(); }
catch (IllegalAccessException e) { e.printStackTrace(); }
return null;
}
}
For me this is the ONLY ONE AND ACCEPTABLE* SOLUTION TO determine the ANDROID APPLICATION BuildConfig.class:
// base entry point
// abstract application
// which defines the method to obtain the desired class
// the definition of the application is contained in the library
// that wants to access the method or in a superior library package
public abstract class BasApp extends android.app.Application {
/*
* GET BUILD CONFIG CLASS
*/
protected Class<?> getAppBuildConfigClass();
// HELPER METHOD TO CAST CONTEXT TO BASE APP
public static BaseApp getAs(android.content.Context context) {
BaseApp as = getAs(context, BaseApp.class);
return as;
}
// HELPER METHOD TO CAST CONTEXT TO SPECIFIC BASEpp INHERITED CLASS TYPE
public static <I extends BaseApp> I getAs(android.content.Context context, Class<I> forCLass) {
android.content.Context applicationContext = context != null ?context.getApplicationContext() : null;
return applicationContext != null && forCLass != null && forCLass.isAssignableFrom(applicationContext.getClass())
? (I) applicationContext
: null;
}
// STATIC HELPER TO GET BUILD CONFIG CLASS
public static Class<?> getAppBuildConfigClass(android.content.Context context) {
BaseApp as = getAs(context);
Class buildConfigClass = as != null
? as.getAppBuildConfigClass()
: null;
return buildConfigClass;
}
}
// FINAL APP WITH IMPLEMENTATION
// POINTING TO DESIRED CLASS
public class MyApp extends BaseApp {
#Override
protected Class<?> getAppBuildConfigClass() {
return somefinal.app.package.BuildConfig.class;
}
}
USAGE IN LIBRARY:
Class<?> buildConfigClass = BaseApp.getAppBuildConfigClass(Context);
if(buildConfigClass !- null) {
// do your job
}
*there are couple of things need to be watched out:
getApplicationContext() - could return a context which is not an App ContexWrapper implementation - see what Applicatio class extends & get to know of the possibilities of context wrapping
the class returned by final app could be loaded by different class loaders than those who will use it - depends of loader implementation and some principals typical (chierarchy, visibility) for loaders
everything depends on the implemmentation of as in this case simple DELEGATION!!! - the solution could be more sophisticetaded - i wanted only to show here the usage of DELEGATION pattern :)
** why i downwoted all of reflection based patterns because they all have weak points and they all in some certain conditions will fail:
Class.forName(className); - because of not speciified loader
context.getPackageName() + ".BuildConfig"
a) context.getPackageName() - "by default - else see b)" returns not package defined in manifest but application id (somtimes they both are the same), see how the manifest package property is used and its flow - at the end apt tool will replace it with applicaton id (see ComponentName class for example what the pkg stands for there)
b) context.getPackageName() - will return what the implementaio wants to :P
*** what to change in my solution to make it more flawless
replace class with its name that will drop the problems wchich could appear when many classes loaded with different loaders accessing / or are used to obtain a final result involving class (get to know what describes the equality between two classes (for a compiler at runtime) - in short a class equality defines not a self class but a pair which is constituted by the loader and the class. (some home work - try load a inner class with different loader and access it by outer class loaded with different loader) - it would turns out that we will get illegal access error :) even the inner class is in the same package has all modificators allowing access to it outer class :) compiler/linker "VM" treats them as two not related classes...
I had a number of tests, it ran perfectly, untill I write Global object:
#Override
public void onStart(Application app) {
Mails.plugin = app.plugin(MailerPlugin.class).email();
Mails.from = app.configuration().getString("smtp.from");
if (Mails.plugin != null) Logger.info("Mailer plugin successfully loaded");
if (Mails.from != null) Logger.info("Mail account is " + Mails.from);
}
Here I am loading plugin for email messages. Now when I try to run my fakeApplication with inMemoryDatabase I get a null pointer exception. Probably it is becouse fakeApplication don't use configuration file, and can't load configuration from this file. Please help me to sort out this problem.
Try adding custom config parameters in your FakeApplication:
Map<String, Object> additionalConfiguration = new HashMap<String, Object>();
additionalConfiguration.put("smtp.from", "foo#bar.com");
running(fakeApplication(additionalConfiguration), new Runnable() {
...
I find the solution , for this reason I create fake Global class (or you also can mock it):
class Global extends GlobalSettings{
}
and Then can pass it:
#BeforeClass
public static void startApp() {
app = Helpers.fakeApplication(new Global());
Helpers.start(app);
}
I am using Spring Data Redis with Jedis. I am trying to store a hash with key vc:${list_id}. I was able to successfully insert to redis. However, when I inspect the keys using the redis-cli, I don't see the key vc:501381. Instead I see \xac\xed\x00\x05t\x00\tvc:501381.
Why is this happening and how do I change this?
Ok, googled around for a while and found help at http://java.dzone.com/articles/spring-data-redis.
It happened because of Java serialization.
The key serializer for redisTemplate needs to be configured to StringRedisSerializer i.e. like this:
<bean
id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.server}"
p:port="${redis.port}"
p:use-pool="true"/>
<bean
id="stringRedisSerializer"
class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
/>
Now the key in redis is vc:501381.
Or like #niconic says, we can also set the default serializer itself to the string serializer as follows:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:defaultSerializer-ref="stringRedisSerializer"
/>
which means all our keys and values are strings. Notice however that this may not be preferable, since you may want your values to be not just strings.
If your value is a domain object, then you can use Jackson serializer and configure a serializer as mentioned here i.e. like this:
<bean id="userJsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer">
<constructor-arg type="java.lang.Class" value="com.mycompany.redis.domain.User"/>
</bean>
and configure your template as:
<bean
id="redisTemplate"
class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:valueSerialier-ref="userJsonRedisSerializer"
/>
It's a very old question, but my answer might be helpful for someone who got the same issue while working with Redis using Spring Boot. I was stuck on the same issue while storing hash type data in redis. I have written the required config file changes for the RedisTemplate.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = "com.redis")
public class AppCofiguration {
#Bean
JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConFactory = new JedisConnectionFactory();
jedisConFactory.setHostName("127.0.0.1");
jedisConFactory.setPort(6379);
return jedisConFactory;
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
final RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(jedisConnectionFactory());
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
// the following is not required
template.setHashValueSerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
return template;
}
}
If the data type is String then template.setHashValueSerializer(new StringRedisSerializer()); and template.setHashKeySerializer(new StringRedisSerializer()); are not required.
I know this question has been a while, but I did some research on this topic again recently, so I would like to share how this "semi-hashed" key is generated by going thru part of the spring source code here.
First of all, Spring leverages AOP to resolve annotations like #Cacheable, #CacheEvict or #CachePut etc. The advice class is CacheInterceptor from Spring-context dependency, which is a subclass of CacheAspectSupport (also from Spring-context). For the ease of this explanation, I would use #Cacheable as an example to go thru part of the source code here.
When the method annotated as #Cacheable is invoked, AOP would route it to this method protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) from CacheAspectSupport class, in which it would try to resolve this #Cacheable annotation. In turn, it leads to the invocation of this method public Cache getCache(String name) in the implementing CacheManager. For this explanation, the implementing CacheManage would be RedisCacheManager (from Spring-data-redis dependency).
If the cache was not hit, it will go ahead to create the cache. Below is the key methods from RedisCacheManager:
protected Cache getMissingCache(String name) {
return this.dynamic ? createCache(name) : null;
}
#SuppressWarnings("unchecked")
protected RedisCache createCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (usePrefix ? cachePrefix.prefix(cacheName) : null), redisOperations, expiration,
cacheNullValues);
}
Essentially, it will instantiate an RedisCache object. To do this, it requires 4 parameters, namely, cacheName, prefix (this is the key parameter with regards to answering this question), redisOperation (aka, the configured redisTemplate), expiration (default to 0) and cacheNullValues (default to false). The constructor below shows more details about RedisCache.
/**
* Constructs a new {#link RedisCache} instance.
*
* #param name cache name
* #param prefix must not be {#literal null} or empty.
* #param redisOperations
* #param expiration
* #param allowNullValues
* #since 1.8
*/
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
long expiration, boolean allowNullValues) {
super(allowNullValues);
Assert.hasText(name, "CacheName must not be null or empty!");
RedisSerializer<?> serializer = redisOperations.getValueSerializer() != null ? redisOperations.getValueSerializer()
: (RedisSerializer<?>) new JdkSerializationRedisSerializer();
this.cacheMetadata = new RedisCacheMetadata(name, prefix);
this.cacheMetadata.setDefaultExpiration(expiration);
this.redisOperations = redisOperations;
this.cacheValueAccessor = new CacheValueAccessor(serializer);
if (allowNullValues) {
if (redisOperations.getValueSerializer() instanceof StringRedisSerializer
|| redisOperations.getValueSerializer() instanceof GenericToStringSerializer
|| redisOperations.getValueSerializer() instanceof JacksonJsonRedisSerializer
|| redisOperations.getValueSerializer() instanceof Jackson2JsonRedisSerializer) {
throw new IllegalArgumentException(String.format(
"Redis does not allow keys with null value ¯\\_(ツ)_/¯. "
+ "The chosen %s does not support generic type handling and therefore cannot be used with allowNullValues enabled. "
+ "Please use a different RedisSerializer or disable null value support.",
ClassUtils.getShortName(redisOperations.getValueSerializer().getClass())));
}
}
}
So what the use of prefix in this RedisCache? --> As shown in the constructor about, it is used in this statement this.cacheMetadata = new RedisCacheMetadata(name, prefix);, and the constructor of RedisCacheMetadata below shows more details:
/**
* #param cacheName must not be {#literal null} or empty.
* #param keyPrefix can be {#literal null}.
*/
public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {
Assert.hasText(cacheName, "CacheName must not be null or empty!");
this.cacheName = cacheName;
this.keyPrefix = keyPrefix;
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// name of the set holding the keys
this.setOfKnownKeys = usesKeyPrefix() ? new byte[] {} : stringSerializer.serialize(cacheName + "~keys");
this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
}
At this point, we know that some prefix parameter has been set to RedisCacheMetadata, but how exactly is this prefix used to form the key in Redis (e.g.,\xac\xed\x00\x05t\x00\tvc:501381 as you mentioned)?
Basically, the CacheInterceptor will subsequently move forward to invoke a method private RedisCacheKey getRedisCacheKey(Object key) from the above-mentioned RedisCache object, which returns an instance of RedisCacheKey by utilizing the prefix from RedisCacheMetadata and keySerializer from RedisOperation.
private RedisCacheKey getRedisCacheKey(Object key) {
return new RedisCacheKey(key).usePrefix(this.cacheMetadata.getKeyPrefix())
.withKeySerializer(redisOperations.getKeySerializer());
}
By reaching this point, the "pre" advice of CacheInterceptor is completed, and it would go ahead to execute the actual method annotated by #Cacheable. And after completing the execution of the actual method, it will do the "post" advice of CacheInterceptor, which essentially put the result to RedisCache. Below is the method of putting the result to redis cache:
public void put(final Object key, final Object value) {
put(new RedisCacheElement(getRedisCacheKey(key), toStoreValue(value))
.expireAfter(cacheMetadata.getDefaultExpiration()));
}
/**
* Add the element by adding {#link RedisCacheElement#get()} at {#link RedisCacheElement#getKeyBytes()}. If the cache
* previously contained a mapping for this {#link RedisCacheElement#getKeyBytes()}, the old value is replaced by
* {#link RedisCacheElement#get()}.
*
* #param element must not be {#literal null}.
* #since 1.5
*/
public void put(RedisCacheElement element) {
Assert.notNull(element, "Element must not be null!");
redisOperations
.execute(new RedisCachePutCallback(new BinaryRedisCacheElement(element, cacheValueAccessor), cacheMetadata));
}
Within the RedisCachePutCallback object, its callback method doInRedis() actually invoke a method to form the actual key in redis, and the method name is getKeyBytes() from RedisCacheKey instance. Below shows the details of this method:
/**
* Get the {#link Byte} representation of the given key element using prefix if available.
*/
public byte[] getKeyBytes() {
byte[] rawKey = serializeKeyElement();
if (!hasPrefix()) {
return rawKey;
}
byte[] prefixedKey = Arrays.copyOf(prefix, prefix.length + rawKey.length);
System.arraycopy(rawKey, 0, prefixedKey, prefix.length, rawKey.length);
return prefixedKey;
}
As we can see in the getKeyBytes method, it utilizes both the raw key (vc:501381 in your case) and prefix key (\xac\xed\x00\x05t\x00\t in your case).
Use StringRedisTemplate to replace RedisTemplate.
By default, RedisTemplate uses Java serialization, StringRedisTemplate uses StringRedisSerializer.
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
</bean>
You have to serialize teh objects that you are sending it to redis. Below is the complete running example of it. It uses interface DomainObject as Serializable
Below are the steps
1) make your maven pom.xml with following jars
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.3.0.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
2) make your configuration xml as follows
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<bean id="jeidsConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="localhost" p:port="6379" p:password="" />
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connection-factory-ref="jeidsConnectionFactory" />
<bean id="imageRepository" class="com.self.common.api.poc.ImageRepository">
<property name="redisTemplate" ref="redisTemplate"/>
</bean>
</beans>
3) Make your classes as follows
package com.self.common.api.poc;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class RedisMainApp {
public static void main(String[] args) throws IOException {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("mvc-dispatcher-servlet.xml");
ImageRepository imageRepository = (ImageRepository) applicationContext.getBean("imageRepository");
BufferedImage img = ImageIO.read(new File("files/img/TestImage.png"));
BufferedImage newImg;
String imagestr;
imagestr = encodeToString(img, "png");
Image image1 = new Image("1", imagestr);
img = ImageIO.read(new File("files/img/TestImage2.png"));
imagestr = encodeToString(img, "png");
Image image2 = new Image("2", imagestr);
imageRepository.put(image1);
System.out.println(" Step 1 output : " + imageRepository.getObjects());
imageRepository.put(image2);
System.out.println(" Step 2 output : " + imageRepository.getObjects());
imageRepository.delete(image1);
System.out.println(" Step 3 output : " + imageRepository.getObjects());
}
/**
* Decode string to image
* #param imageString The string to decode
* #return decoded image
*/
public static BufferedImage decodeToImage(String imageString) {
BufferedImage image = null;
byte[] imageByte;
try {
BASE64Decoder decoder = new BASE64Decoder();
imageByte = decoder.decodeBuffer(imageString);
ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
image = ImageIO.read(bis);
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
return image;
}
/**
* Encode image to string
* #param image The image to encode
* #param type jpeg, bmp, ...
* #return encoded string
*/
public static String encodeToString(BufferedImage image, String type) {
String imageString = null;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try {
ImageIO.write(image, type, bos);
byte[] imageBytes = bos.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
imageString = encoder.encode(imageBytes);
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
return imageString;
}
}
package com.self.common.api.poc;
public class Image implements DomainObject {
public static final String OBJECT_KEY = "IMAGE";
public Image() {
}
public Image(String imageId, String imageAsStringBase64){
this.imageId = imageId;
this.imageAsStringBase64 = imageAsStringBase64;
}
private String imageId;
private String imageAsStringBase64;
public String getImageId() {
return imageId;
}
public void setImageId(String imageId) {
this.imageId = imageId;
}
public String getImageName() {
return imageAsStringBase64;
}
public void setImageName(String imageAsStringBase64) {
this.imageAsStringBase64 = imageAsStringBase64;
}
#Override
public String toString() {
return "User [id=" + imageAsStringBase64 + ", imageAsBase64String=" + imageAsStringBase64 + "]";
}
#Override
public String getKey() {
return getImageId();
}
#Override
public String getObjectKey() {
return OBJECT_KEY;
}
}
package com.self.common.api.poc;
import java.io.Serializable;
public interface DomainObject extends Serializable {
String getKey();
String getObjectKey();
}
package com.self.common.api.poc;
import java.util.List;
import com.self.common.api.poc.DomainObject;
public interface Repository<V extends DomainObject> {
void put(V obj);
V get(V key);
void delete(V key);
List<V> getObjects();
}
package com.self.common.api.poc;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import com.self.common.api.poc.DomainObject;
public class ImageRepository implements Repository<Image>{
#Autowired
private RedisTemplate<String,Image> redisTemplate;
public RedisTemplate<String,Image> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String,Image> redisTemplate) {
this.redisTemplate = redisTemplate;
}
#Override
public void put(Image image) {
redisTemplate.opsForHash()
.put(image.getObjectKey(), image.getKey(), image);
}
#Override
public void delete(Image key) {
redisTemplate.opsForHash().delete(key.getObjectKey(), key.getKey());
}
#Override
public Image get(Image key) {
return (Image) redisTemplate.opsForHash().get(key.getObjectKey(),
key.getKey());
}
#Override
public List<Image> getObjects() {
List<Image> users = new ArrayList<Image>();
for (Object user : redisTemplate.opsForHash().values(Image.OBJECT_KEY) ){
users.add((Image) user);
}
return users;
}
}
For more reference on sprinf jedis you can see http://www.javacodegeeks.com/2012/06/using-redis-with-spring.html
Sample Code is taken from http://javakart.blogspot.in/2012/12/spring-data-redis-hello-world-example.html
I just read some source code is from org.apache.cxf.common.logging.JDKBugHacks and also in
http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/catalina/core/JreMemoryLeakPreventionListener.java. In order to make my question clear not too broad. :)
I just ask one piece of code in them.
// Calling getPolicy retains a static reference to the context
// class loader.
try {
// Policy.getPolicy();
Class<?> policyClass = Class
.forName("javax.security.auth.Policy");
Method method = policyClass.getMethod("getPolicy");
method.invoke(null);
} catch (Throwable e) {
// ignore
}
But I didn't understand this comment. "Calling getPolicy retains a static reference to the context class loader". And they trying to use JDKBugHacks to work around it.
UPDATE
I overlooked the static block part. Here it is. This is the key. Actually it already has policy cached. So why cache contextClassLoader also? In comment, it claims #deprecated as of JDK version 1.4 -- Replaced by java.security.Policy.
I have double checked the code of java/security/Policy.java. It really removed the cached classloader. So my doubt is valid! :)
#Deprecated
public abstract class Policy {
private static Policy policy;
private static ClassLoader contextClassLoader;
static {
contextClassLoader = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return Thread.currentThread().getContextClassLoader();
}
});
};
I also add the getPolicy source code.
public static Policy getPolicy() {
java.lang.SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(new AuthPermission("getPolicy"));
return getPolicyNoCheck();
}
static Policy getPolicyNoCheck() {
if (policy == null) {
synchronized(Policy.class) {
if (policy == null) {
String policy_class = null;
policy_class = java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction<String>() {
public String run() {
return java.security.Security.getProperty
("auth.policy.provider");
}
});
if (policy_class == null) {
policy_class = "com.sun.security.auth.PolicyFile";
}
try {
final String finalClass = policy_class;
policy = java.security.AccessController.doPrivileged
(new java.security.PrivilegedExceptionAction<Policy>() {
public Policy run() throws ClassNotFoundException,
InstantiationException,
IllegalAccessException {
return (Policy) Class.forName
(finalClass,
true,
contextClassLoader).newInstance();
}
});
} catch (Exception e) {
throw new SecurityException
(sun.security.util.ResourcesMgr.getString
("unable to instantiate Subject-based policy"));
}
}
}
}
return policy;
}
Actually I dig deeper, I find some interesting thing. Someone report a bug to apache CXF about the org.apache.cxf.common.logging.JDKBugHacks for this piece code recently.
In order for disabling url caching, JDKBugHacks runs:
URL url = new URL("jar:file://dummy.jar!/");
URLConnection uConn = url.openConnection();
uConn.setDefaultUseCaches(false);
When having the java.protocol.handler.pkgs system property set, that can lead to deadlocks between the system classloader and the file protocol Handler in particular situations (for instance if the file protocol URLStreamHandler is a signleton).
Besides that, the code above is really there for the sake of setting defaultUseCaches to false only, so actually opening a connection can be avoided, to speed up the execution.
So the fix is
URL url = new URL("jar:file://dummy.jar!/");
URLConnection uConn = new URLConnection(url) {
#Override
public void connect() throws IOException {
// NOOP
}
};
uConn.setDefaultUseCaches(false);
It's normal that JDK or apache cxf to have some minor bugs. And normally they will fix it.
javax.security.auth.login.Configuration has the same issues with Policy but it's not Deprecated.
The Policy class in java 6 contains a static reference to a classloader that is initialized to the current threads context classloader on the first access to the class:
private static ClassLoader contextClassLoader;
static {
contextClassLoader =
(ClassLoader)java.security.AccessController.doPrivileged
(new java.security.PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
});
};
Tomcats lifecycle listener is making sure to to initialize this class from within a known environment where the context classloader is set to the system classloader. If this class was first accessed from within a webapp, it would retain a reference to the webapps classloader. This would prevent the webapps classes from getting garbage collected, creating a leak of perm gen space.