Skip to content

Commit

Permalink
feat: VarHandleReflectionInjector.
Browse files Browse the repository at this point in the history
  • Loading branch information
QwQ-dev committed Dec 23, 2024
1 parent 23d83fb commit 22b500a
Show file tree
Hide file tree
Showing 14 changed files with 313 additions and 13 deletions.
3 changes: 2 additions & 1 deletion annotation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,6 @@ We only need to get `AnnotationProcessingService` through dependency injection a
Let's focus on `annotationProcessingService.processAnnotations(basePackage, false, this.getClassLoader())`.

The first parameter is the package we need to scan.
The second is whether the processed class should be injected into the singleton mode by the Fairy framework. If it is false, it will be created through parameterless reflection.
The second is whether the processed class should be injected into the singleton mode by the Fairy framework. If it is
false, it will be created through parameterless reflection.
The third is the classloader that needs to be scanned.
8 changes: 5 additions & 3 deletions cache/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
### cache

The module is a caching solution that supports `Caffeine`'s `Cache` and `AsyncCache`,
as well as an in-memory `Redis` database implementation, offering functional programming support and automatic lock handling.
The module is a caching solution that supports `Caffeine`'s `Cache` and `AsyncCache`,
as well as an in-memory `Redis` database implementation, offering functional programming support and automatic lock
handling.

The original purpose of this module was to design a L1 cache for the `mongodb` module, but now it is **general-purpose**.
The original purpose of this module was to design a L1 cache for the `mongodb` module, but now it is **general-purpose
**.

### usage

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,28 @@
@UtilityClass
public final class CacheServiceFactory {
/**
* Creates a Redis cache service with the specified configuration.
* Creates a {@link RedisCacheService} with the specified configuration.
*
* @param config the Redis configuration
* @return a new Redis cache service instance
* @return a new {@link RedisCacheService} instance
* @see RedisCacheService
* @see RedisCacheServiceInterface
* @see Config
*/
public static RedisCacheServiceInterface createRedisCache(Config config) {
return new RedisCacheService(config);
}

/**
* Creates a Caffeine synchronous cache service with custom configuration.
* Creates a {@link CaffeineCacheService} with custom configuration.
*
* @param cache the Caffeine cache
* @param <K> the cache key type
* @param <V> the cache value type
* @return a new Caffeine cache service instance
* @return a new {@link CaffeineCacheService} instance
* @see CacheServiceInterface
* @see Cache
* @see CaffeineCacheService
*/
public static <K, V> CacheServiceInterface<Cache<K, V>, V> createCaffeineCache(Cache<K, V> cache) {
return new CaffeineCacheService<>(cache);
Expand All @@ -49,6 +55,9 @@ public static <K, V> CacheServiceInterface<Cache<K, V>, V> createCaffeineCache(C
* @param <K> the cache key type
* @param <V> the cache value type
* @return a new Caffeine cache service instance
* @see CacheServiceInterface
* @see Cache
* @see CaffeineCacheService
*/
public static <K, V> CacheServiceInterface<Cache<K, V>, V> createCaffeineCache() {
return new CaffeineCacheService<>();
Expand All @@ -61,6 +70,9 @@ public static <K, V> CacheServiceInterface<Cache<K, V>, V> createCaffeineCache()
* @param <K> the cache key type
* @param <V> the cache value type
* @return a new async Caffeine cache service instance
* @see CacheServiceInterface
* @see AsyncCache
* @see CaffeineAsyncCacheService
*/
public static <K, V> CacheServiceInterface<AsyncCache<K, V>, V> createCaffeineAsyncCache(AsyncCache<K, V> asyncCache) {
return new CaffeineAsyncCacheService<>(asyncCache);
Expand All @@ -72,6 +84,9 @@ public static <K, V> CacheServiceInterface<AsyncCache<K, V>, V> createCaffeineAs
* @param <K> the cache key type
* @param <V> the cache value type
* @return a new async Caffeine cache service instance
* @see CacheServiceInterface
* @see AsyncCache
* @see CaffeineAsyncCacheService
*/
public static <K, V> CacheServiceInterface<AsyncCache<K, V>, V> createCaffeineAsyncCache() {
return new CaffeineAsyncCacheService<>();
Expand Down
51 changes: 51 additions & 0 deletions commons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
### commons

This is a module full of good stuff that is useful in every way, so it's a bit of a mixed bag, but I'll always update
this document when there's new content.

### usage

```kotlin
// Dependencies
dependencies {
// commons module
compileOnly("me.qwqdev.library:commons:1.0-SNAPSHOT")
}
```

### [VarHandleReflectionInjector](src/main/java/me/qwqdev/library/commons/injector/VarHandleReflectionInjector.java)

This is an `injector`, and its main use is to be used
with [VarHandleAutoInjection](src/main/java/me/qwqdev/library/commons/injector/annotation/VarHandleAutoInjection.java).

Yes, just like its name, we don't have to write a bunch of ugly code to assign `VarHandle`, let it all disappear, Amen.

```java
public class Example {
public static void main(String[] args) {
Test test = new Test();

// we can use TField_HANDLE
Test.TField_HANDLE.set(test, 2);

// prints 2
System.out.println(test.getTField());
}
}
```

```java

@Getter
@Setter
public class Test {
private volatile int tField = 100;

@VarHandleAutoInjection(fieldName = "tField")
public static VarHandle TField_HANDLE;

static {
new VarHandleReflectionInjector().inject(Test.class);
}
}
```
2 changes: 2 additions & 0 deletions commons/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ fairy {

// Dependencies
dependencies {
// Annotation module
compileOnly(project(":annotation"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.qwqdev.library.commons;

import io.fairyproject.FairyLaunch;
import io.fairyproject.container.InjectableComponent;
import io.fairyproject.plugin.Plugin;

/**
* The type Commons launcher.
*
* @author qwq-dev
* @since 2024-12-23 18:32
*/
@FairyLaunch
@InjectableComponent
public class CommonsLauncher extends Plugin {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package me.qwqdev.library.commons.factory;

import lombok.experimental.UtilityClass;
import me.qwqdev.library.commons.injector.StaticInjectorInterface;
import me.qwqdev.library.commons.injector.VarHandleReflectionInjector;

/**
* Factory for creating injector instances.
*
* @author qwq-dev
* @since 2024-12-23 18:38
*/
@UtilityClass
public final class InjectorFactory {
/**
* Create a {@link VarHandleReflectionInjector} instance.
*
* @return a {@link VarHandleReflectionInjector} instance
* @see StaticInjectorInterface
* @see VarHandleReflectionInjector
*/
public static StaticInjectorInterface createVarHandleReflectionInjector() {
return new VarHandleReflectionInjector();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.qwqdev.library.commons.injector;

/**
* Interface for object injectors.
*
* @author qwq-dev
* @since 2024-12-23 16:44
*/
public interface ObjectInjectorInterface {
/**
* Process the given object.
*
* @param object given object
*/
void inject(Object object);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package me.qwqdev.library.commons.injector;

/**
* Interface for static injectors.
*
* @author qwq-dev
* @since 2024-12-23 17:07
*/
public interface StaticInjectorInterface {
/**
* Process the given class.
*
* @param clazz given class
*/
void inject(Class<?> clazz);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package me.qwqdev.library.commons.injector;

import me.qwqdev.library.commons.injector.annotation.VarHandleAutoInjection;

import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
* Utility class for injecting {@link VarHandle} instances into static fields annotated with {@link VarHandleAutoInjection}.
* This class automatically injects {@link VarHandle} instances into the static fields of a class based on the annotations
* present on those fields.
*
* <p>The injection is done via reflection, which allows this class to work without explicitly hardcoding the
* dependencies. However, this approach introduces some overhead and may not be suitable for high-performance use cases.
* </p>
*
* <p>The injection process works in two ways:
* <ul>
* <li><b>Default:</b> Uses {@link MethodHandles#lookup()} to locate the {@link VarHandle} for the specified static field.
* This is the most common use case when static methods aren't needed.</li>
* <li><b>Static Method:</b> If specified in the {@link VarHandleAutoInjection} annotation, a static method can be used
* to retrieve the {@link VarHandle} for the field. This allows for more customized handling of the {@link VarHandle}
* injection.</li>
* </ul>
* </p>
*
* <p>If using a static method for injection, the class containing the static method must be loadable by the current
* thread's context {@link ClassLoader}. The method should be accessible and match the required signature for retrieving
* the {@link VarHandle}.</p>
*
* <p>Due to the use of reflection, the fields will be made accessible even if they are private, ensuring that injection
* can occur regardless of visibility modifiers.</p>
*
* <p>This class implements the {@link StaticInjectorInterface} interface, which defines the contract for injecting
* dependencies (in this case, {@link VarHandle} instances) into static fields of a class.</p>
*
* @author qwq-dev
* @since 2024-12-23 16:15
*/
public class VarHandleReflectionInjector implements StaticInjectorInterface {
/**
* {@inheritDoc}
* <p>
* Injects {@link VarHandle} instances into static fields of the given class that are annotated with
* {@link VarHandleAutoInjection}. This method uses reflection to find the appropriate {@link VarHandle}
* for each annotated field and assigns it to the field.
*
* <p>The method performs the injection by either using the default method of
* {@link MethodHandles#privateLookupIn(Class, MethodHandles.Lookup)} to
* locate the {@link VarHandle} for the specified static field or by using a static method if defined in the
* annotation {@link VarHandleAutoInjection}.
*
* <p>The specified static method must contain two parameters: {@link String} and {@link Class}
*
* <p>If a static method is specified in the annotation, it will be called to retrieve the {@link VarHandle}
* for the field. The class containing the static method must be loadable by the current thread's context
* {@link ClassLoader}.</p>
*
* @param clazz the class into which {@link VarHandle} instances will be injected into its static fields
* @throws IllegalStateException if the injection fails due to reflection issues or invalid annotations
*/
@Override
public void inject(Class<?> clazz) {
Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {
VarHandleAutoInjection annotation = field.getAnnotation(VarHandleAutoInjection.class);

if (annotation == null) {
continue;
}

String targetFieldName = annotation.fieldName();
String staticMethodName = annotation.staticMethodName();
String staticMethodPackage = annotation.staticMethodPackage();
boolean useStaticMethod = !staticMethodName.isEmpty() && !staticMethodPackage.isEmpty();

try {
VarHandle varHandle;

if (useStaticMethod) {
Class<?> staticClass = Class.forName(staticMethodPackage);
Method method = staticClass.getDeclaredMethod(staticMethodName, String.class, Class.class);
method.setAccessible(true);
varHandle = (VarHandle) method.invoke(null, targetFieldName, field.getType());
} else {
varHandle = MethodHandles.privateLookupIn(clazz, MethodHandles.lookup())
.unreflectVarHandle(clazz.getDeclaredField(targetFieldName));
}

field.setAccessible(true);
field.set(null, varHandle);
} catch (Exception exception) {
throw new IllegalStateException("Failed to inject VarHandle for field: " + field.getName(), exception);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package me.qwqdev.library.commons.injector.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.invoke.VarHandle;

/**
* Annotation to mark a field for automatic {@link VarHandle} injection.
*
* @author qwq-dev
* @since 2024-12-23 16:15
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VarHandleAutoInjection {
/**
* The name of the target field for which the {@link VarHandle} is being generated.
*
* @return the field name
*/
String fieldName();

/**
* The name of the static method used to generate the {@link VarHandle}.
*
* <p>The specified static method must contain two parameters: {@link String} and {@link Class}
*
* @return the static method name, or an empty string if not specified
*/
String staticMethodName() default "";

/**
* The fully qualified name of the class containing the static method used to generate the {@link VarHandle}.
*
* <p>The class must be loadable by the current thread's context {@link ClassLoader}.
*
* @return the fully qualified class name, or an empty string if not specified
*/
String staticMethodPackage() default "";
}
Loading

0 comments on commit 22b500a

Please sign in to comment.