Jay
Jay

Reputation: 2454

dynamic conditional code injection into a java file

I have a java file which has a method that has been coded like this

public void testMethod() {
ClassA objA = new ClassA("objA");
objA.someMethod();
ClassA objB = new ClassA("objB");
objB.someMethod();
ClassA objC = new ClassA("objC");
objC.someMethod();
}

This file has been given to me and I have the liberty of a) changing this file manually and adding code to it b) changing this file automatically using a Java Parser. My aim here is to control the execution of someMethod(). For instance, I want to inject a Configuration object into the class, which will specify which object's someMethod() should be invoked. So, something like this:

Configuration config;

public void testMethod() {
ClassA objA = new ClassA("objA");
if(config.shouldExecuteFor(objA))
objA.someMethod();
ClassA objB = new ClassA("objB");
if(config.shouldExecuteFor(objB))
objB.someMethod();
ClassA objC = new ClassA("objC");
if(config.shouldExecuteFor(objC))
objC.someMethod();
}

The if conditions that are shown above, could be

I am inclined towards (b). I am not sure if I am solving this problem correctly or not, or if there is something that I can do to solve it in a simpler manner by leveraging annotations/aspects (as in 'c' above).

I would appreciate any input in solving this problem.

Upvotes: 0

Views: 740

Answers (1)

Lars Gendner
Lars Gendner

Reputation: 1982

Here is an example using Spring AOP with AspectJ. I created a dummy configuration class MyConfiguration which simply returns an alternating boolean when calling its shouldExecuteFor() method to have some kind of test behavior. The ConfiguredCallAspect uses a call pointcut, which only works in Spring AOP when load time weaving is used. That is why I use XML configuration via applicationContext.xml and not annotations. The -Xreweavable option in aop.xml is also important for that, as well as the two -javaagent arguments passed to the JVM. This became rather complex but I think it does the job you were asking for.

src/experiments/stackoverflow/aspectj/ClassA.java

package experiments.stackoverflow.aspectj;

public class ClassA {

    private String name;

    public ClassA(String name) {
        this.name = name;
    }

    public void someMethod() {
        System.out.println("someMethod(\"" + name + "\") called on " + this);
    }

}

src/experiments/stackoverflow/aspectj/App.java

package experiments.stackoverflow.aspectj;

public class App {

    public void run() {
        ClassA object1 = new ClassA("obj1");
        object1.someMethod();
        ClassA object2 = new ClassA("obj2");
        object2.someMethod();
        ClassA object3 = new ClassA("obj3");
        object3.someMethod();
        ClassA object4 = new ClassA("obj4");
        object4.someMethod();
        ClassA object5 = new ClassA("obj5");
        object5.someMethod();
    }

}

src/experiments/stackoverflow/aspectj/ConfiguredCallAspect.java

package experiments.stackoverflow.aspectj;

import javax.inject.Inject;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Configurable;

@Aspect
@Configurable
public class ConfiguredCallAspect {

    private MyConfiguration config;

    public MyConfiguration getConfig() {
        return config;
    }

    @Inject
    public void setConfig(MyConfiguration config) {
        this.config = config;
    }

    /**
     * Pointcut for all calls to ClassA.someMethod().
     */
    @Around("call(* ClassA.someMethod())")
    public void conditionalExecutionOfSomeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Aspect start");
        if (config.shouldExecuteFor(joinPoint.getTarget())) {
            joinPoint.proceed();
        }
        System.out.println("Aspect end");
    }

}

src/experiments/stackoverflow/aspectj/Main.java

package experiments.stackoverflow.aspectj;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main {

    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        context.getBean(App.class).run();
    }

}

src/experiments/stackoverflow/aspectj/MyConfiguration.java

package experiments.stackoverflow.aspectj;

public class MyConfiguration {

    private boolean executionTrigger = false;

    public boolean shouldExecuteFor(Object anObject) {
        executionTrigger = !executionTrigger;
        System.out.println("should execute " + anObject + "? " + executionTrigger);
        return executionTrigger;
    }

}

src/META-INF/aop.xml

<!DOCTYPE aspectj PUBLIC
        "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
    <weaver
        options="-Xreweavable">
        <include within="experiments.stackoverflow.aspectj..*" />
    </weaver>
    <aspects>
        <aspect name="experiments.stackoverflow.aspectj.ConfiguredCallAspect"/>
    </aspects>
</aspectj>

src/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xmlns:context="http://www.springframework.org/schema/context"
   xmlns:aop="http://www.springframework.org/schema/aop"
   xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

   <aop:aspectj-autoproxy proxy-target-class="true"/>
   <context:load-time-weaver />

   <bean id="configuredCallAspect" class="experiments.stackoverflow.aspectj.ConfiguredCallAspect" factory-method="aspectOf">
        <property name="config"><ref bean="config"/></property>
   </bean>
   <bean id="config" class="experiments.stackoverflow.aspectj.MyConfiguration" />
   <bean id="app" class="experiments.stackoverflow.aspectj.App" />

</beans>

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>experiments</groupId>
    <artifactId>experiments</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <properties>
        <org.springframework.version>4.2.1.RELEASE</org.springframework.version>
        <aspectj.version>1.8.7</aspectj.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-instrument</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Start the program using:

java -javaagent:<path-to>/aspectjweaver-1.8.7.jar -javaagent:<path-to>/spring-instrument-4.2.1.RELEASE.jar experiments.stackoverflow.aspectj.Main

Produced output:

Aspect start
should execute experiments.stackoverflow.aspectj.ClassA@a0bf272? true
someMethod("obj1") called on experiments.stackoverflow.aspectj.ClassA@a0bf272
Aspect end
Aspect start
should execute experiments.stackoverflow.aspectj.ClassA@4a89ef44? false
Aspect end
Aspect start
should execute experiments.stackoverflow.aspectj.ClassA@310a7859? true
someMethod("obj3") called on experiments.stackoverflow.aspectj.ClassA@310a7859
Aspect end
Aspect start
should execute experiments.stackoverflow.aspectj.ClassA@1cbc1dde? false
Aspect end
Aspect start
should execute experiments.stackoverflow.aspectj.ClassA@4971f459? true
someMethod("obj5") called on experiments.stackoverflow.aspectj.ClassA@4971f459
Aspect end

Upvotes: 1

Related Questions