Thursday, December 10, 2020

Spring AOP vs AspectJ

This link to the official Spring document is very helpful: https://docs.spring.io/spring-framework/docs/2.0.x/reference/aop.html#aop-understanding-aop-proxies The following paragraph from the above document contains important technical details.

6.8.1.2. Working with multiple application contexts The AnnotationBeanConfigurerAspect used to implement the @Configurable support is an AspectJ singleton aspect. The scope of a singleton aspect is the same as the scope of static members, that is to say there is one aspect instance per classloader that defines the type. This means that if you define multiple application contexts within the same classloader hierarchy you need to consider where to define the bean and where to place spring-aspects.jar on the classpath.

Consider a typical Spring web-app configuration with a shared parent application context defining common business services and everything needed to support them, and one child application context per servlet containing definitions particular to that servlet. All of these contexts will co-exist within the same classloader hierarchy, and so the AnnotationBeanConfigurerAspect can only hold a reference to one of them. In this case we recommend defining the bean in the shared (parent) application context: this defines the services that you are likely want to inject into domain objects. A consequence is that you cannot configure domain objects with references to beans defined in the child (servlet-specific) contexts using the @Configurable mechanism (probably not something you want to do anyway!).

When deploying multiple web-apps within the same container, ensure that each web-application loads the types in spring-aspects.jar using its own classloader (for example, by placing spring-aspects.jar in 'WEB-INF/lib'). If spring-aspects.jar is only added to the container wide classpath (and hence loaded by the shared parent classloader), all web applications will share the same aspect instance which is probably not what you want.

Overview of Spring AOP

This programming style is very confusing because of the two different imlementations of AOP and two different styles of configuration. From the above link, my conclusion is summarized below.

  1. There are basically two implemenations of AOP: AspectJ and Spring AOP.
  2. There are 3 different configuration styles: Aspect language style, @AspectJ annotation style, and XML style.
  3. If choose AspectJ, you have two styles to choose: Aspect language style and @AspectJ. If choose Spring AOP, you have two styles to choose: @AspectJ and XML style.
  4. In Spring AOP, even if you choose the @AspectJ style, it does not mean that you are using the AspectJ implemenation. It is still Spring AOP. See below paragraph in the above link:

6.2.1. Enabling @AspectJ Support To use @AspectJ aspects in a Spring configuration you need to enable Spring support for configuring Spring AOP based on @AspectJ aspects, and autoproxying beans based on whether or not they are advised by those aspects. By autoproxying we mean that if Spring determines that a bean is advised by one or more aspects, it will automatically generate a proxy for that bean to intercept method invocations and ensure that advice is executed as needed.

The @AspectJ support is enabled by including the following element inside your spring configuration:

<aop:aspectj-autoproxy/>

To use XML style, use

   
   <aop:config>
    .....
   </aop:config>
 

Proxy vs Code Weaving

Spring AOP is proxy-based. There are two kinds of implementation for the proxy. JDK dynamic proxy and the CGLIB proxy. If the target object to be proxied implements at least one interface then a JDK dynamic proxy will be used. All of the interfaces implemented by the target type will be proxied. If the target object does not implement any interfaces then a CGLIB proxy will be created.

You can force the use of CGLIB proxies in configuration. See the referred link.

Because Spring AOP is proxy based, there is the self-invocation issue. See "6.6.1. Understanding AOP proxies" of the linked document. AspectJ does not have this self-invocation issue.

AspectJ is not proxy-based. AspectJ uses AspectJ compiler/weaver. There are two kinds of weaving: build-time weaving and load-time weaving. The build-time weaving happens when you compile the application. The load-time weaving happens when the class is loaded into JVM. Further, for load-time weaving, there are two ways of the implemenation: (1) weaving at the JVM launch time (2) weaving when a class is loaded into JVM after JVM started.

See https://stackoverflow.com/questions/5717883/aspectj-compiler-ajc-vs-load-time-weaving#:~:text=1%20Answer&text=ajc%20(compile%20time)%20will%20only,that%20are%20affected%20by%20aspects.&text=The%20reason%20is%20that%20under,is%20not%20necessary%20under%20CTW.

Mix Spring AOP and AspectJ

What if you do want to use AspectJ instead of or in addition to Spring AOP? Section "6.8. Using AspectJ with Spring applications" of the linked document covers this.

AspectJ

AspectJ is an example of using the Java Instrument feature. This feature is a fundamentally separate from other Java programming areas. See JavaDoc https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html for more information.

To use this feature, there are two options:

  1. To specify an agent on the command line when a JVM is launched.
  2. To configure the application the JVM will use it after JVM is launched.
See https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html.

The pure AspectJ uses the first option. You need to specify the agent on the command line to start JVM.

The Spring support for AspectJ adds the second option so you can weave the Aspects when the class is loaded into JVM. This enables the applicaiton to do AspectJ on the classloader level.

An Example

Below is an example in to create an AOP class in a Spring boot application. It will set customized information when a database connection is created in Oracle.

To use this class, add the following dependency to the pom.xml file:

        <dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
  
The AOP class is below. It uses pure annotation.

import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

@Aspect
@Configuration
public class ClientIdentifierConnectionPreparer {

	private static final Logger LOGGER = LoggerFactory.getLogger(ClientIdentifierConnectionPreparer.class);

	private static String CLIENT_IDENTIFIER = "-MyApplication-";
	private static String CLASS_NAME = "ClientIdentifierConnectionPreparer.CLASS";
	private static String METHOD_NAME = "afterGetConnection(JoinPoint joinPoint, Object result)";

	@AfterReturning(value = "execution(java.sql.Connection javax.sql.DataSource.getConnection(..))", returning = "result")
	public void afterGetConnection(JoinPoint joinPoint, Object result) throws SQLException {
		Connection connection = (Connection) result;
		
		String prepSql = "{ call DBMS_SESSION.SET_IDENTIFIER(?) }";
		try (CallableStatement cs = connection.prepareCall(prepSql)) {
			cs.setString(1, CLIENT_IDENTIFIER);
			cs.execute();
		}

		prepSql = "{ call DBMS_APPLICATION_INFO.SET_ACTION(?)}";
		try (CallableStatement cs = connection.prepareCall(prepSql)) {
			cs.setString(1, METHOD_NAME);
			cs.execute();
		}

		prepSql = "{ call DBMS_APPLICATION_INFO.SET_CLIENT_INFO(?)}";
		try (CallableStatement cs = connection.prepareCall(prepSql)) {
			cs.setString(1, CLASS_NAME);
			cs.execute();
		}
	}

}
When there is a connection created, you can use the following query on Oracle to see the session with the specified client identifier.
  select * from v$session where client_identifier = '-MyApplication-'

The following query can show the actual queries used in the session.

select 
    se.sid,
    se.username,
    se.machine,
    se.seconds_in_wait,
    se.state,
    se.prev_exec_start,
    oc.cursor_type,
    trim(sql_text) sql_txt
from dual
    join v$session se
        on client_identifier = '-MyApplication-'
    join v$open_cursor oc
        on oc.sql_id = se.prev_sql_id and oc.sid = se.sid