I have a problem with the annotated method in interface
public interface CsvImportService {
#Import
void importFile(Long userId, String organizationId, MultipartFile file, String charset) throws Exception;
}
Implementation of interface
#Service
public class CsvImportServiceImpl implements CsvImportService {
#Override
public void importFile(Long userId, String organizationId, MultipartFile file, String charset) {
...
}
}
I tried to handle it by Spring AOP
#Slf4j
#Aspect
#Component
public class ImportAspect {
#AfterReturning(pointcut = "#annotation(com.backend.annotations.Import)")
public void handleImport(JoinPoint joinPoint) throws Throwable {
LOGGER.info("handle");
}
}
But found out that annotation doesn't go to the impl, so I realized that I want to add the annotation to the realization method and started to write BPP
#Component
public class ImportAnnotationBeanPostProcessor implements BeanPostProcessor {
Map<String, Class<?>> map = new HashMap<>();
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
if (CsvImportService.class.isAssignableFrom(beanClass)) {
map.put(beanName, beanClass);
}
return bean;
}
#SneakyThrows
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = map.get(beanName);
if (beanClass != null) {
for (Method declaredMethod : CsvImportService.class.getDeclaredMethods()) {
if (declaredMethod.isAnnotationPresent(Import.class)) {
Method importMethod = beanClass.getDeclaredMethod(declaredMethod.getName(), declaredMethod.getParameterTypes());
//TODO Add annotation to method
}
}
}
return bean;
}
}
And didn't find a reflection method that adds an annotation to the method.
how am I supposed to do that?
Related
In my aspect method, i need get value of name (param of custom annotation) name = "unit test"
Method call by user:
#Service
#RequiredArgsConstructor
#Slf4j
public class Task {
#CronLogger(name = "unit test")
public void testCronLogger(String param) {
log.info("testCronLogger ...");
}
}
custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CronLogger {
public String name() default "";
}
Aspect method:
#Aspect
#Component
#EnableAspectJAutoProxy
public class CronLoggerAspect {
private static final Logger log = LoggerFactory.getLogger(CronLoggerAspect.class);
#Around("#annotation(CronLogger)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] tab = joinPoint.getArgs();
for (Object object : tab) {
log.debug("CronLogger: {}", object);
}
return joinPoint.proceed();
}
}
Console:
CronLogger: test
testCronLogger ...
How about this (untested, I simply modified your code)?
#Around("#annotation(cronLogger)")
public Object trace(ProceedingJoinPoint joinPoint, CronLogger cronLogger) throws Throwable {
log.debug("CronLogger: {}", cronLogger.name());
return joinPoint.proceed();
}
Please be careful with upper- and lower-case characters. One is an annotation class name, the other a method parameter name.
need get Method and get Annotation of this method.
#Around("#annotation(CronLogger)")
public Object trace(ProceedingJoinPoint joinPoint) throws Throwable {
String name = MethodSignature.class.cast(joinPoint.getSignature()).getMethod().getAnnotation(CronLogger.class)
.name();
log.debug("CronLogger: {}", name);
return joinPoint.proceed();
}
when I use a aspect on an annotation, I cannot use AnnotationUtils.getAnnotation,
//here, I cannot not find PulsarListener
PulsarListener annotation = AnnotationUtils.getAnnotation(method, PulsarListener.class);
and when i remove #Aspect ,then it's ok.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface PulsarListener {
String[] topics() default {};
}
#Aspect
#Slf4j
#Component
#Order(0)
public class MDCAspect {
#Around("#annotation(PulsarListener)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
try {
String requestUUID = MDC.get("requestUUID");
if (StringUtils.isEmpty(requestUUID)) {
String uid = ObjectId.get().toHexString();
MDC.put("requestUUID", uid);
}
return joinPoint.proceed();
} finally {
MDC.clear();
}
}
}
#Component
#Slf4j
public class PulsarConsumer {
#PulsarListener(topics = "${topics}")
public void listen(Message<byte[]> receive) {
//doSomething
}
}
public class PulsarPostProcessor implements BeanPostProcessor {
#Value("${pulsar.service.url}")
private String pulsar_service_url;
#Autowired
private ApplicationContext applicationContext;
#Override
public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
Method[] methods = bean.getClass().getDeclaredMethods();
for (Method method : methods) {
//here , i canot not found PulsarListener
//here is the problem
PulsarListener annotation = AnnotationUtils.getAnnotation(method, PulsarListener.class);
if (annotation != null) {
if (log.isDebugEnabled()) {
log.debug("bean :{},method:{}", beanName, method.getName());
}
}
}
}
I am trying to make a custom annotation like ibatis #Select.
Anyway, In conclusion, the goal is
append some data into the parameter which the method has custom annotation
First take a look end point - ArtistNodeRepository.java
#Repository
public interface ArtistNodeRepository {
#CreateNode(tid = "artist")
public Node create(Map data) throws Exception;
}
What want to do with CreateNode annotation is put data.put("type", "artist") into parameter Map.
Here is the Annotation - CreateNode.java
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
#Documented
public #interface CreateNode {
String[] values() default "";
String tid();
}
To controller annotation, I prepared this BeanPostProcessor - NodeAnnotationProcessor.java
#Component
public class NodeAnnotationProcessor implements BeanPostProcessor {
private ConfigurableListableBeanFactory configurableListableBeanFactory;
#Autowired
public NodeAnnotationProcessor(ConfigurableListableBeanFactory configurableListableBeanFactory) {
super();
this.configurableListableBeanFactory = configurableListableBeanFactory;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
this.scanNodeAnnotation(bean, beanName);
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// this.scanNodeAnnotation(bean, beanName);
return bean;
}
protected void scanNodeAnnotation(Object bean, String beanName){
this.configureMethodAction(bean);
}
private void configureMethodAction(Object bean){
Class<?> managedBeanClass = bean.getClass();
ReflectionUtils.MethodCallback methodCallback = new NodeMethodCallback(configurableListableBeanFactory, bean);
ReflectionUtils.doWithMethods(managedBeanClass, methodCallback);
}
}
I am not clear where to put MethodCallback to postProcessBeforeInitialization or postProcessAfterInitialization. In my thought, it would be in after since I am trying to manipulate parameter of the method
Finally, this is the MethodCallback - NodeMethodCallback.java
public class NodeMethodCallback implements ReflectionUtils.MethodCallback {
private Logger logger = LoggerFactory.getLogger(NodeMethodCallback.class);
private ConfigurableListableBeanFactory beanFactory;
private Object bean;
private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
public NodeMethodCallback(ConfigurableListableBeanFactory beanFactory, Object bean) {
this.beanFactory = beanFactory;
this.bean = bean;
}
#Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
logger.info("doWith method info :: " + String.valueOf(bean) + "." + bean.getClass().getName());
/*
What I expected is Printing ArtistNodeRepository Class with create Method
But It prints something like ...
SessionFlashMapManager
DefaultRequestToViewNameTranslator
...
*/
try {
logger.info("When I call you :: " + method.getName()); // I expect method which contains #CreateNode annotation, but it is not ...
Annotation[] methodAnnotations = method.getDeclaredAnnotations();
boolean isTarget = false;
String tid = "";
for(Annotation anno : methodAnnotations) {
logger.info("annotation Class :: " + anno.getClass().getName());
if(isTarget) break;
if(anno instanceof CreateNode) {
logger.info("CreateNode annotation found");
CreateNode createNode = method.getDeclaredAnnotation(CreateNode.class);
tid = createNode.tid();
isTarget = true;
}
}
if(!isTarget) return;
ReflectionUtils.makeAccessible(method);
/*
Do Somthing with Parameter ...
Do Somthing with Parameter ...
Do Somthing with Parameter ...
Do Somthing with Parameter ...
Do Somthing with Parameter ...
*/
} catch (Exception e ){
logger.error("ERROR", e);
}
}
}
The problem is ... in doWith I could not find ArtistNodeRepository instance.
What should I do with MethodCallback and BeanPostProcessor to achieve the goal?
Good sample codes would be nice as well as good answers.
i think you misunderstood the useage of ReflectionUtils.doWithMethods. it means iterate the class method if match callback. rather than when invoke method callback.
public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
// Keep backing up the inheritance hierarchy.
Method[] methods = getDeclaredMethods(clazz);
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
if (clazz.getSuperclass() != null) {
doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
i think you can use aspect. like this.
#Around("execution(public * org.springframework.data.jpa.repository.JpaRepository+.*(..))")
// #Around("#annotation(Repository)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
CreateNode createNode = method.getAnnotation(CreateNode.class);
if(createNode != null) {
Object[] args = joinPoint.getArgs();
// do your business
}
return joinPoint.proceed();
}
hope can help you
We have a problem with our tests that the field UriInfo is not correctly injected when the resource is wrapped in a TransactionalProxy.
We tried using the SpringResourceFactory but that did not help either.
I tried to extract the relevant classes for this usecase:
public class InMemoryClientFactory implements FactoryBean<InMemoryClientExecutor>{
#Inject
private SessionResource sessionResource;
#Override
public InMemoryClientExecutor getObject() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
Registry registry = dispatcher.getRegistry();
registry.addSingletonResource(sessionResource);
final InMemoryClientExecutor inMemoryClientExecutor = new InMemoryClientExecutor(dispatcher);
}
#Override
public Class getObjectType() {
return InMemoryClientExecutor.class;
}
#Override
public boolean isSingleton() {
return true;
}
}
#Path("session")
public interface SessionResource {
#GET
#Path("{sessionId}")
#Produces({MediaType.APPLICATION_XML})
Response get(#PathParam("sessionId") String sessionId);
#DELETE
#Path("{sessionId}")
Response delete(#PathParam("sessionId") String sessionId);
}
#Service
#Transactional
public class SessionResourceImpl implements SessionResource {
#Context
private UriInfo uriInfo;
#Override
public Response get(String sessionId) {
// uriInfo will be null here
String url = uriInfo.getBaseUriBuilder().path(SessionResource.class).path(SessionResource.class, "delete").build(sessionId)
.toString());
return Response.ok(session).build();
#Override
public Response delete(String sessionId) {
System.out.println("Deleted Session "+1);
}
}
#ContextConfiguration(locations = ["classpath:/META-INF/testContext.xml"])
#Transactional
#RunWith(SpringJUnit4ClassRunner.class)
public class SessionResourceIT {
#Inject
InMemoryRestClientFactory inMemoryClientFactory;
#Inject
SessionResource resource;
#Test
public void test() {
SessionResource resource = inMemoryClientFactory.createProxy(SessionResource.class);
ClientResponse cr = client.get(sessionId);
assertNotNull(cr.getEntity(String.class));
}
}
A possible workaround is to unwrap the transactional proxy for the tests, this works as long as the test itself is annotated with #Transactional. I hope someone has a better solution than this.
public class InMemoryClientFactory implements FactoryBean<InMemoryClientExecutor>{
#Inject
private SessionResource sessionResource;
#Override
public InMemoryClientExecutor getObject() throws Exception {
Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
Registry registry = dispatcher.getRegistry();
registry.addSingletonResource(unwrapProxy(sessionResource));
final InMemoryClientExecutor inMemoryClientExecutor = new InMemoryClientExecutor(dispatcher);
}
#Override
public Class getObjectType() {
return InMemoryClientExecutor.class;
}
#Override
public boolean isSingleton() {
return true;
}
private static Object unwrapProxy(Object bean) throws Exception {
Object result = bean;
/*
* If the given object is a proxy, set the return value as the object
* being proxied, otherwise return the given object.
*/
if (AopUtils.isAopProxy(bean) && bean instanceof Advised) {
Advised advised = (Advised) bean;
result = advised.getTargetSource().getTarget();
}
return result;
}
}
My server doesn't have spring AOP jars and I can't add them. Spring version is 2.0.6.
I want to use proxy for 5 of my services.
What is the best way to do this
An example using a Spring bean post-processor to proxy every bean:
public class ProxifyingPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
Class beanClass = bean.getClass();
if (Proxy.isProxyClass(beanClass)) {
return bean;
}
List<Class<?>> interfaceList = getAllInterfaces(beanClass);
Class[] interfaces = (interfaceList.toArray(new Class[interfaceList.size()]));
return Proxy.newProxyInstance(beanClass.getClassLoader(), interfaces, new InvocationHandler() {
#Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
return method.invoke(bean, objects);
}
});
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private List<Class<?>> getAllInterfaces(Class<?> cls) {
if (cls == null) {
return null;
}
LinkedHashSet<Class<?>> interfacesFound = new LinkedHashSet<Class<?>>();
getAllInterfaces(cls, interfacesFound);
return new ArrayList<Class<?>>(interfacesFound);
}
private void getAllInterfaces(Class<?> cls, HashSet<Class<?>> interfacesFound) {
while (cls != null) {
Class<?>[] interfaces = cls.getInterfaces();
for (Class<?> i : interfaces) {
if (interfacesFound.add(i)) {
getAllInterfaces(i, interfacesFound);
}
}
cls = cls.getSuperclass();
}
}
}
Take a look at java.lang.reflect.Proxy API. Note that it allows to create proxies for interfaces only.
You can implement Dynamic Proxies or CGLib proxies.