Spring AOP: объявление дополнительных методов или полей от имени типа - PullRequest
0 голосов
/ 01 мая 2020

В спецификации Spring AOP упоминается:

Введение: объявление дополнительных методов или полей от имени типа. Spring AOP позволяет вам вводить новые интерфейсы (и соответствующую реализацию) для любого рекомендованного объекта. Например, вы можете использовать введение, чтобы bean-компонент реализовал интерфейс IsModified, чтобы упростить кэширование. (В сообществе AspectJ введение называется объявлением между типами.)

Я не выяснил, как мы можем добавить новое поле для рекомендованного класса, не могли бы вы привести пример если вы сталкивались с?

Ответы [ 2 ]

1 голос
/ 02 мая 2020

Код для демонстрации внедрения Spring AOP привел к длинному ответу. Надеюсь, это поможет

Рассмотрим следующий интерфейс

package rg.test.aop;

public interface UserService {
    void sayHello();
}

и две реализации

UserServiceOneImpl

package rg.test.aop.one;

import org.springframework.stereotype.Service;

import rg.test.aop.UserService;

@Service
public class UserServiceOneImpl implements UserService {

    @Override
    public void sayHello() {
        System.out.println("One");
    }

}

и UserServiceTwoImpl

package rg.test.aop.two;

import org.springframework.stereotype.Service;

import rg.test.aop.UserService;

@Service
public class UserServiceTwoImpl implements UserService {

    @Override
    public void sayHello() {
        System.out.println("Two");
    }

}

, а также интерфейс и его реализация, которые будут представлены в AOP

package rg.test.aop.intro;

public interface LoginTracker {
    String FIELD = "Field";

    Integer incrementLoginCount();

}

Реализация

package rg.test.aop.intro;

public class DefaultLoginTracker implements LoginTracker {

    Integer count = 5;

    @Override
    public Integer incrementLoginCount() {
        return ++count;
    }

}

Spring AOP рекомендует применять одинаково для всех классов в пакете rg.test.aop.two

package rg.test.aop.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;
import org.springframework.stereotype.Component;

import rg.test.aop.intro.DefaultLoginTracker;
import rg.test.aop.intro.LoginTracker;

@Aspect
@Component
public class IntroTestAspect {

    @DeclareParents(value="rg.test.aop.two.*+", defaultImpl=DefaultLoginTracker.class)
    LoginTracker tracker;
}

Реализуемый интерфейс определяется типом аннотированного поля. (Здесь LoginTracker)

Любой компонент соответствующего типа реализует интерфейс LoginTracker.

Теперь, когда вы запускаете следующий тестовый класс

//package and imports

public class IntroductionTest {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(IntroductionConfig.class);

        LoginTracker tracker = ctx.getBean(LoginTracker.class);
        System.out.println(tracker);
        System.out.println("------------------------");
        UserService userOne = (UserService) ctx.getBean("userServiceOneImpl");
        UserService userTwo = (UserService) ctx.getBean("userServiceTwoImpl");

        printDetails(userOne);
        System.out.println("------------------------");
        printDetails(userTwo);

        ctx.registerShutdownHook();

    }

    private static void printDetails(Object obj) throws Exception {
        System.out.println("Is LoginTracker type :"+LoginTracker.class.isAssignableFrom(obj.getClass()));
        System.out.println("Is UserService type :"+UserService.class.isAssignableFrom(obj.getClass()));

        System.out.println("Implemented Interfaces : ");
        for (Class clazz : obj.getClass().getInterfaces()) {
            System.out.println(clazz);
        }

        System.out.println("Methods : ");
        for (Method method : obj.getClass().getMethods()) {
            System.out.println(method);
        }

        System.out.println("Fields : ");
        for (Field field : obj.getClass().getFields()) {
            System.out.println(field);
        }

        // If Advised
        if (Advised.class.isAssignableFrom(obj.getClass())) {
            LoginTracker us = (LoginTracker) obj;
            System.out.println(us.incrementLoginCount());

        }
    }

}

Печатает следующий журнал (Примечание: только соответствующие части журнала скопированы здесь для удобства.)

rg.test.aop.two.UserServiceTwoImpl@c15d8b
------------------------
Is LoginTracker type :false
Is UserService type :true
Implemented Interfaces : 
interface rg.test.aop.UserService
Methods : 
public void rg.test.aop.one.UserServiceOneImpl.sayHello()
Fields : 
------------------------
Is LoginTracker type :true
Is UserService type :true
Implemented Interfaces : 
interface rg.test.aop.UserService
interface rg.test.aop.intro.LoginTracker
interface org.springframework.aop.SpringProxy
interface org.springframework.aop.framework.Advised
interface org.springframework.core.DecoratingProxy
Methods : 
public final java.lang.Integer com.sun.proxy.$Proxy19.incrementLoginCount()
public final void com.sun.proxy.$Proxy19.sayHello()
Fields : 
public static final java.lang.String rg.test.aop.intro.LoginTracker.FIELD
Count :6

Журнал показывает методы и поля, которые были введены для bean-компонента UserServiceTwoImpl.

0 голосов
/ 01 мая 2020

Рассмотрим следующее

public interface Foo {
    public void method();
}

@Service
public class FooService implements Foo {
    @Override
    public void method() {
        System.out.println("FooService method called");
    }
}

@Aspect
@Component
@Scope(value = "prototype")
public class FooAdvice {
    private int count;

    @Around(value = "execution(* com.example.Foo.method())")
    public void advice(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("invocation #" + count);
        count++;
        pjp.proceed();
    }
}

С @Configuration подобным

@ComponentScan
@Configuration
@EnableAspectJAutoProxy
public class Example {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Example.class);
        var foo = ctx.getBean(Foo.class);
        foo.method(); // invocation #0
        foo.method(); // invocation #1
    }
}

Инфраструктура AOP свяжет новый экземпляр FooAdvice (поскольку это прототип bean) с FooService bean, потому что его Around рекомендация соответствует FooService.method().

Каждый раз, когда вы вызываете method, FooAdvice будет запускать и увеличивать count, по существу связывая новое поле с FooService instance.

Обратите внимание, что если FooAdvice не имеет области действия "прототипа", то синглтон FooAdvice может посоветовать несколько бинов, которые соответствуют выражению pointcut его метода advice, разделяя поле между всеми ними .

...