I am using Spring AOP and have below aspect:
#Aspect
public class LoggingAspect {
#Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("logBefore() is running!");
System.out.println("hijacked : " + joinPoint.getSignature().getName());
System.out.println("******");
}
}
Above aspect intercepts addCustomer method execution. addCustomer method takes string as an input.
But I need to log input passed to addCustomer method inside logBefore method.
Is it possible to do so ?
You have a few options:
First, you can use the JoinPoint#getArgs() method which returns an Object[] containing all the arguments of the advised method. You might have to do some casting depending on what you want to do with them.
Second, you can use the args pointcut expression like so:
// use '..' in the args expression if you have zero or more parameters at that point
#Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..)) && args(yourString,..)")
then your method can instead be defined as
public void logBefore(JoinPoint joinPoint, String yourString)
Yes, the value of any argument can be found using getArgs
#Before("execution(* com.mkyong.customer.bo.CustomerBo.addCustomer(..))")
public void logBefore(JoinPoint joinPoint) {
Object[] signatureArgs = thisJoinPoint.getArgs();
for (Object signatureArg: signatureArgs) {
System.out.println("Arg: " + signatureArg);
...
}
}
If you have to log all args or your method have one argument, you can simply use getArgs like described in previous answers.
If you have to log a specific arg, you can annoted it and then recover its value like this :
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface Data {
String methodName() default "";
}
#Aspect
public class YourAspect {
#Around("...")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method method = MethodSignature.class.cast(point.getSignature()).getMethod();
Object[] args = point.getArgs();
StringBuilder data = new StringBuilder();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int argIndex = 0; argIndex < args.length; argIndex++) {
for (Annotation paramAnnotation : parameterAnnotations[argIndex]) {
if (!(paramAnnotation instanceof Data)) {
continue;
}
Data dataAnnotation = (Data) paramAnnotation;
if (dataAnnotation.methodName().length() > 0) {
Object obj = args[argIndex];
Method dataMethod = obj.getClass().getMethod(dataAnnotation.methodName());
data.append(dataMethod.invoke(obj));
continue;
}
data.append(args[argIndex]);
}
}
}
}
Examples of use :
public void doSomething(String someValue, #Data String someData, String otherValue) {
// Apsect will log value of someData param
}
public void doSomething(String someValue, #Data(methodName = "id") SomeObject someData, String otherValue) {
// Apsect will log returned value of someData.id() method
}
There is also another way if you define one pointcut for many advices it can be helpful:
#Pointcut("execution(#com.stackoverflow.MyAnnotation * *(..))")
protected void myPointcut() {
}
#AfterThrowing(pointcut = "myPointcut() && args(someId,..)", throwing = "e")
public void afterThrowingException(JoinPoint joinPoint, Exception e, Integer someId) {
System.out.println(someId.toString());
}
#AfterReturning(pointcut = "myPointcut() && args(someId,..)")
public void afterSuccessfulReturn(JoinPoint joinPoint, Integer someId) {
System.out.println(someId.toString());
}
Your can use either of the following methods.
#Before("execution(* ong.customer.bo.CustomerBo.addCustomer(String))")
public void logBefore1(JoinPoint joinPoint) {
System.out.println(joinPoint.getArgs()[0]);
}
or
#Before("execution(* ong.customer.bo.CustomerBo.addCustomer(String)), && args(inputString)")
public void logBefore2(JoinPoint joinPoint, String inputString) {
System.out.println(inputString);
}
joinpoint.getArgs() returns object array. Since, input is single string, only one object is returned.
In the second approach, the name should be same in expression and input parameter in the advice method i.e. args(inputString) and public void logBefore2(JoinPoint joinPoint, String inputString)
Here, addCustomer(String) indicates the method with one String input parameter.
you can get method parameter and its value and if annotated with a annotation with following code:
Map<String, Object> annotatedParameterValue = getAnnotatedParameterValue(MethodSignature.class.cast(jp.getSignature()).getMethod(), jp.getArgs());
....
private Map<String, Object> getAnnotatedParameterValue(Method method, Object[] args) {
Map<String, Object> annotatedParameters = new HashMap<>();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Parameter[] parameters = method.getParameters();
int i = 0;
for (Annotation[] annotations : parameterAnnotations) {
Object arg = args[i];
String name = parameters[i++].getDeclaringExecutable().getName();
for (Annotation annotation : annotations) {
if (annotation instanceof AuditExpose) {
annotatedParameters.put(name, arg);
}
}
}
return annotatedParameters;
}
If it's a single String argument, do:
joinPoint.getArgs()[0];
if your using #Aspect an option is add this method inside your Aspect and send the JoinPoint and the name of parameter you need.
private Object getParameter(ProceedingJoinPoint joinPoint, String parameterName) {
Object valueParameter = null;
if (Objects.nonNull(joinPoint) && joinPoint.getSignature() instanceof MethodSignature
&& Objects.nonNull(parameterName) ) {
MethodSignature method = (MethodSignature)joinPoint.getSignature();
String[] parameters = method.getParameterNames();
for (int t = 0; t< parameters.length; t++) {
if( Objects.nonNull(parameters[t]) && parameters[t].equals(parameterName)) {
Object[] obj = joinPoint.getArgs();
valueParameter = obj[t];
}
}
}
return valueParameter;
}
and the call example:
Object parameterObject = getParameter(joinPoint, "nameClient");
if ( Objects.nonNull(parameterObject) ) {
String parametro = String.valueOf(parameterObject);
}
Only need know the type of object for convert
Related
I have a program written in Java 11 with two custom annotation. One on Method level and one on Parameter level.
I`m using Spring AOP to act on methods that use my Custom annotations. But I have not found a way to get the value ouf of an optional annotation that is on Parameter level.
#CustomAnnotation
public void myMethod(#CustomValue String param1, int param2) {
...
}
#AfterReturning(value = "#annotation(customAnnotation)", argNames = "customAnnotation, jp")
public void afterReturning(JoinPoint jp, CustomAnnotation customAnnotation) {
...
}
The program works well with my #CustomAnnotation, but I have problems to get the value out of parameters that is annotated with #CustomValue. #CustomValue is useless if the method is not annotated with #CustomAnnotation. And the number of parameters that can be annotated with #CustomValue is 0 or more.
How can I get the values from the parameters that is annotated with #CustomValue on a method that is annotated with #CustomAnnotation?
#AfterReturning(value = "#annotation(CustomAnnotation)", argNames = "customAnnotation, jp")
public void afterReturning(JoinPoint jp, CustomAnnotation customAnnotation) {
CustomAnnotation annotation = customAnnotation;
List<Parameter> parameterList = new ArrayList<>();
if (jp.getSignature() instanceof MethodSignature) {
MethodSignature signature =
(MethodSignature) jp.getSignature();
Method method = signature.getMethod();
anno = method.getAnnotation(CustomAnnotation.class);
Parameter[] params = method.getParameters();
for (Parameter param : params) {
try {
if(param.getAnnotation(CustomValue.class) !=null){
parameterList.add(param);
}
} catch (Exception e) {
//do nothing
}
}
}
}
You should now get the list of parameters annotated with #CustomValue. This exact code may not work while copy-paste, but you should get the idea.
Maybe there is a better way, but I solved this by using following:
#AfterReturning(value = "#annotation(customAnnotation)", argNames = "customAnnotation, jp")
public void afterReturning(JoinPoint jp, CustomAnnotation customAnnotation) {
MethodSignature signature = (MethodSignature) jp.getSignature();
Parameter[] parameters = signature.getMethod().getParameters();
Object[] args = jp.getArgs();
for (int i = 0; i < args.length; ++i) {
if (parameters[i].isAnnotationPresent(CustomValue.class)) {
Object paramValue = args[i];
}
}
}
I have a url like http://localhost:8080?scope=openid%20profile
I want to transfer it to http://localhost:8080?scope=openid&scope=profile
so that the following endpoint could accept it.
// Scope.java
public enum Scope {
OPENID,
PROFILE;
public static Scope fromString(String value) {
return valueOf(value.toUpperCase());
}
}
// AuthorizationEndpoint.java
#GET
#Path("authorize")
public Response authorize(#Valid #NotEmpty #QueryParam("scope") Set<Scope> scope) {
...
}
I tried to add a filter like below, but the map of request params is unmodifiable !
// Split.java
public #interface Split {
String value();
}
// AuthorizationEndpoint.java
#GET
#Path("authorize")
// add #Split(" ")
public Response authorize(#Valid #NotEmpty #QueryParam("scope") #Split(" ") Set<Scope> scope)
{
...
}
// SplitFilter.java
#Provider
class SplitFilter implements ContainerRequestFilter {
private final ResourceInfo resourceInfo;
SplitFilter(ResourceInfo resourceInfo) {
this.resourceInfo = resourceInfo;
}
#Override
public void filter(ContainerRequestContext requestContext) {
for (var parameter: resourceInfo.getResourceMethod().getParameters()) {
var queryParam = parameter.getAnnotation(QueryParam.class);
if (queryParam == null) continue;
var split = parameter.getAnnotation(Split.class);
if (split == null) continue;
var queryName = queryParam.value();
// Note: queryParams is unmodifiable!!!
var queryParams = requestContext.getUriInfo().getQueryParameters();
var originQueryValues = queryParams.get(queryName);
if (originQueryValues.size() != 1) {
throw new IllegalStateException("Incorrect size of query values: " + originQueryValues + ", expect only 1.");
}
var splitQueryValue = originQueryValues.get(0).split(split.value());
// Error: originQueryValues is unmodifiable!!!
originQueryValues.clear();
originQueryValues.addAll(Arrays.asList(splitQueryValue));
}
}
}
So is there a proper way to modify the request params in filters or other inteceptors?
I also tried to make Set<Scope> a single class that recieves a String value as constructor param,
but how to get the converter that converts a String value to a instance of Scope instead of calling Scope.fromString?
I think you can't use a filter for that, exactly because a filter is not allowed to change the params. You could use a param converter, roughly something like this:
#Provider
public class ScopeConverterProvider implements ParamConverterProvider {
#Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
if(rawType == Set.class) {
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(Split.class)) {
return (ParamConverter<T>) new ScopeConverter(((Split) annotation).value());
}
}
return (ParamConverter<T>) new ScopeConverter(" ");// default if there's no #Split annotation
}
return null;
}
}
public class ScopeConverter implements ParamConverter<Set<Scope>> {
private final String splitCharacter;
public ScopeConverter(String splitCharacter) {
this.splitCharacter = splitCharacter;
}
#Override
public Set<Scope> fromString(String value) {
// strip the [] that the value gets automatically wrapped in,
// because the query param is a set
value = value.substring(1, value.length() - 1);
String[] splits = value.split(splitCharacter);
HashSet<Scope> scopes = new HashSet<>();
for (String split : splits) {
scopes.add(Scope.fromString(split));
}
return scopes;
}
#Override
public String toString(Set<Scope> value) {
return null; // unused
}
}
I have a bean class
public class Group{string name;Type type; }
and another bean
public class Type{String name;}
Now, i want to bind group by using jdbi #BindBean
#SqlBatch("INSERT INTO (type_id,name) VALUES((SELECT id FROM type WHERE name=:m.type.name),:m.name)")
#BatchChunkSize(100)
int[] insertRewardGroup(#BindBean ("m") Set<Group> groups);
How can i bind the user defined object's property as member of the bean??
You could implement your own Bind-annotation here. I implemented one that I am adopting for this answer. It will unwrap all Type ones.
I think it could be made fully generic with a little more work.
Your code would look like this (please note that m.type.name changed to m.type):
#SqlBatch("INSERT ... WHERE name=:m.type),:m.name)")
#BatchChunkSize(100)
int[] insertRewardGroup(#BindTypeBean ("m") Set<Group> groups);
This would be the annotation:
#BindingAnnotation(BindTypeBean.SomethingBinderFactory.class)
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.PARAMETER})
public #interface BindTypeBean {
String value() default "___jdbi_bare___";
public static class SomethingBinderFactory implements BinderFactory {
public Binder build(Annotation annotation) {
return new Binder<BindTypeBean, Object>() {
public void bind(SQLStatement q, BindTypeBean bind, Object arg) {
final String prefix;
if ("___jdbi_bare___".equals(bind.value())) {
prefix = "";
} else {
prefix = bind.value() + ".";
}
try {
BeanInfo infos = Introspector.getBeanInfo(arg.getClass());
PropertyDescriptor[] props = infos.getPropertyDescriptors();
for (PropertyDescriptor prop : props) {
Method readMethod = prop.getReadMethod();
if (readMethod != null) {
Object r = readMethod.invoke(arg);
Class<?> c = readMethod.getReturnType();
if (prop.getName().equals("type") && r instanceof Type) {
r = ((Type) r).getType();
c = r.getClass();
}
q.dynamicBind(c, prefix + prop.getName(), r);
}
}
} catch (Exception e) {
throw new IllegalStateException("unable to bind bean properties", e);
}
}
};
}
}
}
Doing this in JDBI is not possible , you have to bring out the property and give is a argument.
Hi i am using reflections to achieve something.
I have been given class name, method name of that class and parameter values that needs to be passed to that method in a file(Take any file. Not a constraint).
I have to call that method with the parameters. This methods do not return anything.
There is a huge list of methods in this classes and parameter list of each varies.
E.g: method1(String, String, int, boolean)
method1(String, int, boolean) and likewise i have different permutations and combinations.
So how can i achieve this.
I have tried hard coding things with different switch clauses but it is a real overhead and risky thing to maintain.
Can we dynamically do this thing, like on the fly read the method name and its parameter from the file and call it.
Any small code snippet will be helpful.
TIA.
Hi all i have found the solution to the above question. below is the sample code snippet.
package reflections;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectionTest {
public void method1(String str, int number) {
System.out.println(str + number);
}
public void method1(String str) {
System.out.println(str);
}
public void method1() {
System.out.println("helloworld");
}
public static void main(String[] args) throws ClassNotFoundException,
InstantiationException, IllegalAccessException,
NoSuchMethodException, SecurityException, IllegalArgumentException,
InvocationTargetException {
// Step 1) Make an object array and store the parameters that you wish
// to pass it.
Object[] obj = {};// for method1()
// Object[] obj={"hello"}; for method1(String str)
// Object[] obj={"hello",1}; for method1(String str,int number)
// Step 2) Create a class array which will hold the signature of the
// method being called.
Class<?> params[] = new Class[obj.length];
for (int i = 0; i < obj.length; i++) {
if (obj[i] instanceof Integer) {
params[i] = Integer.TYPE;
} else if (obj[i] instanceof String) {
params[i] = String.class;
}
// you can do additional checks for other data types if you want.
}
String methoName = "method1"; // methodname to be invoked
String className = "reflections.ReflectionTest";// Class name
Class<?> cls = Class.forName(className);
Object _instance = cls.newInstance();
Method myMethod = cls.getDeclaredMethod(methoName, params);
myMethod.invoke(_instance, obj);
}
}
I hope this will help others too.
public class ReflectionSample
{
private Object mString = null;
private int mValue;
public ReflectionSample()
{
}
public ReflectionSample(int oValue)
{
mValue = oValue;
}
public ReflectionSample(String oString)
{
mString = oString;
}
public ReflectionSample(String oString, int oValue)
{
setValues(oString, oValue);
}
public void setValues(String oString, int oValue)
{
mString = oString;
mValue = oValue;
}
public String toString()
{
return ""+mString+":"+mValue;
}
public void run()
{
String oInput = "Teststring";
Class<?> cls;
String clsname = "main.ReflectionSample";
Object rs = null; // ReflectionSample
Object rsc = null;
System.out.println(this.getClass().getName());
try
{
System.out.println(clsname);
cls = Class.forName(clsname);
if(cls == null)
{
System.err.println(clsname + " doesn't exist");
return;
}
// Look for a constructor which has a single string
Constructor<?> ct = null;
Class<?>[] param_types = new Class<?>[1];
Object[] arguments = new Object[1];
param_types[0] = String.class;
// get the string constructor
ct = cls.getConstructor(param_types);
// We only have one object
arguments = new Object[1];
arguments[0] = oInput;
// Instantiate the object with passed in argument.
rs = ct.newInstance(arguments);
System.out.println("String constructor sample: "+rs);
// Instantiate with default constructor
param_types = new Class<?>[0];
arguments = new Object[0];
ct = cls.getConstructor(param_types);
rs = ct.newInstance(arguments);
rsc = rs; // Keep it for later, to lazy to call it again
System.out.println("Default constructor sample: "+rs);
// Instantiate with string and int constructor
param_types = new Class<?>[2];
arguments = new Object[2];
// Must be in the same order as the params I think
param_types[0] = String.class;
param_types[1] = Integer.TYPE; // <-- Its a primitive so use TYPE not Class
arguments[0] = oInput;
arguments[1] = new Integer(1);
ct = cls.getConstructor(param_types);
rs = ct.newInstance(arguments);
System.out.println("String plus int constructor sample: "+rs);
// call the setValues method
param_types[0] = String.class;
param_types[1] = Integer.TYPE; // <-- Its a primitive so use TYPE not Class
arguments[0] = oInput;
arguments[1] = 1;
System.out.println("setValues invocation before: "+rsc);
Method m = cls.getMethod("setValues", param_types);
m.invoke(rsc, arguments);
System.out.println("setValues invocation after: "+rsc);
// An alternative method to pass the parameters
m = cls.getMethod("setValues", String.class, Integer.TYPE);
m.invoke(rsc, oInput+"x", 2);
System.out.println("setValues invocation after: "+rsc);
}
catch(Throwable e)
{
System.err.println(e.getLocalizedMessage());
}
}
}
Output:
main.ReflectionSample
main.ReflectionSample
String constructor sample: Teststring:0
Default constructor sample: null:0
String plus int constructor sample: Teststring:1
setValues invocation before: null:0
setValues invocation after: Teststring:1
Hope this helps.
I don't know if this is a newer feature in Java, but I have seen that you can use invoke now with parameters as well, instead of using an array, which might make your code better to read (This is the alternative way). If you need a variable number of arguments and you don't know beforehand how many there will be, allocating the array is defeinitly working and should also be backwardcompatible.
A simple solution would be to create a Class with the Arguments required to be passed:
public class ObjectArguments {
private PrintWriter out;
private String productId;
private int action;
public ObjectArguments(PrintWriter out, String productId, int action) {
this.out = out;
this.productId = productId;
this.action = action;
}
public PrintWriter getOut() {
return out;
}
public String getProductId() {
return productId;
}
public int getAction() {
return action;
}
}
Assuming that you want to invoke a class Foo with a method named bar.
Then it would be done like this.
PrintWriter out = null;
String productId = null;
int action = 0;
Class[] paramArguments = new Class[1];
paramArguments[0] = ObjectArguments.class;
ObjectArguments newObj = new ObjectArguments(out,productId,action);
Class cls = Class.forName("Foo");
Object obj = cls.newInstance();
Method method = cls.getDeclaredMethod("bar", paramArguments);
method.invoke(obj, newObj);
For two int parameters the example is as below, similarly other datatype parameters can also be called
Method method=new Test1().getClass().getMethod(x, new Class[] {int.class,int.class});
We can call a method that needs 3 arguments int,int,string as below :
Method method=new Test1().getClass().getMethod(x, new Class[] {int.class,int.class, String.class});
TLDR: I'd like to know how to extend fit.TypeAdaptor so that I can invoke a method that expects parameters as default implementation of TypeAdaptor invokes the binded (bound ?) method by reflection and assumes it's a no-param method...
Longer version -
I'm using fit to build a test harness for my system (a service that returns a sorted list of custom objects). In order to verify the system, I thought I'd use fit.RowFixture to assert attributes of the list items.
Since RowFixture expects the data to be either a public attribute or a public method, I thought of using a wrapper over my custom object (say InstanceWrapper) - I also tried to implement the suggestion given in this previous thread about formatting data in RowFixture.
The trouble is that my custom object has around 41 attributes and I'd like to provide testers with the option of choosing which attributes they want to verify in this RowFixture. Plus, unless I dynamically add fields/methods to my InstanceWrapper class, how will RowFixture invoke either of my getters since both expect the attribute name to be passed as a param (code copied below) ?
I extended RowFixture to bind on my method but I'm not sure how to extend TypeAdaptor so that it invokes with the attr name..
Any suggestions ?
public class InstanceWrapper {
private Instance instance;
private Map<String, Object> attrs;
public int index;
public InstanceWrapper() {
super();
}
public InstanceWrapper(Instance instance) {
this.instance = instance;
init(); // initialise map
}
private void init() {
attrs = new HashMap<String, Object>();
String attrName;
for (AttrDef attrDef : instance.getModelDef().getAttrDefs()) {
attrName = attrDef.getAttrName();
attrs.put(attrName, instance.getChildScalar(attrName));
}
}
public String getAttribute(String attr) {
return attrs.get(attr).toString();
}
public String description(String attribute) {
return instance.getChildScalar(attribute).toString();
}
}
public class MyDisplayRules extends fit.RowFixture {
#Override
public Object[] query() {
List<Instance> list = PHEFixture.hierarchyList;
return convertInstances(list);
}
private Object[] convertInstances(List<Instance> instances) {
Object[] objects = new Object[instances.size()];
InstanceWrapper wrapper;
int index = 0;
for (Instance instance : instances) {
wrapper = new InstanceWrapper(instance);
wrapper.index = index;
objects[index++] = wrapper;
}
return objects;
}
#Override
public Class getTargetClass() {
return InstanceWrapper.class;
}
#Override
public Object parse(String s, Class type) throws Exception {
return super.parse(s, type);
}
#Override
protected void bind(Parse heads) {
columnBindings = new TypeAdapter[heads.size()];
for (int i = 0; heads != null; i++, heads = heads.more) {
String name = heads.text();
String suffix = "()";
try {
if (name.equals("")) {
columnBindings[i] = null;
} else if (name.endsWith(suffix)) {
columnBindings[i] = bindMethod("description", name.substring(0, name.length()
- suffix.length()));
} else {
columnBindings[i] = bindField(name);
}
} catch (Exception e) {
exception(heads, e);
}
}
}
protected TypeAdapter bindMethod(String name, String attribute) throws Exception {
Class partypes[] = new Class[1];
partypes[0] = String.class;
return PHETypeAdaptor.on(this, getTargetClass().getMethod("getAttribute", partypes), attribute);
}
}
For what it's worth, here's how I eventually worked around the problem:
I created a custom TypeAdapter (extending TypeAdapter) with the additional public attribute (String) attrName. Also:
#Override
public Object invoke() throws IllegalAccessException, InvocationTargetException {
if ("getAttribute".equals(method.getName())) {
Object params[] = { attrName };
return method.invoke(target, params);
} else {
return super.invoke();
}
}
Then I extended fit.RowFixture and made the following overrides:
public getTargetClass() - to return my class reference
protected TypeAdapter bindField(String name) throws Exception - this is a protected method in ColumnFixture which I modified so that it would use my class's getter method:
#Override
protected TypeAdapter bindField(String name) throws Exception {
String fieldName = camel(name);
// for all attributes, use method getAttribute(String)
Class methodParams[] = new Class[1];
methodParams[0] = String.class;
TypeAdapter a = TypeAdapter.on(this, getTargetClass().getMethod("getAttribute", methodParams));
PHETypeAdapter pheAdapter = new PHETypeAdapter(fieldName);
pheAdapter.target = a.target;
pheAdapter.fixture = a.fixture;
pheAdapter.field = a.field;
pheAdapter.method = a.method;
pheAdapter.type = a.type;
return pheAdapter;
}
I know this is not a neat solution, but it was the best I could come up with. Maybe I'll get some better solutions here :-)