Spring之Spring AOP

AOP(Aspect-Oriented Programming, 面向切面编程)

Aspect(切面):指对横切关注点的抽象即为切面。

Joinpoint(连接点):被拦截到的点,一般是方法的调用。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置。

Pointcut(切入点):对joinpoint进行拦截的定义,每个类都拥有多个连接点。

例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

Advice(通知):指拦截到joinpoint之后所做的事情就是通知。前置、后置、异常、最终、环绕等。

Target(目标对象):代理的目标对象

Proxy(代理):向目标对象应用通知之后创建的对象

 

AspectJ:Java 社区里最完整最流行的 AOP 框架

 

在Bean 配置文件中定义一个空的 XML 元素 <aop:aspectj-autoproxy>

<aop:aspectj-autoproxy> 元素时, 会自动为与 AspectJ 切面匹配的 Bean 创建代理.

 

 

AspectJ 注解切面声明

要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例.

在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.

通知是标注有某种注解的简单的 Java 方法.

AspectJ 支持 5 种类型的通知注解:

@Before: 前置通知, 在方法执行之前执行

@After: 后置通知, 在方法执行之后执行

@AfterRunning: 返回通知, 在方法返回结果之后执行

@AfterThrowing: 异常通知, 在方法抛出异常之后

@Around: 环绕通知, 围绕着方法执行

 

例子:

@Aspect

public class LogAspects {



//抽取公共的切入点表达式

//1、本类引用

//2、其他的切面引用

@Pointcut("execution(public int com.aop.MathCalculator.*(..))")

public void pointCut(){};



//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)

@Before("pointCut()")

public void logStart(JoinPoint joinPoint){

Object[] args = joinPoint.getArgs();

System.out.println(""+joinPoint.getSignature().getName()+"运行。。。 @Before:参数列表是:{"+Arrays.asList(args)+"}");

}



@After("com.aop.LogAspects.pointCut()")

public void logEnd(JoinPoint joinPoint){

System.out.println(""+joinPoint.getSignature().getName()+"结束。。。 @After");

}



//JoinPoint一定要出现在参数表的第一位

@AfterReturning(value="pointCut()",returning="result")

public void logReturn(JoinPoint joinPoint,Object result){

System.out.println(""+joinPoint.getSignature().getName()+"正常返 回。。。@AfterReturning:运行结果:{"+result+"}");

}



@AfterThrowing(value="pointCut()",throwing="exception")

public void logException(JoinPoint joinPoint,Exception exception){

System.out.println(""+joinPoint.getSignature().getName()+"异常。。。 异常信息:{"+exception+"}");

}



}

AspectJ 切入点表达式

1 execution( * com.spring.ArithmeticCalculator.*(..)): 匹配 ArithmeticCalculator 中声明的所有方法,第一个 * 代表任意修饰符及任意返回值. 第二个 * 代表任意方法. .. 匹配任意数量的参数. 若目标类与接口与该切面在同一个包中, 可以省略包名.

2 execution( public * ArithmeticCalculator.*(..)): 匹配 ArithmeticCalculator 接口的所有公有方法.

3 execution( public double ArithmeticCalculator.*(..)): 匹配 ArithmeticCalculator 中返回 double 类型数值的方法

4 execution( public double ArithmeticCalculator.*(double, ..)): 匹配第一个参数为 double 类型的方法, .. 匹配任意数量任意类型的参数

5 execution( public double ArithmeticCalculator.*(double, double)): 匹配参数类型为 double, double 类型的方法.

 

环绕通知是所有通知类型中功能最为强大的, 能够全面地控制连接点. 甚至可以控制是否执行连接点.

对于环绕通知来说, 连接点的参数类型必须是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允许控制何时执行, 是否执行连接点.

在环绕通知中需要明确调用 ProceedingJoinPoint 的 proceed() 方法来执行被代理的方法. 如果忘记这样做就会导致通知被执行了, 但目标方法没有被执行.

注意: 环绕通知的方法需要返回目标方法执行之后的结果, 即调用 joinPoint.proceed(); 的返回值, 否则会出现空指针异常

 

切面优先级

在同一个连接点上应用不止一个切面时, 它们的优先级是不确定的.

1 切面的优先级可以通过实现 Ordered 接口或利用 @Order 注解指定.

2 实现 Ordered 接口, getOrder() 方法的返回值越小, 优先级越高.

@Aspect

@Order(1)

public class LogAspects{}



@Aspect

public class LogAspects implements Ordered{

public int getOrder() {

return 1;

}

}

切点

切入点方法的访问控制符同时也控制着这个切入点的可见性. 如果切入点要在多个切面中共用, 最好将它们集中在一个公共的类中. 在这种情况下, 它们必须被声明为 public. 在引入这个切入点时, 必须将类名也包括在内. 如果类没有与这个切面放在同一个包中, 还必须包含包名.

 

引入通知

在开发中有时候需要给一些类添加接口,spring提供了一种快速的方法,可以在切面中通过@DeclareParents指定满足指定表达式的类将自动实现某些接口。

Value: 属性值也可以是一个 AspectJ 类型的表达式, 以将一个即可引入到多个类中.  defaultImpl: 属性中指定这个接口使用的实现类。

例如:

下面是新定义了一个Calculator接口

public interface Calculator {

public int get(float a);

}

下面是两种Calculator的具体实现

public class CalculatorImpl implements Calculator{



public int get(float a) {

return Math.round(a);

}

}



public class CalculatorImpl2 implements Calculator{

public int get(float a) {

return (int)Math.floor(a);

}

}

下面是指定切面来做实现MathCalculator动态实现接口

@Aspect

public class LogAspects implements Ordered{

@DeclareParents(value="com.aop.MathCalculator",defaultImpl=CalculatorImpl.class)

private Calculator calculator;

}





public void test01(){

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);

MathCalculator mathCalculator = applicationContext.getBean(MathCalculator.class);

Calculator calculator=(Calculator) mathCalculator;

System.out.println(calculator.get(4.5f));

applicationContext.close();

}

 

如上动态接口的实现,现需要我们声明一个Calculator 类型的属性,然后通过@DeclareParents指定的value匹配类及defaultImpl的具体实现,可以得出不同的结果。

 

XML 的配置声明切面

<aop:config> 元素用来定义切面配置。

<aop:aspect> 元素来为具体的切面定义

XML声明实现:

<!-- 定义LoggingAspect切面bean -->

<bean id="loggingAspect"

class="com.spring.aop.xml.LoggingAspect"></bean>

<aop:config>

<!-- 配置切点表达式 -->

<aop:pointcut expression="execution(* com.spring.aop.xml.ArithmeticCalculator.*(int, int))" 

id="pointcut"/>

<!-- 配置切面及通知 -->

<aop:aspect ref="loggingAspect">

<!-- 前置通知 -->

<aop:before method="beforeMethod" pointcut-ref="pointcut"/>

<!-- 后置通知 -->

<aop:after method="afterMethod" pointcut-ref="pointcut"/>

<!-- 异常通知 -->

<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/>

<!-- 返回通知 -->

<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>

</aop:aspect>

</aop:config>

 

在这里提及一下tx事务的XML配置:

<!-- 2. 配置事务属性 -->

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>

<!-- 根据方法名指定事务的属性 -->

<tx:method name="purchase" propagation="REQUIRES_NEW"/>

<tx:method name="get*" read-only="true"/>

<tx:method name="find*" read-only="true"/>

<tx:method name="*"/>

</tx:attributes>

</tx:advice>



<!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->

<aop:config>

<aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))" 

id="txPointCut"/>

<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>

</aop:config>

 

 

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流光影下

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值