方面建议其他方面

时间:2016-03-16 08:47:05

标签: java spring aop spring-aop

我目前正在开发两个使用Spring-AOP的Spring应用程序。我有一个方面允许简单的性能记录,其定义如下:

@Aspect
final class PerformanceAdvice {
    private Logger logger = LoggerFactory.getLogger("perfLogger");

    public Object log(final ProceedingJoinPoint call) throws Throwable {
        logger.info("Logging statistics.");
    }
}

然后可以通过Spring AOP配置使用以下XML创建此建议:

<bean id="performanceAdvice" class="com.acme.PerformanceAdvice" />

<aop:config>
    <aop:aspect ref="performanceAdvice">
        <aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
    </aop:aspect>
</aop:config>

这适用于由Spring创建的类,例如使用@Service注释的类。但我也希望这方面也建议二级项目中的其他方面。我知道Spring并不像their docs中所述那样支持这一点:

  

在Spring AOP中,不可能将方面本身作为其他方面建议的目标。类上的@Aspect注释将其标记为方面,因此将其从自动代理中排除。

因此,我可能需要更强大的功能,例如AspectJ。或者是否有可能让Spring知道这方面并仍然允许建议?从StackOverflow上的许多其他问题(与这个特定问题没有直接关系)我尝试制作方面@Configurable,通过将它们定义为@Component并使用它们来使它们具有Spring感知能力各种XML和插件设置,例如:

<context:spring-configured />
<context:annotation-config/>
<context:load-time-weaver/>
<aop:aspectj-autoproxy/>

我现在已经没想完了。我是否需要撰写成熟的AspectJ方面?如果是这样,它可以使用相同的配置,如Spring,引用现有方面和定义新的切入点?这将非常有用,因此我不必为PerformanceAdvice重新Project 1,但仍会在Project 2中引用并使用它。

关于this comment的修改 为了让自己更清楚,我有以下例子。

我在Project 2中有一项服务。

@Service
public class TargetSpringServiceImpl implements TargetSpringService {
    @Override
    public String doSomeComplexThings(String parameter) {
        return "Complex stuff";
    }
}

当调用此方法时,我有一个方面可以进行一些验证。

@Aspect
public class ValidationAdvice {
    @Autowired
    ValidationService validationService

    public void validate(JoinPoint jp) throws Throwable {
        //Calls the validationService to validate the parameters
    }
}

将以下切入点作为执行:

<bean id="validationAdvice" class="com.acme.advice.ValidationAdvice" />

<aop:config>
    <aop:aspect ref="validationAdvice">
        <aop:before pointcut="execution(* com.acme.service..*(..))" method="validate"/>
    </aop:aspect>
</aop:config>

我希望在PerformanceAdvice log()方法上调用我的ValidationAdvice validate()方法。 doSomeComplexThings()类的TargetSpringService方法上的 NOT 。因为这只是一个额外的切入点。问题在于建议另一方面的方法。

4 个答案:

答案 0 :(得分:0)

首先,Spring不使用aspectJ来实现AOP,而是使用JDK或cglib动态代理。这里的aspectJ样式只是指语法。

Aspectj方面是静态的,并且在编译或类加载时使用字节码注入。

然后spring只能在它管理的对象上应用代理,因为在依赖注入期间应用了动态代理。此外,如果您有2个不同的项目,则必须确保它们共享相同的弹簧上下文,否则它们将被隔离,并且将1个项目中的方面应用于第2个bean将无效。

是的,你可能不得不在这里使用真正的aspectJ方面。出于性能记录的目的,它更合适,因为不会对性能产生影响。

答案 1 :(得分:0)

所以(根据我的理解)你想在第二个项目中重复使用你的第一个项目的建议。但另外您还想为建议添加更多逻辑。这可以通过用a包装项目-1的建议来完成 项目-2中的自定义实现。 您可以通过包含其他建议(请参阅Advice ordering):

来实现

项目1 需要一些小修改(或者实施Ordered,您可以使用@Order注释/当然您也可以注入订单而不是硬编码) :

public class LoggingPerformanceAdvice implements org.springframework.core.Ordered {

    Object log(final ProceedingJoinPoint pjp) throws Throwable {
       // does the logging/statistics
       ...
       Object result = pjp.proceed(pjp.getArgs());
       ...
       return result;
    }

    @Override
    public int getOrder() {
       return Ordered.LOWEST_PRECEDENCE;
    }
}

项目2

中创建自定义建议建议实施
public class AdditionalPerformanceAdvice implements Ordered {

   Object log(final ProceedingJoinPoint pjp) throws Throwable {
      ...
      Object result = pjp.proceed(pjp.getArgs());
      ...
      return result;
   }

    @Override
    public int getOrder() {
       return Ordered.HIGHEST_PRECEDENCE;
    }
}

将组件连接在一起:

<!-- component of project 1 -->
<bean id="loggingPerformanceAdvice" class="com.acme.project1.LoggingPerformanceAdvice"/>
<!-- component of project 2 -->
<bean id="additionalPerformanceAdvice" class="com.acme.project2.AdditionalPerformanceAdvice"/>

<aop:config>
   <aop:aspect ref="loggingPerformanceAdvice">
      <aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
   </aop:aspect>
   <aop:aspect ref="additionalPerformanceAdvice">
      <aop:around pointcut="execution(* com.acme..*(..))" method="log"/>
   </aop:aspect>
</aop:config> 

首先调用Ordered.HIGHEST_PRECEDENCE的方面。

答案 2 :(得分:0)

如果我理解你想对方面建议采取行动,我会做的就是这样(使用aspectj可以改为弹簧注释):

public abstract aspect AspectPerformanceLogging {

    private static Logger LOG = LoggerFactory.getLogger(AspectPerformanceLogging.class);

    before() : methodExecution() {
        LOG.info("Loggin stat");
        doBefore(thisJoinPoint);
        LOG.info("Loggin stat end");
    }

    after() : methodExecution() {
        LOG.info("Loggin stat");
        doAfter(thisJoinPoint);
        LOG.info("Loggin stat end");
    }

    Object around() : methodExecution() {
        LOG.info("Loggin stat");
        Object result = doAround((ProceedingJoinPoint)thisJoinPoint);
        LOG.info("Loggin stat end");
        return result;
    }

    protected abstract pointcut methodExecution();

    protected abstract Object doBefore(JoinPoint joinPoint);

    protected abstract Object doAfter(JoinPoint joinPoint);

    protected abstract Object doAround(ProceedingJoinPoint joinPoint);
}

然后我创建了扩展这方面的其他方面。

答案 3 :(得分:0)

我已经找到了解决问题的两种方法。一个是实际建议的方面,另一个解决问题,但实际上更优雅。

解决方案1:建议方面

AspectJ中,可以编织任何东西。借助AspectJ LTW documentation中所述的META-INF/aop.xml文件,我可以参考方面并按以下方式定义新的切入点。

对项目1的更改

PerformanceAdvice

要允许AspectJ定义新的切入点,建议必须为abstract并且具有可以挂钩的抽象pointcut方法。

@Aspect
final class PerformanceAdvice extends AbstractPerformanceAdvice {
    @Override
    void externalPointcut(){}
}

@Aspect
public abstract class AbstractPerformanceAdvice {
    private Logger logger = LoggerFactory.getLogger("perfLogger");

    @Pointcut
    abstract void externalPointcut();

    @Around("externalPointcut()")
    public Object log(final ProceedingJoinPoint call) throws Throwable {
        logger.info("Logging statistics.");
    }
}

对项目2的更改

META-INF/aop.xml

aop.xml文件定义了一个名为ConcretePerformanceAdvice的新方面。它也扩展了AbstractPerformanceAdvice,但定义了一个新的切入点。然后,在AspectJ IS 可能(与Spring-AOP不同)来定义另一个方面的切入点。

<aspectj>
    <aspects>
        <concrete-aspect name="com.example.project2.ConcretePerformanceAdvice" extends="com.example.project1.AbstractPerformanceAdvice">
            <pointcut name="externalPointcut" expression="execution(* com.example.project2.ValidationAdvice.validate(..))"/>
        </concrete-aspect>
    </aspects>    
    <weaver options="-verbose"/>
</aspectj>

pom.xml

编织方面需要一些仪器。这需要依赖项和插件来执行它。至于依赖性,它如下:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-instrument</artifactId>
    <version>${org.springframework.version}</version>
</dependency>

目前,在测试期间,我通过surefire-plugin进行检测。这需要以下几点:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.8</version>
            <configuration>
                <forkMode>once</forkMode>
                <argLine>
                    -javaagent:"${settings.localRepository}/org/springframework/spring-instrument/${org.springframework.version}/spring-instrument-${org.springframework.version}.jar"
                </argLine>
                <useSystemClassloader>true</useSystemClassloader>
            </configuration>
        </plugin>
    </plugins>
</build>

Spring上下文

要启用加载时编织,还需要激活编织。因此,必须将以下内容添加到Spring上下文中。

<context:load-time-weaver/>

解决方案2:委派给Spring bean

Spring-AOP不允许方面建议其他方面。但它确实允许在Spring @Component上运行建议。所以另一个解决方案是将建议中的验证移动到另一个Spring bean。然后将此Spring bean自动装入建议并执行,但PerformanceAdvice在验证组件上有切入点,而不在验证方面。所以看起来如下:

对项目1的更改

无!

对项目2的更改

该建议自动装配Spring @Component并将其逻辑委托给组件。

@Aspect
public class ValidationAdvice {
    @Autowired
    private ValidatorDefault validatorDefault;

    public void validate(JoinPoint jp) throws Throwable {
        validatorDefault.validate(jp);
    }
}

@Component
public class ValidatorDefault {
    @Autowired
    ValidationService validationService

    public void validate(JoinPoint jp) throws Throwable {
        //Calls the validationService to validate the parameters
    }
}

然后在Spring上下文中,可以在@Component上定义切入点,而ValidationAdvice自动装配@Component

<!-- Scan the package to find the ValidatorDefault component for autowiring -->
<context:component-scan base-package="com.example.project2" />

<bean id="validationAdvice" class="com.example.project2.ValidationAdvice" />
<bean id="performanceAdvice" class="com.example.project1.PerformanceAdvice" />

<aop:config>
    <aop:aspect ref="validationAdvice">
        <aop:before pointcut="execution(* com.acme.service..*.*(..))" method="validate"/>
    </aop:aspect>
    <aop:aspect ref="performanceAdvice">
        <aop:around pointcut="execution(* com.example.project2.ValidatorDefault.validate(..))" method="log"/>
    </aop:aspect>
</aop:config>