how to intercept a method with specific parameters with bytebuddy - byte-buddy

I want to intercept method named methodA with one arg which's type is String as blow, what should i do. How to use hasParameters() api?
public class Demo {
public static void main(String[] args) {
new ByteBuddy()
.subclass(A.class)
.method(named("methodA").and(hasParameters(?)))
}
static class A {
public void methodA() {
System.out.println("methodA() invoked.");
}
public void methodA(String arg) {
System.out.println("methodA(" + arg + ") invoked.");
}
}
}

For this you want the ElementMatchers.takesArguments(String.class) matcher.
So something like that:
Class<? extends A> loaded = new ByteBuddy().subclass(A.class)
.method(ElementMatchers.named("methodA").and(ElementMatchers.takesArguments(String.class)))
.intercept(MethodDelegation.to(Demo.class))
.make().load(Demo.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();
A instance = loaded.getConstructor().newInstance();
instance.methodA("abc");
instance.methodA();
public class Demo {
static void intercept(String arg){
System.out.println("intercepted");
}
}

To clarify, you need to define a matcher (similar to a filter) to apply to methods. Create some constraint in the matcher so it will only match to some parameter structure you specify:
ElementMatcher<Iterable<? extends ParameterDescription>> matcher = parameterDescriptions -> {
for (ParameterDescription p : parameterDescriptions) {
if (p.getType().equals(TypeDescription.STRING.asGenericType()) && p.getIndex() == 0) return true;
}
return false;
};
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(SomeType.class)
.method(ElementMatchers.hasParameters(matcher))
.intercept(FixedValue.value("Hello World"))
.make()
.load(SomeType.class.getClassLoader(),
ClassReloadingStrategy.fromInstalledAgent());

Related

Spring Session redis How can I extend the save method in the RedisSessionRepository?

When I save a session with redis,
I'd like to add custom data.
RedisSessionRepository.class
....
#Override
public void save(CustomRedisSessionRepository.RedisSession session) {
if (!session.isNew) {
String key = getSessionKey(session.hasChangedSessionId() ? session.originalSessionId : session.getId());
Boolean sessionExists = this.sessionRedisOperations.hasKey(key);
if (sessionExists == null || !sessionExists) {
throw new IllegalStateException("Session was invalidated");
}
}
session.save();
//I want add code..... (custom data..)
}
So I decided to expand.
public class MyRedisSessionRepository extends RedisSessionRepository {
public MyRedisSessionRepository(RedisOperations<String, Object> sessionRedisOperations) {
super(sessionRedisOperations);
}
#Override
public void save(RedisSessionRepository.RedisSession session) {
super.save(session);
//add custom data...
}
}
But I can't.
The access modifier for RedisSession is 'default'.
public class RedisSessionRepository implements SessionRepository<RedisSessionRepository.RedisSession> {
...
final class RedisSession implements Session {
....
}
..
}
So I can't extend the save method of RedisSessionRepository.
Is there any other way Or is there an expandable class?

Using TypeDefinition in ElementMatchers.takesArguments()

I am creating a class like this:
class A{
A(A a){
...
}
A(){
A(null);
}
}
and my code looks like this:
TypeDefinition selfTypeDefinition = TypeDescription.Generic.Builder.rawType(TargetType.class).build();
Class c = new ByteBuddy().subclass(Object.class,ConstructorStrategy.Default.NO_CONSTRUCTORS).name("A").
defineConstructor(Modifier.PUBLIC).withParameters(selfTypeDefinition)
.intercept(MethodCall.invoke(Object.class.getConstructor())
.andThen(MethodCall.run(new Runnable() {
#Override
public void run() {
System.out.println("!!!!init A(A)");
}
})))
.defineConstructor(Modifier.PUBLIC).withParameters(new Type[0])
.intercept(MethodCall.invoke(ElementMatchers.isConstructor()
.and(ElementMatchers.takesArguments(selfTypeDefinition)))
.with(new Object[1])
.andThen(MethodCall.run(new Runnable() {
#Override
public void run() {
System.out.println("!!!!init A()");
}
})))
.make().load(Test.class.getClassLoader()).getLoaded();
The problem is I cannot pass TypeDefinition to ElementMatchers.takesArguments(). I have tried passing selfTypeDefinition.asErasure() but it does not work.
Just use selfTypeDefinition.asErasure() to represent a TypeDescription. Byte Buddy also supports matching generic type descriptions, but the matcher you are using expects an erasure format.

How to use #DataProvider present in different class

How to use #DataProvider that is present in a different class?
I have created a different package and I have defined data providers next to each test cases. Please share how I may to use that in a different class.
You can use the dataProviderClass attribute of #Test:
public class StaticProvider {
#DataProvider(name = "create")
public static Object[][] createData() {
return new Object[][] {
new Object[] { new Integer(42) }
};
}
}
public class MyTest {
#Test(dataProvider = "create", dataProviderClass = StaticProvider.class)
public void test(Integer n) {
// ...
}
}
Check the documentation for more details.
If you have unique dataProvider method name (createData), and if you choose not to give name after DataProvider annotation as below,
#DataProvider
public Object[][] createData(){
}
Then you can use the method name as below,
#Test(dataProvider = "createData", dataProviderClass = StaticProvider.class)

orika property expression mapping

Given
classA {
long fahr;
....
and
classB {
long cels;
....
how can I map the following in Orika?
A.fahr <-> (B.cels*9)/5
Do I need customised Mapper or Filter ?
I suggest to use field level converter if both are of different data types but since they are of same data type we have to use a custom converter for entire class.
This is sample converter that suitable for this use case.
import ma.glasnost.orika.BoundMapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
public class EntryClass {
public static void main(String[] args) {
EntryClass ec = new EntryClass();
BoundMapperFacade<A, B> facade = getMapperFactory().getMapperFacade(A.class, B.class);
A fahr = new A(455);
B cels = facade.map(fahr);
System.out.println(cels);
A revFahr = facade.mapReverse(cels);
System.out.println(revFahr);
}
private static MapperFactory getMapperFactory() {
MapperFactory factory = new DefaultMapperFactory.Builder()
.build();
ConverterFactory cfactory = factory.getConverterFactory();
cfactory.registerConverter(new FahrCelsConverter());
factory.classMap(A.class, B.class)
.field("fahr", "cels")
.byDefault()
.register();
return factory;
}
}
public class A {
long fahr;
public A(long fahr) {
this.fahr = fahr;
}
public long getFahr() {
return fahr;
}
public void setFahr(long fahr) {
this.fahr = fahr;
}
#Override
public String toString() {
return "A [fahr=" + fahr + "]";
}
}
public class B {
long cels;
public B(long cels) {
this.cels = cels;
}
public long getCels() {
return cels;
}
public void setCels(long cels) {
this.cels = cels;
}
#Override
public String toString() {
return "B [cels=" + cels + "]";
}
}
public class FahrCelsConverter extends BidirectionalConverter<A, B>
{
#Override
public B convertTo(A source, Type<B> destinationType, MappingContext mappingContext) {
if(source != null)
{
return new B((source.fahr - 32) * 5 / 9);
}
return null;
}
#Override
public A convertFrom(B source, Type<A> destinationType, MappingContext mappingContext) {
if(source != null)
{
return new A((source.cels / 5) * 9 + 32);
}
return null;
}
}
It's more suited to use a converter (by id).

How do I override Class<?>.getName() for certain classes?

My goal is to be able to override what I get back from CustomClass.class.getName() and CustomClass.getClass().getName()
It should return a custom value, which I think is best to define in an attribute like
#NameOverride("Custom.fully.qualified.class.name")
public class CustomClass {}
Is there any way to do that?
Fred's answer is okay, but his aspect could be somewhat more elegant with less code and especially fewer reflection calls. Sorry, I prefer AspectJ native code style, but #AspectJ annotation style would not be much longer:
String around(Class clazz) : call(public String Class.getName()) && target(clazz) {
NameOverride nameOverride = (NameOverride) clazz.getAnnotation(NameOverride.class);
return nameOverride == null ? proceed(clazz) : nameOverride.value();
}
Here is the full source code. I added a class without annotation to show the different behaviour and also a restriction to class definitions - #Target(ElementType.TYPE) - to the annotation class. I am also showing package names and imports:
package test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface NameOverride {
String value();
}
package test;
public class NormalClass {}
package test;
#NameOverride("Custom.fully.qualified.class.name")
public class CustomClass {}
package test;
public class Main {
public static void main(String[] args) {
System.out.println(NormalClass.class.getName());
System.out.println(CustomClass.class.getName());
System.out.println(new NormalClass().getClass().getName());
System.out.println(new CustomClass().getClass().getName());
}
}
package aspectj;
import test.NameOverride;
public aspect GetNameOverrider {
#SuppressWarnings({ "unchecked", "rawtypes" })
String around(Class clazz) : call(public String Class.getName()) && target(clazz) {
NameOverride nameOverride = (NameOverride) clazz.getAnnotation(NameOverride.class);
return nameOverride == null ? proceed(clazz) : nameOverride.value();
}
}
The output:
test.NormalClass
Custom.fully.qualified.class.name
test.NormalClass
Custom.fully.qualified.class.name
This is for sure not the best/fastest solution but maybe a POC...
First of all the file structure:
./src/aspectj:
GetNameOverrider.aj
./src/test:
CustomClass.java Main.java NameOverride.java
NameOverride.java:
#Retention(RetentionPolicy.RUNTIME)
public #interface NameOverride {
String value();
}
GetNameOverrider.aj:
#Aspect
public class GetNameOverrider {
#Around("call(String getName()) && !within(aspectj..*)")
public String advice(ProceedingJoinPoint pjp) throws Throwable {
String ret = (String) pjp.proceed();
String className = "" + pjp.getTarget();
className = className.replace("class ", "");
try {
test.NameOverride anno = Class.forName(className).getAnnotation(
test.NameOverride.class);
if (anno != null) {
return anno.value();
}
} catch (ClassNotFoundException e) {
return ret;
}
return ret;
}
}
gives me for Main.java:
public class Main {
public static void main(String[] args) {
System.out.println(CustomClass.class.getName());
System.out.println(new CustomClass().getClass().getName());
}
}
the output:
Custom.fully.qualified.class.name
Custom.fully.qualified.class.name