铁锤的IT奇妙探险 Java Back-End Coder

Spring AOP概念和配置


AOP的核心概念,AOP配置的多种方式。

Spring AOP

  • AOP代表的是一个横向的关系,如果说“对象”是一个空心的圆柱体,其中封装的是对象的属性和行为;那么面向方面编程的方法,就仿佛一把利刃,将这些空心圆柱体剖开,以获得其内部的消息。而剖开的切面,也就是所谓的“方面”了。
  • 实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
  • AOP 其实就是代理模式的典型应用

切面相当于应用对象间的横切点

相关概念

  1. 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。
  2. 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。(被拦截到的方法)
  3. 通知(Advice): 在特定的连接点,AOP框架执行的动作。(增强连接点,拦截后要做的事情)
  4. 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。(指我们要对哪些连接点进行拦截的定义,所有的切入点都是连接点,但反之不成立,只有增强的连接点才是切入点)
  5. 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口
  6. 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象
  7. AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理
  8. 织入(Weaving): 组装方面来创建一个被通知对象。(指把增强应用到目标对象来创建新的代理对象的过程)

AOP配置

基于 Java API 的方式

此配置方式需要实现相关的接口,例如 MethodBeforeAdvice 和 AfterReturningAdvice,并且在 XML 配置中定义相应的规则即可实现。

public class Person {
   public Person findPerson() {
      Person person = new Person(1, "JDK");
      System.out.println("findPerson 被执行");
      return person;
   }
   public Person(Integer id, String name) {
      this.id = id;
      this.name = name;
   }
   private Integer id;
   private String name;
}

再定义一个 advice 类,用于对拦截方法的调用之前和调用之后进行相关的业务处理

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;

public class MyAdvice implements MethodBeforeAdvice, AfterReturningAdvice {
   @Override
   public void before(Method method, Object[] args, Object target) throws Throwable {
      System.out.println("准备执行方法: " + method.getName());
   }

   @Override
   public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
      System.out.println(method.getName() + " 方法执行结束");
   }

然后需要在 application.xml 文件中配置相应的拦截规则

<!-- 定义 advisor -->
<bean id="myAdvice" class="org.springframework.advice.MyAdvice"></bean>
<!-- 配置规则,拦截方法名称为 find* -->
<bean class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
    <property name="advice" ref="myAdvice"></property>
    <property name="pattern" value="org.springframework.beans.*.find.*"></property>
</bean>

<!-- 定义 DefaultAdvisorAutoProxyCreator 使所有的 advisor 配置自动生效 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>

测试类为

public class MyApplication {
   public static void main(String[] args) {
      ApplicationContext context =
            new ClassPathXmlApplicationContext("classpath*:application.xml");
      Person person = context.getBean("person", Person.class);
      person.findPerson();
   }
}
准备执行方法: findPerson
findPerson 被执行
findPerson 方法执行结束
基于 @AspectJ 注解的方式

首先需要在项目中添加 aspectjweaver 的 jar 包

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

然后开启自动代理功能,一共有两种方式

//配置Spring开启AOP注解的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

基于注解
Configuration
EnableAspectJAutoProxy
public class AppConfig {
}

之后我们需要声明拦截器的类和拦截方法,以及配置相应的拦截规则(定义切面和切点)

//将通知类bean通过注解注入IOC容器
@Componentl("logger")
//定义切面
@Aspect
public class MyAspectJ {

   // 配置拦截类 Person(定义切点)
   @Pointcut("execution(* org.springframework.beans.Person.*(..))")
   public void pointCut() {
   }
	//定义通知
   @Before("pointCut()")
   public void doBefore() {
      System.out.println("执行 doBefore 方法");
   }

   @After("pointCut()")
   public void doAfter() {
      System.out.println("执行 doAfter 方法");

然后在 application.xml 配置中添加注解类,最后添加一个需要拦截的方法

<bean class="org.springframework.advice.MyAspectJ"/>

package org.springframework.beans;

// 需要拦截的 Bean
public class Person {
   public Person findPerson() {
      Person person = new Person(1, "JDK");
      System.out.println("执行 findPerson 方法");
      return person;
   }
    // 获取其他方法
}

开启测试代码

public class MyApplication {
   public static void main(String[] args) {
      ApplicationContext context =
            new ClassPathXmlApplicationContext("classpath*:application.xml");
      Person person = context.getBean("person", Person.class);
      person.findPerson();
   }
}

执行 doBefore 方法
执行 findPerson 方法
执行 doAfter 方法
基于 XML 标签的方式

把相关信息配置到 application.xml 中即可

<!-- 拦截处理类即把通知bean对象也交给spring来管理 -->
<bean id="myPointcut" class="org.springframework.advice.MyPointcut"></bean>

<!-- 配置切面 -->
<aop:config>
    <!-- 拦截规则配置,即切入点表达式 -->
    <aop:pointcut id="pointcutConfig"
                    expression="execution(* org.springframework.beans.Person.*(..))"/>
    <!-- 拦截方法配置 
		1. aspect标签中:id为切面的唯一标识
		2. ref属性是指定通知类bean的id
		3. method用于指定哪个方法是前置或后置通知
		4. pointcut指定要拦截的方法
		5. after-returning:后置,after-throwing:异常,after:最终
	-->
    <aop:aspect ref="myPointcut">
        <aop:before method="doBefore" pointcut-ref="pointcutConfig"/>
        <aop:after method="doAfter" pointcut-ref="pointcutConfig"/>
    </aop:aspect>
</aop:config>

之后,添加一个普通的类来进行拦截业务的处理

public class MyPointcut {
   public void doBefore() {
      System.out.println("执行 doBefore 方法");
   }
   public void doAfter() {
      System.out.println("执行 doAfter 方法");
   }
}

拦截的方法和测试代码与第二种注解的方式相同

原理

Spring AOP 的原理其实很简单,它其实就是一个动态代理,我们在调用 getBean() 方法的时候返回的其实是代理类的实例,而这个代理类在 Spring 中使用的是 JDK Proxy 或 CgLib 实现的,它的核心代码在 DefaultAopProxyFactory.createAopProxy(…) 中

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {

	@Override
	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
		if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
			Class<?> targetClass = config.getTargetClass();
			if (targetClass == null) {
				throw new AopConfigException("TargetSource cannot determine target class: " +
						"Either an interface or a target is required for proxy creation.");
			}
            // 判断目标类是否为接口
			if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                // 是接口使用 jdk 的代理
				return new JdkDynamicAopProxy(config);
			}
            // 其他情况使用 CgLib 代理
			return new ObjenesisCglibAopProxy(config);
		}
		else {
			return new JdkDynamicAopProxy(config);
		}
	}
    // 忽略其他代码
}

Similar Posts

Comments