Guice Framework-AOP (@Aspect Oriented Programming)

2. AOP Aspect-Oriented Programming

2.1 Getting Started with AOP

In the previous chapters, we mainly talked about Guice's dependency injection. After we have the basis of dependency injection, let's look at Guice's AOP. Let's start with an example to understand the principle and implementation of Guice's AOP in simple terms.

First, we define the service Service, which has a simple method sayHello. Of course, we have a default implementation of ServiceImpl, and then use @ImplementedBy to associate the service with the default implementation, and mark the implementation of the service as a singleton pattern.

1 @ImplementedBy(ServiceImpl.class)

2 public interface Service {

3     void sayHello();

4 }

 

In the service implementation ServiceImpl, our sayHello method is to output a line of information, which contains the class name of the service, hashCode, method name and execution time.

 

 1 @Singleton

 2 public class ServiceImpl implements Service {

 3 

 4     @Override

 5     @Named("log")

 6     public void sayHello() {

 7         System.out.println(String.format("[%s#%d] execute %s at %d", this.getClass().getSimpleName(),hashCode(),"sayHello",System.nanoTime()));

 8     }

 9 

10 }

11 

Next define an AOP implementation. Implement our method interceptor in Aopalliance (the AOP alliance that everyone recognizes). This interceptor LoggerMethodInterceptor does not do anything special, but only records the execution time. Of course, because the execution time is relatively short, we describe it in nanoseconds (although not so precise).

In MethodInvocation we must call the proceed() method so that our service can be executed. Of course, if we want to do some control we can decide whether to call the service code or not.

 1 import static java.lang.System.out;

 3 import org.aopalliance.intercept.MethodInterceptor;

 4 import org.aopalliance.intercept.MethodInvocation;

 5 

 6 public class LoggerMethodInterceptor implements MethodInterceptor {

 7 

 8     @Override

 9     public Object invoke(MethodInvocation invocation) throws Throwable {

10         String methodName = invocation.getMethod().getName();

11 long startTime = System.nanoTime ();

12         out.println(String.format("before method[%s] at %s", methodName, startTime));

13         Object ret = null;

14         try {

15             ret = invocation.proceed();

16         } finally {

17             long endTime=System.nanoTime();

18             out.println(String.format(" after method[%s] at %s, cost(ns):%d", methodName, endTime,(endTime-startTime)));

19         }

20 return right;

21     }

22 }

23 

Finally, our client program, note that here we need to bind an interceptor, this interceptor matches any class annotated with log method. So this is why the implementation method of our service needs to be marked with log.

 

 

 1 public class AopDemo {

 2     @Inject

 3     private Service service;

 4 

 5     public static void main(String[] args) {

 6         Injector inj = Guice.createInjector(new Module() {

 7             @Override

 8             public void configure(Binder binder) {

 9                 binder.bindInterceptor(Matchers.any(),//

10                         Matchers.annotatedWith(Names.named("log")),//

11                         new LoggerMethodInterceptor());

12             }

13         });

14         inj.getInstance(AopDemo.class).service.sayHello();

15         inj.getInstance(AopDemo.class).service.sayHello();

16         inj.getInstance(AopDemo.class).service.sayHello();

17     }

18 }

19 

Our program outputs what we expect.

 

before method[sayHello] at 7811306067456

[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811321912287

after method[sayHello] at 7811322140825, cost(ns):16073369

before method[sayHello] at 7811322315064

[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811322425280

after method[sayHello] at 7811322561835, cost(ns):246771

before method[sayHello] at 7811322710141

[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811322817521

after method[sayHello] at 7811322952455, cost(ns):242314

 

 

A few notes about this result.

 

(1) Due to the use of AOP, our service is no longer the service implementation class we wrote, but an inherited subclass, which should be completed in memory.

 

(2) Except the first call is time-consuming (maybe guice has done a lot of processing internally), other call events are 0 milliseconds (our service itself does nothing).

 

(3) It does complete the AOP function we expect.

 

Our example is here for now, let's take a look at the related concepts of AOP.

 

2.2 AOP related concepts

 

To be honest, AOP has a complete system, there are a lot of concepts, and they are not easy to understand. Here we combine examples and some scenarios to get a general understanding of these concepts.

 

Advice

 

The so-called notification is the function that our aspect needs to complete. For example, the notification in the example 2.1 is the time-consuming execution of the recording method, and this function is called a notification.

 

For example, in many systems, we will record the operation process of the operator, but this recording process does not want to invade the service too much, so we can use AOP to complete it, and our function of recording the log is a notification. In addition to describing the work to be done by the aspect, the advice also needs to describe when the work is performed, such as before, after, before and after the method, or only when an exception is thrown.

 

Joinpoint

 

The join point describes the timing of our notification in the execution of the program. This timing can be described by a "point", which is transient. Usually we have the following types of transients: before the method runs, after the method runs, when an exception is thrown, or a property is read and modified, and so on. It is always our notifications (functions) that insert these points to complete our additional functions or to control our execution flow. For example, in the example in 2.1, our notification (time consumption) not only records the execution time before the method is executed, but also outputs the time consumption after the method is executed. Then we have two connection points, one is when the method runs before, and one after the method runs.

 

Pointcut

 

A pointcut describes the scope of execution of an advice. If advice describes "when" to do "what" and join point describes "when", then pointcut can be understood as "where". For example, in the 2.1 example our entry point is the @Named("log") annotated method of all services managed by the Guice container. This way our notifications are limited to these places, which are called entry points.

 

Aspect

 

An aspect is a combination of advice and pointcut. That is to say, the aspect includes two parts: notification and entry point. It can be seen that the aspect we are talking about is notification and entry point. In other words, when and where to do things.

 

Introduction

 

Introducing means allowing us to add new methods and properties to an existing class. Personally, I think this feature is very powerful, but in most cases it is of little use, because if a class needs a facet to add new methods or properties, then we can have many more elegant ways to bypass this problem, but it is impossible to bypass this problem. You may not care much about this feature.

 

Target

 

The target is the object to be notified, such as the ServiceImpl object in our 2.1 example.

 

Proxy

 

A proxy is a new object created after the target object is notified of a reference. For example, in the 2.1 example, the Service object we got is not ServiceImpl itself, but its wrapped subclass ServiceImpl$$EnhancerByGuice$$96717882.

 

Weaving

 

Weaving is the process of applying aspects to target objects to create new proxy objects. Usually we have several practical to complete the weaving process:

 

Compile time: The weaving process is completed when the Java source file is programmed into a class. AspectJ has a compiler that compiles the bytecode of the aspect into the target bytecode at compile time.

 

On class loading: Aspects are woven when the target class is loaded into the JVM. Since it happens in the class loading process, a special class loader (ClassLoader) is required, and AspectJ supports this feature.

 

Runtime: Aspects are woven at some runtime in the target class. Under normal circumstances, the AOP container will create a new proxy object to complete the function of the target object. In fact, in the 2.1 example, Guice uses this method.

 

The conditions for Guice to support AOP are:

 

Class must be public or package (default) 

A class cannot be final 

Methods must be public, package or protected 

A method cannot be of type final 

Instances must be injected via Guice's @Inject or have a parameterless constructor 

2.3 Aspect injection dependency

 

What if an aspect (interceptor) also needs to inject some dependencies? It doesn't matter, Guice allows you to inject all aspects of dependencies before associating them. Take a look at the example below.

 

We have a front-end service that outputs the names of all called methods.

 

 

1 @ImplementedBy(BeforeServiceImpl.class)

2 public interface BeforeService {

4     void before(MethodInvocation invocation);

5 }

1 public class BeforeServiceImpl implements BeforeService {

3     @Override

4     public void before(MethodInvocation invocation) {

5         System.out.println("before method "+invocation.getMethod().getName());

6     }

7 }

Then there is an aspect, which depends on the front service, and then outputs a method call ending statement.

 

 1 public class AfterMethodInterceptor implements MethodInterceptor {

 2    @Inject

 3     private BeforeService beforeService;

 4     @Override

 5     public Object invoke(MethodInvocation invocation) throws Throwable {

 6         beforeService.before(invocation);

 7         Object ret = null;

 8         try {

 9             ret = invocation.proceed();

10         } finally {

11             System.out.println("after "+invocation.getMethod().getName());

12         }

13 return right;

14     }

15 }

 

 

In AopDemo2 demonstrates how to inject the dependencies of the aspect. On line 9, AfterMethodInterceptor asks Guice to inject its dependencies.

 

 1 public class AopDemo2 {

 2     @Inject

 3     private Service service;

 4     public static void main(String[] args) {

 5         Injector inj = Guice.createInjector(new Module() {

 6             @Override

 7             public void configure(Binder binder) {

 8                 AfterMethodInterceptor after= new AfterMethodInterceptor();

 9                 binder.requestInjection(after);

10                 binder.bindInterceptor(Matchers.any(),//

11                         Matchers.annotatedWith(Names.named("log")),//

12                         after);

13             }

14         });

15         AopDemo2 demo=inj.getInstance(AopDemo2.class);

16         demo.service.sayHello();

17     }

18 }

 

 

Although the aspect allows injecting its dependencies, it should be noted here that if the aspect dependencies still go through the aspect, the program will fall into an infinite loop and the heap will overflow for a long time.

 

2.4 Matches

 

The API that Binder binds to an aspect is

 

com.google.inject.Binder.bindInterceptor(Matcher<? super Class<?>>, Matcher<? super Method>, MethodInterceptor...)

 

The first parameter is the match class, the second parameter is the match method, and the third array parameter is the method interceptor. That is to say, so far Guice can only intercept the method, and then do some aspect work.

 

For Matcher there are the following APIs:

 

com.google.inject.matcher.Matcher.matches (T) 

com.google.inject.matcher.Matcher.and(Matcher<? super T>) 

com.google.inject.matcher.Matcher.or(Matcher<? super T>) 

Among them, the second and third methods have not been found to be useful. It seems that Guice does not apply to them, and I have not fully understood them at present.

 

For the first method, if it matches Class, then T is a type of Class<?>, and if it matches Method, it is a Method object. It's hard to understand. See an example.

 

 1 public class ServiceClassMatcher implements Matcher<Class<?>>{

 2     @Override

 3     public Matcher<Class<?>> and(Matcher<? super Class<?>> other) {

 4         return null;

 5     }

 6     @Override

 7     public boolean matches(Class<?> t) {

 8         return t==ServiceImpl.class;

 9     }

10     @Override

11     public Matcher<Class<?>> or(Matcher<? super Class<?>> other) {

12         return null;

13     }

14 }

 

 

In the previous example, we used the Matchers.any() object to match all classes and identify methods through annotations. Here, we can only match the ServiceImpl class to control the service running process.

 

In fact, there is an abstract Matcher class com.google.inject.matcher.AbstractMatcher<T> in Guice, we only need to override the matches method.

 

In most cases we just need to use the default classes provided by Matchers. There are the following APIs in Matchers:

 

com.google.inject.matcher.Matchers.any(): any class or method 

com.google.inject.matcher.Matchers.not(Matcher<? super T>): a class or method that does not meet this condition 

com.google.inject.matcher.Matchers.annotatedWith(Class<? extends Annotation>): a class or method with this annotation 

com.google.inject.matcher.Matchers.annotatedWith(Annotation): a class or method with this annotation 

com.google.inject.matcher.Matchers.subclassesOf(Class<?>): matches subtypes of this class (including its own type) 

com.google.inject.matcher.Matchers.only(Object): A class or method equal to the specified type (here, the equals method returns true) 

com.google.inject.matcher.Matchers.identicalTo(Object): The class or method of the same type as the specified type (here refers to the same object) 

com.google.inject.matcher.Matchers.inPackage(Package): package the same class 

com.google.inject.matcher.Matchers.inSubpackage(String): classes in subpackages (including this package) 

com.google.inject.matcher.Matchers.returns(Matcher<? super Class<?>>): The return value is a method of the specified type 

Usually we only need to use the above method or combination method to meet our needs.

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327013317&siteId=291194637