How to use byte-buddy in springboot - java

I want to use byte-buddy to intercept methods with java annotation. A simplified example is as follows:
Annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface TraceLog {
String logName() default "";
}
The Interceptor class is
public class TraceLogInterceptor {
#RuntimeType
public static Object handleTraceLog(#Origin Method method,
#AllArguments Object[] args,
#SuperCall Callable<?> superCall) throws Exception {
TraceLog traceLogAnnotation = method.getAnnotation(TraceLog.class);
System.out.println(traceLogAnnotation.logName());
superCall.call();
}
}
The service class is
public class HelloServiceImpl {
#TraceLog(logName = "myLog")
public String writeHello(String name) {
return "Hello " + name;
}
}
The test method is
#Test
public void writeHello() throws IllegalAccessException, InstantiationException, IOException, NoSuchMethodException, InvocationTargetException {
DynamicType.Unloaded<?> dynamicType =
new ByteBuddy()
.subclass(HelloServiceImpl.class)
.method(ElementMatchers.isAnnotatedWith(TraceLog.class))
.intercept(MethodDelegation.to(TraceLogInterceptor.class))
.make();
HelloServiceImpl helloService = (HelloServiceImpl) dynamicType.load(this.getClass().getClassLoader())
.getLoaded()
.getConstructor()
.newInstance();
System.out.println(helloService.writeHello("XXXXX"));
}
It can work. However, I want to know how to inject the interceptor(TraceLogInterceptor) to spring. And I also want to know how to intercept all methods with the #TraceLog annotation and generate the corresponding proxy class without specifying the superclass(new ByteBuddy().subclass(xxx)). Thanks!

Related

JUnit method.getAnnotations(CustomAnnotation.class) throwing NullPointerException

I am struggling to write unit test cases for aspect code. Please find the all the respective code.
Custom Annotation -
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface WriteAccessAuthorization{
boolean isAdmin() default false;
}
Aspect Code -
#Aspect
class AccessAspect {
...
...
boolean isAdminForWriteAccess(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
WriteAccessAuthorization writeAccessAuthorization =
method.getAnnotation(WriteAccessAuthorization.class);
return writeAccessAuthorization.isAdminPortal();
}
...
}
Here I am getting NPE in last line of the method.
Here method.getAnnotation() is returning null even we are mocking it in Junit test method.
Please find the junit test case code -
class AccessAspectTest {
#Mock private ProceedingJoinPoint joinPoint;
#Mock private MethodSignature methodSignature;
#Mock private Method method;
#Mock private WriteAccessAuthorization writeAccessAuthorization;
#InjectMocks private AccessAspect accessAspect;
#BeforeEach
public void setup() {
MockitoAnnotations.openMocks(this);
}
#Test
void test_isAdmin()
throws Throwable {
//Given
when(joinPoint.getSignature()).thenReturn(methodSignature);
when(methodSignature.getMethod()).thenReturn(getDeclaredMethod(WriteAccessAuthorization.class));
when(method.getAnnotation(WriteAccessAuthorization.class)).thenReturn(writeAccessAuthorization);
//When
accessAspect.isAdminForWriteAccess(joinPoint);
//Then
verify(joinPoint, times(1)).proceed();
}
#NotNull
private <T> Method getDeclaredMethod(Class<T> clazz) throws NoSuchMethodException {
return clazz.getDeclaredMethod("isAdmin");
}
}
In many blogs or stackoverflow answers it was mention to have RUNTIME policy in you annotation but in my case it was already placed.
Please let me know if there is anything else required.
You actually need to apply the annotation to a method. What your code is testing is whether the isAdmin method defined in annotation interface is annotated with WriteAccessAuthorization which it is not. That's why method.getAnnotation returns null.
Here is an example of how to access the annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface WriteAccessAuthorization {
boolean isAdmin() default false;
}
public static final class SomeClass {
#WriteAccessAuthorization
public void doSth() {}
}
public static void main(String[] args) throws Exception {
Method method = SomeClass.class.getDeclaredMethod("doSth");
WriteAccessAuthorization annotation =
method.getDeclaredAnnotation(WriteAccessAuthorization.class);
System.out.println(annotation);
}
Output:
#com.example.test$WriteAccessAuthorization(isAdmin=false)

Test Spring beans with different constructor args over JUnit using #ContextConfiguration

Description of the problem:
I would like users to configure spring beans with custom config files over spring XML configs, like this:
Note that only Strings should be configured by the user, all other beans should be #Autowired without the user knowing it!
<bean class="com.my.group.Provider">
<constructor-arg value="config1.proprietary"/>
<constructor-arg value="config2.proprietary"/>
</bean>
The Provider object looks (simplified) as follows:
public class Provider {
#Autowired
private Foo foo;
private final String[] configNames;
public Provider(final String... configs) {
this.configNames = Preconditions.checkNotNull(configs, "Provided configs must not be null!");
}
public List<Configs> getConfigs() {
return new foo.create(configNames); // here is more logic that I would actually like to test... (not just methods called on foo)
}
}
My question is:
How can I test this solution with various different string inputs, so that all tests can go into one JUnit Test class? Btw: I would like to avoid reflections...
(The unit tests below show what I mean. And they are already capable to do what I want, but they use reflections.)
What I did so far
is using reflections to change the field content afterwards, but tbh that is not sexy at all:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ProviderTest.MyContext.class})
public class ProviderTest {
#Autowired
private Provider sut;
#Test
public void provide_oneConfig() throws NoSuchFieldException, IllegalAccessException {
setConfigFilesViaReflection(sut, "config1.proprietary"");
// When
List<Config> configs = sut.getConfigs();
// Then
assertEquals(1, configs.size());
}
#Test
public void provide_twoConfigs() throws NoSuchFieldException, IllegalAccessException {
setConfigFilesViaReflection(sut, "config1.proprietary", config2.proprietary");
// When
List<Config> configs = sut.getConfigs();
// Then
assertEquals(2, configs.size());
}
private void setConfigFilesViaReflection(final Provider sut, final String... configs) throws NoSuchFieldException,
IllegalAccessException {
Field configNamesField = Provider.class.getDeclaredField("configNames");
configNamesField.setAccessible(true);
configNamesField.set(sut, configs);
}
#Configuration
public static class MyContext {
#Bean
Provider provider() {
return new Provider("willBeOverridenByReflection");
}
#Bean
Foo foo() {
return new Foo(); // this one got mocked in my test
}
}
Sometimes asking a questions helps to search harder.
The #Qualifier / #Resource annotation make it possible to create several beans, and choose them per test like that:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = {ProviderTest.MyContext.class})
public class ProviderTest {
#Autowired
#Qualifier("bar") // could also be #Resource (without #Autowired)
private Provider sut;
#Resource(name="baz")
private Provider sut2; // could also be #Qualifier(with #Autowired)
#Test
public void provide_oneConfig() throws NoSuchFieldException, IllegalAccessException {
// When
List<Config> configs = sut.getConfigs();
// Then
assertEquals(1, configs.size());
}
#Test
public void provide_twoConfigs() throws NoSuchFieldException, IllegalAccessException {
// When
List<Config> configs = sut2.getConfigs();
// Then
assertEquals(2, configs.size());
}
#Configuration
public static class MyContext {
#Bean("bar")
Provider providerBar() {
return new Provider"config1.proprietary");
}
#Bean("baz")
Provider providerBaz() {
return new Provider("config1.proprietary", "config2.proprietary");
}
#Bean
Foo foo() {
return new Foo(); // this one got mocked in my test
}
}
Found my answer here: Autowiring two different beans of same class

Why I couldn't get the field's annotation?

This is my own Annotation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.SOURCE)
public #interface Autowire {
public String id();
}
then, I define a class, and set a property with the annotation:
#Component(id="businessObject")
public class BusinessObject {
#Autowire(id="dataAccessInterface")
private DataAccessInterface dai;
public void print() {
System.out.println(dai.queryFromTableA());
}
public void setDai(DataAccessInterface dai) {
this.dai = dai;
}
}
The #Component is also mine.
after all, I define a tracker:
public class BeanFactory {
private HashMap<String, Object> beanPool;
private HashMap<String, String> components;
public BeanFactory() {
beanPool = new HashMap<>();
scanComponents();
}
private void scanComponents() {
components = ComponentScanner.getComponentClassName("com.oolong.javase.annotation");
}
public Object getBean(String id) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, NoSuchMethodException,
SecurityException, IllegalArgumentException, InvocationTargetException {
if (beanPool.containsKey(id)) {
return beanPool.get(id);
}
if (components.containsKey(id)) {
Object bean = Class.forName(components.get(id)).newInstance();
bean = assemblyMember(bean);
beanPool.put(id, bean);
return getBean(id);
}
throw new ClassNotFoundException();
}
private Object assemblyMember(Object obj) throws ClassNotFoundException,
InstantiationException, IllegalAccessException, NoSuchMethodException,
SecurityException, IllegalArgumentException, InvocationTargetException {
Class cl = obj.getClass();
for (Field f : cl.getDeclaredFields()) {
Autowire at = f.getAnnotation(Autowire.class);
if (at != null) {
Method setMethod = cl.getMethod("set" + captureName(f.getName()), f.getType());
setMethod.invoke(obj, getBean(at.id()));
}
}
return obj;
}
public static String captureName(String name) {
char[] cs=name.toCharArray();
cs[0]-=32;
return String.valueOf(cs);
}
}
the problem is, when I got the field annotation with #Autowire, and want to get the annotation, I can't get the annotation:
Autowire at = f.getAnnotation(Autowire.class);
the at is null.
why?
(Sorry, I am not good at English!)
You are using the SOURCE retention policy. The quote from sdk:
/**
* Annotations are to be discarded by the compiler.
*/
Try to use the RUNTIME retention policy instead:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Autowire {
public String id();
}
According to documentation:
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*

pass result of annotation work to annotated method

Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Multipart {
Class acceptClass();
}
Annotated method:
#Multipart (acceptClass = SomeClass.class)
public void someMethod(SomeClass a){
//do stuff..
}
MultipartAspect:
#Aspect
public class MultipartAspect {
#Autowired(required=true)
private HttpServletRequest request;
#Pointcut(value = "#annotation(Multipart)", argNames = "multipart")
public void before(JoinPoint jp, Multipart multipart) {}
#Before("before()")
public SomeClass doStuffBeforeThing() {
SomeClass sc = new SomeClass(); //object of passed class
//do something..
return sc; //return this to annotated method(somemethod)
}
}
I want before method works execute annotation, create object of passed class(SomeClass) and the pass object of this class to annotated method. Could I do this?
You should use #Around advice instead of #Before.

Spring AOP CGLIB proxy's field is null

Description
Using the vlcj component, the custom component appears as a result of the AOP proxy object null.
MediaList Class
public class MediaList {
private libvlc_media_list_t mediaListInstance;
public MediaList(LibVlc libvlc, libvlc_instance_t instance, libvlc_media_list_t mediaListInstance) {
this.libvlc = libvlc;
this.instance = instance;
createInstance(mediaListInstance);
}
private void createInstance(libvlc_media_list_t mediaListInstance) {
logger.debug("createInstance()");
if(mediaListInstance == null) {
mediaListInstance = libvlc.libvlc_media_list_new(instance);
}
else {
libvlc.libvlc_media_list_retain(mediaListInstance);
}
this.mediaListInstance = mediaListInstance; // <- assignment
logger.debug("mediaListInstance={}", mediaListInstance);
mediaListEventManager = libvlc.libvlc_media_list_event_manager(mediaListInstance);
logger.debug("mediaListEventManager={}", mediaListEventManager);
registerEventListener();
}
public final libvlc_media_list_t mediaListInstance() {
return mediaListInstance; // <- proxy object return null, if use aop
}
}
Custom MediaList Class
public class TestMediaList extends MediaList {
public TestMediaList(LibVlc libvlc, libvlc_instance_t instance) {
super(libvlc, instance);
}
public void xTest(String test){
System.out.println(test);
}
}
Spring Configuration Class
#Configuration
public class PlayerBeanConfig {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
#Resource
public TestMediaList testMediaList(LibVlc libvlc, libvlc_instance_t instance) {
return new TestMediaList(libvlc, instance);
}
}
AOP Configuration Class
#Aspect
public class MediaListAspect {
#Pointcut("execution(* TestMediaList.xTest(..))")
private void anyMethod() {
}
#Around("anyMethod()")
public Object lockAndUnlock(ProceedingJoinPoint joinPoint) throws Throwable {
Object object = joinPoint.proceed();
return object;
}
}
Test Code
public static void main(String[] args) {
boolean b = new NativeDiscovery().discover();
if (b) {
springContext = new AnnotationConfigApplicationContext(PlayerBeanConfig.class);
String[] kkk = new String[]{};
TestMediaList list = springContext.
getBean(TestMediaList.class, LibVlc.INSTANCE, LibVlc.INSTANCE.libvlc_new(kkk.length, kkk));
System.out.println(list.mediaListInstance()); // <- proxy object return null
} else {
logger.error("Cannot find vlc lib, exit application");
}
}
I try to single step tracking, when TestMediaList the build is complete. MediaListInstance () of the method to return to normal values, but when the spring returns to the proxy object, null is returned. At the same time, I also try to return the value correctly if you don't use AOP.
Therefore, I determine the basic problem in AOP dynamic proxy, but I don't know why, did not previously encountered such a situation.
Minimal example
all class in package : vod.demo
TargetClass
public class TargetClass {
private String returnValue;
public TargetClass() {
this.returnValue = "Hello World";
}
public final String test() {
System.out.println("TargetClass.test();");
return returnValue;
}
}
Aspect Class
#Aspect
public class AspectClass {
#Pointcut("execution(* vod.demo.TargetClass.*(..))")
private void targetMethod() {
}
#Around("targetMethod()")
public Object aroundTarget(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("AspectClass.aroundTarget();");
return joinPoint.proceed();
}
}
Spring Config Class
#Configuration
#EnableAspectJAutoProxy
#Import(AspectClass.class)
public class SpringConfig {
#Bean
public TargetClass target() {
return new TargetClass();
}
}
Client Class
public class Client {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
TargetClass target = context.getBean(TargetClass.class);
System.out.println("Client invoke:" + target.test()); // <- output null
}
}
This is a combination of potentially unexpected behaviors. First, Spring uses CGLIB to proxy your beans for AOP. CGLIB proxies are instances of a dynamic subtype of your class that delegate all method calls to a real instance of your class. However, even though the proxy is of a subtype, its fields are not initialized (ie. your TargetClass super constructor is not invoked). A lengthier explanation can be found here.
Additionally, your method
public final libvlc_media_list_t mediaListInstance() {
return mediaListInstance; // <- proxy object return null, if use aop
}
or
public final String test() {
System.out.println("TargetClass.test();");
return returnValue;
}
are final. CGLIB therefore cannot override them to delegate to the real instance. This would be hinted at in Spring logs. For example, you would see
22:35:31.773 [main] INFO o.s.aop.framework.CglibAopProxy - Unable to proxy method [public final java.lang.String com.example.root.TargetClass.test()] because it is final: All calls to this method via a proxy will NOT be routed to the target instance.
Put all of the above together and you get a proxy instance where the field is null and where the proxy cannot delegate to the real instance's method. So your code will actually invoke
public final String test() {
System.out.println("TargetClass.test();");
return returnValue;
}
for an instance where the returnValue field is null.
If you can, change your method, remove the final modifier. If you can't, you'll have to rethink your design.

Categories

Resources