Spring AOP - Access to Repositories autowired field by reflection - java

It's for the first time that Inside an AspectJ I may need to access a local private autowired field of a Repository in order to do some stuff on >exactly< that instance.
I created a pointcut that focuses on each method of every #Repository annotated class. When the pointcut fires, I get the current class instance from which I want to get the bean field.
This is the way:
#Repository
public class MyDao {
#Autowired
private MyBean bean;
public List<Label> getSomething() {
// does something...
}
}
#Aspect
#Component
public class MyAspect {
#Pointcut("within(#org.springframework.stereotype.Repository *)")
public void repositories() {
}
#Before("repositories()")
public void setDatabase(JoinPoint joinPoint) {
try {
Field field = ReflectionUtils.findField(joinPoint.getThis().getClass(), "bean"); // OK since here - joinPoint.getThis().getClass() -> MyDao
ReflectionUtils.makeAccessible(field); // Still OK
Object fieldValue = ReflectionUtils.getField(field, joinPoint.getThis());
System.out.println(fieldValue == null); // true
// should do some stuff with the "fieldValue"
} catch (Exception e) {
e.printStackTrace();
}
}
}
fieldValue is always null even if I create something like private | public | package String something = "blablabla"; instead.
I have ensured that "bean" is actually instantiated when the application starts (verified with the debugger).
I followed How to read the value of a private field from a different class in Java?
What I am missing? | Is it possible? | Are there any different ways?

#springbootlearner suggested this approach access class variable in aspect class
All I had to do is to replace the joinPoint.getThis() with joinPoint.getTarget()
And the final solution is:
#Aspect
#Component
public class MyAspect {
/**
*
*/
#Pointcut("within(#org.springframework.stereotype.Repository *)")
public void repositories() {
}
/**
* #param joinPoint
*/
#Before("repositories()")
public void setDatabase(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
// find the "MyBean" field
Field myBeanField = Arrays.stream(target.getClass().getDeclaredFields())
.filter(predicate -> predicate.getType().equals(MyBean.class)).findFirst().orElseGet(null);
if (myBeanField != null) {
myBeanField.setAccessible(true);
try {
MyBean bean = (MyBean) myBeanField.get(target);// do stuff
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}

Related

Register BeanFactory in SpringBoot

First I'm not sure if it's a good idea to do all this.
Goal is to create some interfaces with annotations to hide legacy position based string access out of a configuration database, without implementing each interface.
Declarative configured Interface:
public interface LegacyConfigItem extends ConfigDbAccess{
#Subfield(length=3)
String BWHG();
#Subfield(start = 3, length=1)
int BNKST();
#Subfield(start = 4, length=1)
int BEINH();
:
}
Base interface for runtime identification
public interface ConfigDbAccess{
}
Dummy implementation without functionality, may change.
public class EmptyImpl {
}
Beanfactory and MethodInvocation interceptor, to handle the unimplemented methods.
#Component
public class InterfaceBeanFactory extends DefaultListableBeanFactory {
protected static final int TEXT_MAX = 400;
#Autowired
private EntityRepo entityRepo;
public <T> T getInstance(Class<T> legacyInterface, String key) {
ProxyFactory factory = new ProxyFactory(new EmptyImpl());
factory.setInterfaces(legacyInterface);
factory.setExposeProxy(true);
factory.addAdvice(new MethodInterceptor() {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
KEY keyAnnotation = invocation.getThis().getClass().getAnnotation(Key.class);
String key= keyAnnotation.key().toUpperCase();
String ptart = invocation.getMethod().getDeclaringClass().getSimpleName();
Vpt result = entityRepo.getOne(new EntityId(ptart.toUpperCase(), schl.toUpperCase()));
Subfield sub = invocation.getMethod().getAnnotation(Subfield.class);
//TODO: Raise missing Subfield annotation
int start = sub.start();
int length = sub.length();
if (start + length > TEXT_MAX) {
//TODO: Raise invalid Subfield config
}
String value = result.getTextField().substring(start,start+length);
return value;
}
});
return (T) factory.getProxy();
}
#Override
protected Map<String, Object> findAutowireCandidates(String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {
Map<String, Object> map = super.findAutowireCandidates(beanName, requiredType, descriptor);
if (ConfigDbAccess.class.isAssignableFrom(requiredType )) {
:
#SpringBootApplication
public class JpaDemoApplication {
#Autowired
private ApplicationContext context;
public static void main(String[] args) {
SpringApplication app = new SpringApplication(JpaDemoApplication.class);
// app.setApplicationContextClass(InterfaceInjectionContext .class);
app.run(args);
}
public class InterfaceInjectionContext extends AnnotationConfigApplicationContext {
public VptInjectionContext () {
super (new InterfaceBeanFactory ());
}
}
So far I got all this stuff working, except when I try to set the applications Context class to my DefaultListableBeanFactory, I'm killing the Spring boot starter web. The application starts, injects the the Autowired fields with my intercepted pseudo implementaition --- and ends.
I think I'm doing something wrong with registering the DefaultListableBeanFactory, but I've no idea how to do it right.
To get this answered:
M. Deinum pointed me to a much simpler solution:
Instead of creating a BeanFactory I installed a BeanPostProcessor with this functioniality.
#RestController
public class DemoRestController {
#Autowired
VptService vptService;
#ConfigItem(key="KS001")
private PrgmParm prgmKs001;
#ConfigItem(key="KS002")
private PrgmParm prgmKs002;
public DemoRestController() {
super();
}
Where the ConfigItem annotation defines the injection point.
Next I created a CustomBeanPostProcessor which scans all incoming beans for
fields having a ConfigItem annotation
#Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
for (Field field : bean.getClass().getDeclaredFields()) {
SHL cfgDef = field.getAnnotation(ConfigItem.class);
if (cfgDef != null) {
Object instance = getlInstance(field.getType(), cfgDef.key());
boolean accessible = field.isAccessible();
field.setAccessible(true);
try {
field.set(bean, instance);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
field.setAccessible(accessible);
}
}
return bean;
}
The getInstnce(field.getType(),cfgDef.key()) creates a proxy with the MethodInterceptor, which does the work.
There are a lot of things to finalize, but all in all it looks good to me.

Get custom method annotation value from junit test

I have a junit test where I'd like to use an annotation on methods to define test settings.
I have a super class of the test class where I have abstracted some processing and where I'd like to read the method annotation values.
I have seen examples of reading method annotations by looping over a class. I'm not sure this will work for what I need. How do I find which test method was called and then read those specific annotation values (TrialMethod.name)?
public class MyUTest extends Processor{
#Test
#TrialMethod(name = "methodToBeTested")
public void testMethod() throws Exception {
//assert stuff
}
}
public class Processor extends TestCase{
private TrialMethodModel trialMethodModel = new TrialMethodModel();
private void setMethodNameByAnnotation() {
Class<?> clazz = this.getClass();
Class<TrialMethod> trialMethodClass = TrialMethod.class;
for (Method method : clazz.getDeclaredMethods()){
if (method.isAnnotationPresent(trialMethodClass)){
trialMethodModel.setName(method.getAnnotation(trialMethodClass).name());
}
}
}
}
#Documented
#Target(ElementType.METHOD)
#Retention(value=RetentionPolicy.RUNTIME)
public #interface TrialMethod {
String name();
}
I learned that you can access the junit method through the junit class. Then getting the annotation value is trivial.
private void setTrialMethodByAnnotation() {
Class<?> clazz = this.getClass();
Class<TrialMethod> trialMethod = TrialMethod.class;
Method method = null;
try {
method = clazz.getMethod(this.getName(),null);
} catch (SecurityException e) {
logger.error(e.getMessage());
} catch (NoSuchMethodException e) {
logger.error(e.getMessage());
}
if(method.isAnnotationPresent(trialMethod)){
trialMethodModel.setName(method.getAnnotation(trialMethod).name());
...
}
}

Optionally inject ContainerRequestContext

In my Jersey application, I'd like to have a ContainerRequestContext instance injected into various objects. In the case that the object in being created outside of the context of a request, I would like null to be injected.
I noticed HK2 has an #Optional annotation that you can annotate dependencies with, and I was hoping that would do the job for, unfortunately it doesn't change the behaviour at all.
public class MyObject {
private final ContainerRequestContext containerRequestContext;
#Inject
public MyObject(#Optional ContainerRequestContext containerRequestContext) {
this.containerRequestContext = containerRequestContext;
}
}
If this object is instantiated outside of a request scope (in my case, a Job run by a Quartz scheduler), then an exception like this gets thrown:
java.lang.IllegalStateException: Not inside a request scope.
It would massively simplify my code if Jersey would just inject null when outside of a request scope, any ideas how to do this?
I've figured out a way of doing it, but it's basically a hack. Instead of having ContainerRequestContext injected, you can instead try to explicitly get a ContainerRequestContext instance from the ServiceLocator, and handle the exception when the context is outside of a request scope.
public class MyObject {
private final Optional<ContainerRequestContext> containerRequestContext;
#Inject
public MyObject(ServiceLocator serviceLocator) {
this.containerRequestContext = getContainerRequestContext(serviceLocator);
}
private Optional<ContainerRequestContext> getContainerRequestContext(ServiceLocator serviceLocator) {
try {
return Optional.of(serviceLocator.getService(ContainerRequestContext.class));
} catch (MultiException e) {
if (e.getCause() instanceof IllegalStateException) {
return Optional.empty();
} else {
throw new ExceptionInInitializerError(e);
}
}
}
}
It's then possible to go one step further and create your own OptionalContainerRequestContext type.
public class OptionalContainerRequestContext {
private final Optional<ContainerRequestContext> containerRequestContext;
#Inject
public OptionalContainerRequestContext(ServiceLocator serviceLocator) {
this.containerRequestContext = getContainerRequestContext(serviceLocator);
}
public ContainerRequestContext get() {
return containerRequestContext.get();
}
public boolean isPresent() {
return containerRequestContext.isPresent();
}
private Optional<ContainerRequestContext> getContainerRequestContext(ServiceLocator serviceLocator) {
try {
return Optional.of(serviceLocator.getService(ContainerRequestContext.class));
} catch (MultiException e) {
if (e.getCause() instanceof IllegalStateException) {
return Optional.empty();
} else {
throw new ExceptionInInitializerError(e);
}
}
}
}
You can then bind it:
bind(OptionalContainerRequestContext.class).to(OptionalContainerRequestContext.class);
And then inject it wherever you need:
public class MyObject {
private final OptionalContainerRequestContext optionalContainerRequestContext;
#Inject
public MyObject(OptionalContainerRequestContext optionalContainerRequestContext) {
this.optionalContainerRequestContext = optionalContainerRequestContext;
}
}
The simple way to deal with optional injection is to #Inject into javax.enterprise.inject.Instance<T>, and then to call instance.isUnsatisfied() before instance.get().

Java - Execute a class method with a specify annotation

I have a android application, but it is not relevant.
I have a class called "Front controller" which will receive some message
through it's constructor. The message, for brievity, could be an integer.
I want somewhere else to create a new controller which will execute
a method based on the integer defined above
public class OtherController {
#MessageId("100")
public void doSomething(){
//execute this code
}
#MessageId("101")
public void doSomethingElse(){
//code
}
}
The front controller could be something like this:
public class FrontController {
private int id;
public FrontController(int id){
this.id=id;
executeProperControllerMethodBasedOnId();
}
public void executeProperControllerMethodBasedOnId(){
//code here
}
public int getId(){
return id;
}
}
So, if the Front Controller will receive the integer 100, it
will execute the method annotated with #MessageId(100). The
front controller don't know exactly the class where this method
is.
The problem which I found is that I need to register somehow
each controller class. I Spring I had #Component or #Controller
for autoloading. After each controllers are register, I need to
call the properly annotated method.
How to achieve this task? In Spring MVC, I had this system
implemented, used to match the HTTP routes. How could I implement
this in a plain java project?
Any suggestions?
Thanks to Google Reflections (hope you can integrate this in your android project.)
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections-maven</artifactId>
<version>0.9.8</version>
</dependency>
For optimisation I've added the requirement to also annotate the class with MessageType annotation and the classes should be in the same package (org.conffusion in my example):
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
public #interface MessageType {
}
The OtherController looks like:
#MessageType
public class OtherController {
#MessageId(id=101)
public void method1()
{
System.out.println("executing method1");
}
#MessageId(id=102)
public void method2()
{
System.out.println("executing method2");
}
}
The implementation will look like:
public void executeProperControllerMethodBasedOnId() {
Set<Class<?>> classes = new org.reflections.Reflections("org.conffusion")
.getTypesAnnotatedWith(MessageType.class);
System.out.println("found classes " + classes.size());
for (Class<?> c : classes) {
for (Method m : c.getMethods()) {
try {
if (m.isAnnotationPresent(MessageId.class)) {
MessageId mid = m.getAnnotation(MessageId.class);
Object o = c.newInstance();
if (mid.id() == id)
m.invoke(o);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Maybe you can optimise and build a static hashmap containing already scanned message ids.
You need to implement some of the work by yourself using reflection, I would recommend to prepare message handlers on initial phase in regards to performance. Also you possibly want to think about Singleton/Per Request controllers. Some of the ways to implement the solution:
interface MessageProcessor {
void execute() throws Exception;
}
/* Holds single instance and method to invoke */
class SingletonProcessor implements MessageProcessor {
private final Object instance;
private final Method method;
SingletonProcessor(Object instance, Method method) {
this.instance = instance;
this.method = method;
}
public void execute() throws Exception {
method.invoke(instance);
}
}
/* Create instance and invoke the method on execute */
class PerRequestProcessor implements MessageProcessor {
private final Class clazz;
private final Method method;
PerRequestProcessor(Class clazz, Method method) {
this.clazz = clazz;
this.method = method;
}
public void execute() throws Exception {
Object instance = clazz.newInstance();
method.invoke(instance);
}
}
/* Dummy controllers */
class PerRequestController {
#MessageId(1)
public void handleMessage1(){System.out.println(this + " - Message1");}
}
class SingletonController {
#MessageId(2)
public void handleMessage2(){System.out.println(this + " - Message2");}
}
class FrontController {
private static final Map<Integer, MessageProcessor> processors = new HashMap<Integer, MessageProcessor>();
static {
try {
// register your controllers
// also you can scan for annotated controllers as suggested by Conffusion
registerPerRequestController(PerRequestController.class);
registerSingletonController(SingletonController.class);
} catch (Exception e) {
throw new ExceptionInInitializerError();
}
}
private static void registerPerRequestController(Class aClass) {
for (Method m : aClass.getMethods()) {
if (m.isAnnotationPresent(MessageId.class)) {
MessageId mid = m.getAnnotation(MessageId.class);
processors.put(mid.value(), new PerRequestProcessor(aClass, m));
}
}
}
private static void registerSingletonController(Class aClass) throws Exception {
for (Method m : aClass.getMethods()) {
if (m.isAnnotationPresent(MessageId.class)) {
MessageId mid = m.getAnnotation(MessageId.class);
Object instance = aClass.newInstance();
processors.put(mid.value(), new SingletonProcessor(instance, m));
}
}
}
/* To process the message you just need to look up processor and execute */
public void processMessage(int id) throws Exception {
if (processors.containsKey(id)) {
processors.get(id).execute();
} else {
System.err.print("Processor not found for message " + id);
}
}
}

Unable to instantiate sub class with parameter

I keep getting the error: java.lang.NoSuchMethodException: com.production.workflow.MyWorkflow.<init>(com.production.model.entity.WorkflowEntity)
I have a constructor that is expecting WorkflowEntity so I'm not able to figure out why it's saying NoSuchMethod. Is there something about constructor inheritance that is preventing this from instantiating?
My instantiation factory:
public static Workflow factory(WorkflowEntity workflowEntity) {
try {
Class<?> clazz = Class.forName(workflowEntity.getClassName()).asSubclass(Workflow.class);
Constructor c = clazz.getConstructor(WorkflowEntity.class);
Object workflowClass = c.newInstance(clazz);
return (Workflow) workflowClass;
} catch (Exception e) {
e.printStackTrace();
logger.severe("Unable to instantiate "+workflowEntity.getClassName()+" class: " + e.getLocalizedMessage());
}
return null;
}
Workflow class:
public class MyWorkflow extends Workflow {
//no constructors
Extended class:
abstract public class Workflow {
protected static final Logger logger = Logger.getLogger(Workflow.class.getName());
private WorkflowEntity entity;
protected WorkflowProcess workflowProcess;
#Autowired
private WorkflowProcessService workflowProcessService;
/* Don't use this one */
public Workflow() { }
/* Default constructor */
public Workflow (WorkflowEntity entity) {
this.entity = entity;
//get first workflow process
//#todo this should factor in rule, for multiple starting points
for (WorkflowProcessEntity workflowProcessEntity : entity.getWorkflowProcesses()) {
workflowProcess = WorkflowProcess.factory(workflowProcessEntity);
break;
}
}
There are two problems in your code:
Constructors are not automatically inherited by subclasses. You need to add the MyWorkflow(WorkflowEntity) constructor to the MyWorkflow class.
Your new instance call needs to be made with the workflowEntity instance (and not the class instance you are giving it now)
Here:
class MyWorkflow extends Workflow {
public MyWorkflow() {
super();
}
public MyWorkflow(WorkflowEntity entity) {
super(entity);
}
}
public static Workflow factory(WorkflowEntity workflowEntity) {
try {
Class<?> clazz = Class.forName(workflowEntity.getClassName())
.asSubclass(Workflow.class);
Constructor<?> c = clazz.getConstructor(WorkflowEntity.class);
Object workflowClass = c.newInstance(workflowEntity);
return (Workflow) workflowClass;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Consider the builder pattern instead of the factory pattern. Here is an example that builds a WorkFlow that takes a WorkflowEntity constructor parameter and builds a workFlow that does not take a WorkFlowEntity pattern (just showing multiple options available via a builder).
public class WorkFlowBuilder
{
private WorkflowEntity constructorParameter;
private Class workflowClass;
public WorkFlowBuilder(Class desiredWorkflowClass)
{
if (desiredWorkflowClass != null)
{
workflowClass = desiredWorkflowClass;
}
else
{
throw new IllegalArgumentException("blah blah blah");
}
}
public void setConstructorParameter(final WorkflowEntity newValue)
{
constructorParameter = newValue;
}
public WorkFlow build()
{
Object workflowObject;
if (constructorParameter != null)
{
Constructor constructor = workflowClass.getConstructor(WorkflowEntity.class);
Object workflowObject;
workflowObject = constructor.newInstance(workflowEntity);
}
else
{
workflowObject = workflowClass.newInstance();
}
return (WorkFlow)workflowObject;
}
}
Use this as follows:
WorkFlowBuilder builder = new WorkFlowBuilder(MyWorkFlow.class);
WorkflowEntity entity = new WorkFlowEntity();
WorkFlow item;
entity... set stuff.
builder.setConstructerParameter(entity)
item = builder.build();
I think you just want to pass in the workflowEntity into the constructor on the newInstance call, instead of the typed Class.
Constructors lost their outside visibility during inheritance.
You need to redefine it in MyWorkflow.
This is done so because sub classes may not support the super class creation process. So super object constructors does not make sense to sub classes and it's even unsafe if they were visible outside.
You should also remove the default constructor if your class can be used if instantiated without WorkflowEntity. Just remove it from Workflow and do not add to MyWorkflow.
UPD
You should also consider using generics to avoid class casting.
public Workflow create(WorkflowEntity workflowEntity) throws
ClassNotFoundException, NoSuchMethodException, SecurityException
, InstantiationException, IllegalAccessException
, IllegalArgumentException, InvocationTargetException {
Class<? extends Workflow> clazz = Class.forName(workflowEntity.getClassName()).asSubclass(Workflow.class);
Constructor<? extends Workflow> c = clazz.getConstructor(WorkflowEntity.class);
Workflow workflowClass = c.newInstance(clazz);
return workflowClass;
}
class WorkflowEntity {
public String getClassName() {
return "className";
};
}
class Workflow {
Workflow(WorkflowEntity entity) {
};
}
class MyWorkflow extends Workflow {
MyWorkflow(WorkflowEntity entity) {
super(entity);
}
}

Categories