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.
*
Related
Here is an example
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface Annotation {
}
#Configuration
public class Configuration {
#Bean
#Annotation
public Test getTest() {
return new Test();
}
}
public class Test() {
public void test() {
// how can get the annotation `#Annotation` here?
}
}
Here is what I have tried getClass().getAnnotations() but this returns empty array. I can see why since getClass() return Test.class which does not have the annotation. How can I get the method that creates this instance and then get the annotation?
You could, in theory, inspect the current Thread stack to determine the name of your caller, then look up the class definition, locate the method, and read its annotations:
var t = Thread.currentThread().getStackTrace()[2];
var className = t.getClassName();
Class<?> clazz;
try {
clazz = Test.class.getClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Caller was loaded by a different ClassLoader :-(");
}
for (var method : clazz.getDeclaredMethods()) {
if (method.getName().equals(t.getMethodName())) {
return method.getAnnotation(YourAnnotation.class).value();
}
}
throw new RuntimeException("Method not found - I might have found the wrong class definition");
However:
inspecting the stack is rather slow, in particular if the stack is deep
inspecting the stack is brittle with respect to refactorings (people don't expect that factoring out code into a utility method will change behaviour)
the compiler can not check that the caller provides the required annotation
this only works reliably if all code is loaded by the same ClassLoader
this can not distinguish overloaded methods
This is therefore a rather brittle hack. Are you sure that there is no better option? For instance, requiring the caller to pass the value as a method parameter would have none of these shortcomings ...
You can use ConfigurableListableBeanFactory to get metadata about any Bean by name. Use BeanNameAware interface to retrieve Bean name.
#Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Qualifier
public #interface CustomAnnotation {
}
#org.springframework.context.annotation.Configuration
public static class ContextConfiguration {
#Bean(name = "TEST")
#CustomAnnotation
public TestObject getTest() {
return new TestObject();
}
}
public class TestObject implements BeanNameAware {
private String beanName;
#Autowired
ConfigurableListableBeanFactory beanFactory;
#Override
public void setBeanName(String name) {
this.beanName = name;
}
public void test() {
CustomAnnotation customAnnotation = (CustomAnnotation) getBeanAnnotation(beanName, CustomAnnotation.class);
}
private Annotation getBeanAnnotation(String beanName, java.lang.Class<? extends Annotation> clazz) {
Annotation annotation = null;
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if( beanDefinition != null && beanDefinition.getSource() instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) beanDefinition.getSource();
annotation = Arrays.stream(metadata.getIntrospectedMethod().getDeclaredAnnotations()).filter(annot -> annot.annotationType().equals(clazz)).findFirst().orElse(null);
}
return annotation;
}
}
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!
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
I have a controller:
#Authorised(id = "{personId}")
#RequestMapping(value = {"{personId}"}, method = GET)
public void test(#PathVariable PersonId personId) {
System.out.println(personId); //gets personId
}
Annotation:
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Authorised {
String id() default "";
}
Pointcut:
#Pointcut("#annotation(Authorised)")
private void AuthorisedMethod() {}
And the method that has to get {personId} value not string "{personId}":
#Before("AuthorisedMethod()")
public void checkIfIsCurrentlyAuthenticated(JoinPoint joinPoint) throws NoSuchMethodException {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
String methodName = signature.getMethod().getName();
Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();
Parameter[] parameters = signature.getMethod().getParameters();
Authorised annotations = joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes).getAnnotation(Authorised.class);
String id = annotations.id();
System.out.println(id); // prints: "{personId}"
// do the chekcing
throw new UnauthenticatedUserException();
}
Can it be achieved and how?
UPDATE: But what if method argument parameter number don't match with the pointcut args()? I mean that what if specific method has parameter #PathVariable PersonId personId and few more, but poincut needs to know only PersonId personId?
Like #statut said you have to write args() like that: args(personId,..)
You can modify #Before() annotation to have PersonId value and pass this value to aspect, for example
#Before("AuthorisedMethod() && args(personId)")
public void checkIfIsCurrentlyAuthenticated(JoinPoint joinPoint, PersonId personId) throws NoSuchMethodException {}
To test it I had the following Aspect:
#Aspect
#Component
public class SomeAspect {
#Pointcut("#annotation(Authorised)")
private void AuthorisedMethod() {
}
#Before("AuthorisedMethod() && args(personId)")
public void checkIfIsCurrentlyAuthenticated(JoinPoint joinPoint, PersonId personId) throws NoSuchMethodException {
System.out.println("aspect " + personId.getId());
}
}
Configuration class:
#Configuration
#ComponentScan(basePackages = {"test"})
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class Config {
}
Test component:
#Component
public class Test {
#Authorised(id = "{personId}")
public void test(PersonId personId) {
System.out.println("component " + personId.getId()); //gets personId
}
}
And testNG's runner:
#ContextConfiguration(classes = Config.class)
public class TestRunner extends AbstractTestNGSpringContextTests {
#Autowired
test.Test test;
#Test
public void testName() {
test.test(new PersonId("id"));
}
}
When I run it, I get printed "aspect id" from aspect and "component id" from invoked method.
You can also get the value of the PathVariable in RequestMapping URL using HandlerInterceptor if that is possible for you.
Write a Handler class that intercepts this Request.
public class AuthorisedHandler extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (!isResourceHandler(handler) && (handler instanceof HandlerMethod)) {
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
Authorised authAnnotation = method.getAnnotation(Authorised.class);
if (authAnnotation != null) {
String personId = getPersonId(request);
//Do all your validations Here
}
}
return true;
}
#SuppressWarnings("rawtypes")
private String getPersonId(HttpServletRequest request) throws IOException {
String personId = request.getParameter("personId");
if(personId == null || personId.equals("")){
Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
personId = (String) pathVariables.get("personId");
}
return personId;
}
private boolean isResourceHandler(Object handler) {
return handler instanceof ResourceHttpRequestHandler;
}
}
And you must configure this Handler bean in spring config xml or Spring Java Config.
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.**.AuthorisedHandler" />
</mvc:interceptor>
</mvc:interceptors>
Now, all the requests will go through this Interceptor. Only which are annotated with #Authorised will go through.
I am running this code into a junit test. However, the annotations are not found and nothing is outputted. What could cause this.
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Datafield {
}
public class EntityTest
{
#Datafield
StandardFieldText username;
#Test
public void TestEntityAnnotation() throws NoSuchFieldException, SecurityException
{
EntityTest et = new EntityTest();
Annotation[] annos = et.getClass().getAnnotations();
for(Annotation a : annos)
System.out.println(a);
}
}
You're requesting the annotations of EntityTest class which indeed has no annotations.
In order to get the annotation above the field you should try:
Field f = ep.getDeclaredField("username");
Annotation[] annos = f.getDeclaredAnnotations();
You requested the annotations of the class itself. You should loop through methods, fields, etc. to retrieve the annotations of these elements: http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html
For instance:
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Datafield {
}
public class EntityTest
{
#Datafield
StandardFieldText username;
#Test
public void TestEntityAnnotation() throws NoSuchFieldException, SecurityException
{
EntityTest et = new EntityTest();
for(Method m : et.getClass().getDeclaredMethods()) {
Annotation[] annos = m.getDeclaredAnnotations();
for(Annotation a : annos)
System.out.println(a);
}
}
}