欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

AOP: Aspect Oriented Programming

程序员文章站 2022-05-13 09:11:23
...
   
The AspectJ Programming Guide:
http://www.eclipse.org/aspectj/doc/next/progguide/index.html


Spring ref 9 - Aspect Oriented Programming with Spring:
http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html
引用
CORE concepts:
Aspect: a modularization of a concern that cuts across multiple classes. Transaction management is a good example of a crosscutting concern in enterprise Java applications. In Spring AOP, aspects are implemented using regular classes (the schema-based approach) or regular classes annotated with the @Aspect annotation (the @AspectJ style).
Join point: a point during the execution of a program, such as the execution of a method or the handling of an exception. In Spring AOP, a join point always represents a method execution.
Advice: action taken by an aspect at a particular join point. Different types of advice include "around," "before" and "after" advice. (Advice types are discussed below.) Many AOP frameworks, including Spring, model an advice as an interceptor, maintaining a chain of interceptors around the join point.
Pointcut: a predicate that matches join points. Advice is associated with a pointcut expression and runs at any join point matched by the pointcut (for example, the execution of a method with a certain name). The concept of join points as matched by pointcut expressions is central to AOP, and Spring uses the AspectJ pointcut expression language by default.
Introduction: declaring additional methods or fields on behalf of a type. Spring AOP allows you to introduce new interfaces (and a corresponding implementation) to any advised object. For example, you could use an introduction to make a bean implement an IsModified interface, to simplify caching. (An introduction is known as an inter-type declaration in the AspectJ community.)
Target object: object being advised by one or more aspects. Also referred to as the advised object. Since Spring AOP is implemented using runtime proxies, this object will always be a proxied object.
AOP proxy: an object created by the AOP framework in order to implement the aspect contracts (advise method executions and so on). In the Spring Framework, an AOP proxy will be a JDK dynamic proxy or a CGLIB proxy.
Weaving: linking aspects with other application types or objects to create an advised object. This can be done at compile time (using the AspectJ compiler, for example), load time, or at runtime. Spring AOP, like other pure Java AOP frameworks, performs weaving at runtime.
Types of advice:
Before advice: Advice that executes before a join point, but which does not have the ability to prevent execution flow proceeding to the join point (unless it throws an exception).
After returning advice: Advice to be executed after a join point completes normally: for example, if a method returns without throwing an exception.
After throwing advice: Advice to be executed if a method exits by throwing an exception.
After (finally) advice: Advice to be executed regardless of the means by which a join point exits (normal or exceptional return).
Around advice: Advice that surrounds a join point such as a method invocation. This is the most powerful kind of advice. Around advice can perform custom behavior before and after the method invocation. It is also responsible for choosing whether to proceed to the join point or to shortcut the advised method execution by returning its own return value or throwing an exception.

关于 advice types 的不错解释:
http://*.com/questions/12643620/throwing-exceptions-in-spring-aop
引用
try {
    //@Before
    method();
    //@AfterReturning
} catch(Throwable t) {
    //@AfterThrowing
} finally {
    //@After
}


Spring AOP是基于Proxy的,所以无法拦截内部方法调用(internal method call),即下面的对Service.b()的intercept是不起作用的:(google key words:spring aop internal method call)
@Component
@Aspect
public class Advice {
    @Around("execution(* somePackages.Service.b(..))")
    public Object swallowThrowing(ProceedingJoinPoint pjp) {
       try {
            return pjp.proceed();
       } catch (Throwable e) {
          //do something
       }
}

public class Service {
  public void a() {
    b(); //equivalent to this.b(); any call to "this" from within your service instance is directly invoked on that instance and cannot be intercepted by the wrapping proxy (the proxy is not even aware of any such call)
  }
  
  public void b() {
  }
}
想要拦截内部方法调用请使用aspectJ的load-time weaving(或者compile time weaving?)。
SPring aop ref 9.6.1 Understanding AOP proxies:
http://static.springsource.org/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies
Spring AOP top problem #1 - aspects are not applied:
http://denis-zhdanov.blogspot.com/2009/07/spring-aop-top-problem-1-aspects-are.html
http://*.com/questions/14111422/spring-aop-does-not-intercept-methods-within-springs-container
http://*.com/questions/7338760/spring-aop-intercepting-calls-from-within-the-same-service-class
引用
Once inside a class instance, any method-calls to methods in the same instance will be directly against the actual instance object, not the wrapping proxy so AOP advices will not be considered.



在任意地方(特指不被spring管理、无法获取到ApplicationContext的地方) new 另外一个新的对象,并为 new 出的对象注入 Spring bean:
http://*.com/questions/11755152/spring-instrument-and-auto-injection-in-new-object
Injection on new-created objects (rather than managed beans) is only possible with compile- or load-time weaving, not with the run-time proxies that Spring uses by default.
引用
1 load-time weaving:
http://*.com/questions/4703206/spring-autowiring-using-configurable
需要注入的类上写上 @Configurable / @Autowired:
//new BackgroundFactory(), 则 由 spring ioc 管理的 UserService 实例会自动被注入进来
@Configurable(preConstruction=true, autowire=Autowire.BY_NAME,dependencyCheck=true)
public class BackgroundFactory implements Factory<List<Background>> {

    private static final Logger logger = LoggerFactory.getLogger(BackgroundFactory.class);

    @Autowired
    private UserService userService;

    @Override
    public List<Background> create(Integer userId) {
        List<Background> backgrounds = userService.findBackgroundsByUserId(userId);
        return backgrounds;
    }

    public UserService getUserService() {
        return userService;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

}
applicationContext.xml 中:
<context:spring-configured/>
<context:load-time-weaver weaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver" aspectj-weaving="on"/>
<context:component-scan base-package="。。。" />
在tomcat 中需要做的(否则会报 [org.apache.catalina.loader.WebappClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method,参见:http://*.com/questions/14176417/spring-maven-project-beancreationexception http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/aop.html#aop-aj-ltw-environments):
引用
1 copy org.springframework.instrument.tomcat.jar into $CATALINA_HOME/lib;
2 tomcat 的 server.xml 中为 Context 标签指定 spring 的 Loader:
<Context docBase="xxx-webapp" path="/xxx-webapp" reloadable="true" source="org.eclipse.jst.jee.server:xxx-webapp">
	<Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/>
</Context>
Maven depen:
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
    </dependency>
load-time weaving时,spring xml 中 <context:spring-configured/> 和 <context:load-time-weaver /> 两个标签都可以用各自对应的注解 @EnableSpringConfigured 和 @EnableLoadTimeWeaving 来替代,即:移除 spring xml 中这两个标签,而在 BackgroundFactory 类上使用 @EnableSpringConfigured 和 @EnableLoadTimeWeaving:
//new BackgroundFactory(), 则 由 spring ioc 管理的 UserService 实例会自动被注入进来
@EnableSpringConfigured
@EnableLoadTimeWeaving(...)
@Configurable(preConstruction=true, autowire=Autowire.BY_NAME,dependencyCheck=true)
public class BackgroundFactory implements Factory<List<Background>> {
...
但需要说明的是,Compile-time weaving 时,不能用 @EnableSpringConfigured!必须是在xml中用 <context:spring-configured/>!@EnableSpringConfigured 这个注解只适用于和 @EnableLoadTimeWeaving 配合使用的 load-time weaving 的情况!
2 Compile-time weaving
因为是在编译时织入,所以如果你是用maven来做项目的build的,则需要在项目的 pom.xml  中为项目添加编译时的aspectj支持:
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjrt</artifactId>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>persistence-api</artifactId>
      <version>1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.5</version>
        <configuration>
          <source>1.6</source>
          <target>1.6</target>
          <complianceLevel>1.6</complianceLevel>
          <Xlint>ignore</Xlint>
          <showWeaveInfo>true</showWeaveInfo>
          <aspectLibraries>
            <aspectLibrary>
              <groupId>org.springframework</groupId>
              <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
          </aspectLibraries>
        </configuration>
        <executions>
          <execution>
            <goals>
              <goal>compile</goal>
              <goal>test-compile</goal>
            </goals>
          </execution>
        </executions>
        <dependencies>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
          </dependency>
          <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjtools</artifactId>
            <version>${aspectj.version}</version>
          </dependency>
        </dependencies>
      </plugin>
    </plugins>
  </build>

</project>
你应该有些奇怪这里为什么要依赖 persistence-api;这是 spring-aspects 的一个bug;如果不引入persistence-api依赖,则在 m2e 下会报 can't determine annotations of missing type javax.persistence.Entity(最开始我在 m2e 和 mvn 下都报这个错,但后来在不依赖 persistence-api 的情况下 mvn 不报这个错了,m2e 还是报;有些奇怪,尚未搞清楚为什么)。详见:
http://www.kubrynski.com/2013/09/injecting-spring-dependencies-into-non.html
https://jira.springsource.org/browse/SPR-6819
spring applicatonContext.xml 中:
<!-- 如上所说,这里只能用<context:spring-configured />的方式,而不能使用@EnableSpringConfigured -->
  <context:spring-configured />
  <context:component-scan base-package="。。。" />
BackgroundFactory 类跟上面的一样,添加 @Configurable & @Autowired 注解即可:
//new BackgroundFactory(), 则 由 spring ioc 管理的 UserService 实例会自动被注入进来
@Configurable(preConstruction=true, autowire=Autowire.BY_NAME, dependencyCheck=true)
public class BackgroundFactory implements Factory<List<Background>> {

    private static final Logger logger = LoggerFactory.getLogger(BackgroundFactory.class);

    @Autowired
    private UserService userService;
。。。
如果你在eclipse中使用了m2e,会发现m2e无法识别aspectj-maven-plugin这个plugin的 <execution>,会报错误 Plugin execution not covered by lifecycle configuration: org.codehaus.mojo:aspectj-maven-plugin:1.4:compile (execution: default, phase: compile);这是因为你的eclipse中尚未安装 m2t 的 AJDT Connector,参考这里安装即可:
http://*.com/questions/6522540/maven-ajdt-project-in-eclipse
引用
Step1:install AJDT(AspectJ development tools) in eclipse
Step2: install "AJDT m2e Configurator", repo url:http://dist.springsource.org/release/AJDT/configurator/
总结:Load-time weaving 因为是在类加载时织入,所以如果是在web war项目,则需要 web container 支持 Load-time weaving 才行;如果如上面的 tomcat 的例子一样,其本身不支持,则需要改变其依赖的lib和server.xml,这在 prod env 下很可能不是你能决定的,所以,首选 Compile-time weaving。
另外,如果你是在一个可获取到 ApplicationContext 的 spring bean 中 new 一个对象,并想将一些依赖注给该new出的对象,可以使用 AutowireCapableBeanFactory:
http://*.com/questions/3813588/how-to-inject-dependencies-into-a-self-instantiated-object-in-spring
http://*.com/questions/3693971/can-i-inject-a-java-object-using-spring-without-any-xml-configuration-files
http://*.com/questions/129207/getting-spring-application-context



good stuffs:
Spring AOP: Dynamic Proxies vs. CGLib proxies:
http://insufficientinformation.blogspot.com/2007/12/spring-dynamic-proxies-vs-cglib-proxies.html
一个 After Throwing Advice 的例子:
http://www.captaindebug.com/2011/09/using-aspectjs-afterthrowing-advice-in.html#.UOjMtbTMh1N
Logging Standard Exceptions with Spring AOP:
http://tech.tejusparikh.com/post/10920309943/logging-standard-exceptions-with-spring-aop