Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to add control over invoking of methods in existing sub classes without modifying the sub classes?

I have some BaseClass with some method void doSomething().

There are different ways to foSomething and they are implemented by SubClass1, SubClass2 and SubClass3.

Now I want to add a Boolean active property to the BaseClass so that when doSomething is called on an instance it will just return without doing anything.

I know I can code the BaseClass to have doSomething() that looks something like:

Void doSomething(){
   if (this.getActive()) actuallyDoSomething();
}

And then @Override actuallyDoSomething() instead of @Override doSomething() in the subclasses. but it feels wrong... in the sense that It has already been agreed that the subclasses should provide an implementation for doSomething() and they are not aware of actuallyDoSomething().

I also can have each sub class add an if (!this.getActive()) return; at the beginning of its implementation of doSomething() but this also seems wrong as its common functionality that I would prefer to keep common.

What is the common/best practice way to do this? Can it be done without changing the sub classes?

Update

The focus of the Q is not about the right way to design such functionality (which is quite simple), but on how such functionality can be added to an existing scenario without breaking anything.

active would be true by default, but it is desired that on any instance of any the said sub classes if someone would call setActive(false) then it will become inactive and consecutive calls to .doSomething() will not do anything...

like image 952
epeleg Avatar asked Feb 19 '14 14:02

epeleg


People also ask

Can we override method in same class?

Therefore, you cannot override two methods that exist in the same class, you can just overload them.

How do you override a method in Java we need to define a method in sub class with the?

When a method in a subclass has the same name, same parameters or signature, and same return type(or sub-type) as a method in its super-class, then the method in the subclass is said to override the method in the super-class. Method overriding is one of the way by which java achieve Run Time Polymorphism.

Can we override the the methods of superclass in a subclass *?

If a method cannot be inherited, then it cannot be overridden. A subclass within the same package as the instance's superclass can override any superclass method that is not declared private or final. A subclass in a different package can only override the non-final methods declared public or protected.

How can we protect sub class to override the method of super class explain with example?

We can only use those access specifiers in subclasses that provide larger access than the access specifier of the superclass. For example, Suppose, a method myClass() in the superclass is declared protected . Then, the same method myClass() in the subclass can be either public or protected , but not private .


1 Answers

You want to use @Around advice from AspectJ and do something like this:

// Let subClass instances run normally...
cec.setActive(true);
letThemDoSomething("BEFORE", sbc1, sbc2, sbc3);

// Now change existing scenario...
cec.setActive(false);
letThemDoSomething("AFTER", sbc1, sbc2, sbc3);

This will output:

BEFORE ======
SubClass1: doSomething() called.
SubClass2: doSomething() called.
SubClass3: doSomething() called.

AFTER ======
Blocking instance<1> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<2> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<3> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!

In the following lines, I'll describe how to make this happen with the annotation.
I will also use Spring here. It helps making the configuration quickier and easier.


1- Configuration

Tools and dependencies

Java 7, AspectJ 1.7.4, Spring 4.0.2

Project structure

AspectJ Around advice sample code - project structure

pom.xml

<project ...>

  <properties>
     <maven.compiler.source>1.7</maven.compiler.source>
     <maven.compiler.target>1.7</maven.compiler.target> 

     <spring.version>4.0.2.RELEASE</spring.version>
     <aspectj.version>1.7.4</aspectj.version>
  </properties>

  <dependencies>
    <!-- Spring -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <!-- AspectJ -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>${aspectj.version}</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
  </dependencies>

</project>

2 - Existing code

BaseClass.java

public class BaseClass {
    public void doSomething() {

    }

    public void say(String msg) {
         System.out.println(msg);
    }
}    

SubClassN.java

public class SubClassN extends BaseClass {
    private Integer index;

    public SubClassN(Integer index) {
        this.index = index;
    }

    @Override
    public void doSomething() {
        say("SubClass" + index + ": doSomething() called.");
    }

    public Integer getIndex() {
        return index;
    }
}

3 - Change existing code (without breaking anything...)

Here comes AspectJ and its @Around advice. We'll first ask AsjectJ to call a particular method when any doSomething method is invoked. doSomething can be anywhere in the BaseClass or in any of its child class.

This particular method is called changeExistingScenario. It can have any name. The important here is the annotation placed on it.

A word about the @Around value:

execution(* my.first.spring.aop.aspectj.BaseClass.doSomething(..))

This expression simply indicates the method signature pattern we want to intercept.
It will intercept any doSomething method in BaseClass or child class no matter how many parameters, return type and access modifier.

For more details see: http://guptavikas.wordpress.com/2010/04/15/aspectj-pointcut-expressions/

ChangeExistingCode.java

@Aspect // Mark ChangeExistingCode as the class for modifying the code 
@Component
public class ChangeExistingCode {
    private boolean active;

    public void setActive(boolean active) {
        this.active = active;
    }

    /**
     *
     * This method will be called by AspectJ anytime a `doSomething` method is called.
     *
     * This will give us a chance to decide whether the `doSomething` method should
     * be called or not.
     *
     */
    @Around("execution(* my.first.spring.aop.aspectj.BaseClass.doSomething(..))")
    public void changeExistingScenario(ProceedingJoinPoint joinPoint) throws Throwable {
        // Is active ?
        if (active) { // Yes, let doSomething() run as usual
            joinPoint.proceed();
        } else {// No, block doSomething() invokation
            Signature s = joinPoint.getSignature();

            System.out.format( //
                    "Blocking instance<%d> method: %s#%s(%s) !!\n", //
                    ((SubClassN)joinPoint.getTarget()).getIndex(), //
                    s.getDeclaringTypeName(), //
                    s.getName(), //
                    Arrays.toString(joinPoint.getArgs()) //
                    );
        }
    }
}

4- Let's all the magic appear...

Main.java

@Configuration // Mark the Main class as the class where Spring will find its configuration
@ComponentScan // Ask Spring to look for other components within the Main class package
@EnableAspectJAutoProxy // Let Spring auto configure AspectJ aspects for us...
public class Main {

    private static int subClassCounter;

    public static void main(String[] args) {
        subClassCounter=0;

        GenericApplicationContext  context = new AnnotationConfigApplicationContext(Main.class);

        SubClassN sbc1 = context.getBean(SubClassN.class);
        SubClassN sbc2 = context.getBean(SubClassN.class);
        SubClassN sbc3 = context.getBean(SubClassN.class);

        ChangeExistingCode cec = context.getBean(ChangeExistingCode.class);

        // Let subClass instances run normally...
        cec.setActive(true);
        letThemDoSomething("BEFORE", sbc1, sbc2, sbc3);

        // Now change existing scenario...
        cec.setActive(false);
        letThemDoSomething("AFTER", sbc1, sbc2, sbc3);

        context.close();
    }

    private static void letThemDoSomething(String prefix, SubClassN... existingClasses) {
        System.out.format("%s ======\n", prefix);
        for (SubClassN subClassInstance : existingClasses) {
            subClassInstance.doSomething();
        }
        System.out.println();
    }

    @Bean // Tell Spring to use this method for creating SubClassN instances
    @Scope(BeanDefinition.SCOPE_PROTOTYPE) // Scope prototype force creation of multiple instances
    private static SubClassN buildSubClassN() {
        subClassCounter++;
        return new SubClassN(subClassCounter);
    }
}

Output

BEFORE ======
SubClass1: doSomething() called.
SubClass2: doSomething() called.
SubClass3: doSomething() called.

AFTER ======
Blocking instance<1> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<2> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!
Blocking instance<3> method: my.first.spring.aop.aspectj.SubClassN#doSomething([]) !!

5- References

  • Download full code: http://www.filedropper.com/advicearoundsample

  • AspectJ official site

    • @Around javadoc
    • @Aspect javadoc
  • Spring official site

    • @Bean javadoc
    • @Component javadoc
    • @ComponentScan javadoc
    • @Configuration javadoc
    • @EnableAspectJAutoProxy javadoc
  • Other useful resources that helped writing this answer

    • AspectJ Pointcut Expressions
    • Mkyong - Spring AOP + AspectJ annotation example
like image 156
Stephan Avatar answered Oct 12 '22 12:10

Stephan