开发AOP的途径--AspectJ开发 (2) (超详细)


AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。 新版本的Spring框架,也建议使用AspectJ来开发AOP。使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。接下来的两个小节中,将对这两种AspectJ的开发方式进行讲解。
所需要的框架包
提取码:b2gq

1.1基于XML的声明式AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在aop:config元素内。aop:config元素及其子元素如下图所示。
在这里插入图片描述
Spring 配置文件中的元素下可以包含多个aop:config元素,一个aop:config元素中又可以包含属性和子元素,其子元素包括aop:pointcut、aop:advisor和aop:aspect。在配置时,这3个子元素必须按照此顺序来定义。在aop:aspect元素下,同样包含了属性和多个子元素,通过使用aop:aspect元素及其子元素就可以在XML文件中配置切面、切入点和通知。图中灰色部分标注的元素即为常用的配置元素,这些常用元素的配置代码如下所示。
在这里插入图片描述
了解了如何在XML中配置切面、切入点和通知后,接下来通过一个案例来演示如何在Spring
中使用基于XML的声明式AspectJ,具体实现步骤如下。
(1)导入AspectJ框架相关的JAR包,具体如下。
●spring-aspects-4.3.6.RELEASE.jar: Spring 为AspectJ提供的实现,Spring的包中已经提供。
●aspectiweaver-1.8.10.jar: 是AspectJ框架所提供的规范,读者可以通过网址"http://mvnrepository.com/artifact/org aspecti/aspectjweaver/1.8.10"下载。
(2)在创建的项目的src目录下,创建一一个com.itheima.aspectj.xml包,在该包中创建切面类MyAspect,并在类中分别定义不同类型的通知.

MyAspect.java

package com.itheima.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/**
 *切面类,在此类中编写通知
 */
public class MyAspect {
	// 前置通知
	public void myBefore(JoinPoint joinPoint) {
		System.out.print("前置通知 :模拟执行权限检查...,");
		System.out.print("目标类是:"+joinPoint.getTarget() );
		System.out.println(",被织入增强处理的目标方法为:"
                            +joinPoint.getSignature().getName());
	}
	// 后置通知
	public void myAfterReturning(JoinPoint joinPoint) {
		System.out.print("后置通知:模拟记录日志...," );
		System.out.println("被织入增强处理的目标方法为:"
                          + joinPoint.getSignature().getName());
	}
	/**
	 * 环绕通知
	 * ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
	 * 1.必须是Object类型的返回值
	 * 2.必须接收一个参数,类型为ProceedingJoinPoint
	 * 3.必须throws Throwable
	 */
	public Object myAround(ProceedingJoinPoint proceedingJoinPoint) 
             throws Throwable {
		// 开始
		System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
		// 执行当前目标方法
		Object obj = proceedingJoinPoint.proceed();
		// 结束
		System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
		return obj;
	}
	// 异常通知
	public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
		System.out.println("异常通知:" + "出错了" + e.getMessage());
	}
	// 最终通知
	public void myAfter() {
		System.out.println("最终通知:模拟方法结束后的释放资源...");
	}
}

applicationContext.java

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<!-- 1 目标类 -->
	<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
	<!-- 2 切面 -->
	<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
	<!-- 3 aop编程 -->
	<aop:config>
        <!-- 配置切面 -->
		<aop:aspect ref="myAspect">
		  <!-- 3.1 配置切入点,通知最后增强哪些方法 -->
		  <aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))"
				                                      id="myPointCut" />
			<!-- 3.2 关联通知Advice和切入点pointCut -->
			<!-- 3.2.1 前置通知 -->
			<aop:before method="myBefore" pointcut-ref="myPointCut" />
			<!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值
			 returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
			<aop:after-returning method="myAfterReturning"
				pointcut-ref="myPointCut" returning="returnVal" />
			<!-- 3.2.3 环绕通知 -->
			<aop:around method="myAround" pointcut-ref="myPointCut" />
			<!-- 3.2.4 抛出通知:用于处理程序发生异常-->
			<!-- * 注意:如果程序没有异常,将不会执行增强 -->
			<!-- * throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
			<aop:after-throwing method="myAfterThrowing"
				pointcut-ref="myPointCut" throwing="e" />
			<!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
			<aop:after method="myAfter" pointcut-ref="myPointCut" />
		</aop:aspect>
	</aop:config>
</beans>

TestXMLAspectJ.java

package com.itheima.aspectj.xml;
import org.springframework.context.ApplicationContext;
import 
    org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
// 测试类
public class TestXmlAspectj {
	public static void main(String args[]) {
		String xmlPath = 
                         "com/itheima/aspectj/xml/applicationContext.xml";
		ApplicationContext applicationContext = 
                          new ClassPathXmlApplicationContext(xmlPath);
		// 1 从spring容器获得内容
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		// 2 执行方法
		userDao.addUser();
	}
}

输出结果如下:
在这里插入图片描述

1.2基于注解的声明式AspectJ

与基于代理类的AOP实现相比,基于XML的声明式ApectJ要便捷得多,但是它也存在着一些缺点,那就是要在Spring文件中配置大量的代码信息。为了解决这个问题,AspectJ 框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。关于AspectJ注解的介绍,如下表所示。
在这里插入图片描述
随意创建一个Web项目,创建com.wz.aspectj.targrt和
在com.wz.aspectj.tagert包中创建接口UserDao

package com.wz.aspectj.target;

public interface UserDao {
		public void addUser();
		public void deleteUser();
}

在com.wz.aspectj.target包中创建接口实现类UserDaolmpl,本例将实现类作为目标类,添加注解@Repository(“userDao”)

package com.wz.aspectj.target;
import org.springframework.stereotype.Repository;
/*
 * 目标类
 * 将实现类作为目标类,将其中的方法进行增强处理
 */
@Repository("userDao")
public class UserDaoImpl implements UserDao{
		public void addUser(){
			System.out.println("添加超级VIP用户");
			System.out.println("朱世纪");
		}
		public void deleteUser(){
			System.out.println("删除平民用户");
			System.out.println("朱世纪");
		}
}

在com.wz.aspectj.annotation包中创建切面类MyAspectJ.java

package com.wz.aspectj.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/*
 *切面类,在此类中编写五种通知:前置通知,后置通知,环绕通知,异常通知,最终通知
 *@Aspect 	   	定义MyAspect为切面类
 *@Component    定义MyAspect为组件,与@Aspect配合使用
 *@Pointcut   	定义切入点表达式
 */
@Aspect
@Component
public class MyAspect {

	// 定义切入点表达式
	@Pointcut("execution(* com.wz.aspectj.target.*.*(..))")
	//定义切入点名称:通过定义方法来表示切入点名称 (一个返回值为void、方法体为空的方法)
	//切入点名称将作为参数传递给需要执行增强的通知方法
	private void myPointCut(){}
	// 前置通知
	@Before("myPointCut()")
	public void myBefore(JoinPoint joinPoint) {
		System.out.print("前置通知 :模拟执行权限检查...,");
		System.out.print("目标类是:"+joinPoint.getTarget() );
		System.out.println(",被织入增强处理的目标方法为:"
		               +joinPoint.getSignature().getName());
	}
	// 后置通知
	@AfterReturning(value="myPointCut()")
	public void myAfterReturning(JoinPoint joinPoint) {
		System.out.print("后置通知:模拟记录日志...," );
		System.out.println("被织入增强处理的目标方法为:"
		              + joinPoint.getSignature().getName());
	}
	// 环绕通知	
	@Around("myPointCut()")
	public Object myAround(ProceedingJoinPoint proceedingJoinPoint) 
            throws Throwable {
		// 开始
		System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
		// 执行当前目标方法
		Object obj = proceedingJoinPoint.proceed();
		// 结束
		System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
		return obj;
	}
	// 异常通知
	@AfterThrowing(value="myPointCut()",throwing="e")
	public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
		System.out.println("异常通知:" + "出错了" + e.getMessage());
	}
	// 最终通知
	@After("myPointCut()")
	public void myAfter() {
		System.out.println("最终通知:模拟方法结束后的释放资源...");
	}
}

首先使用@Aspect注解定义了切面类,由于该类在Spring中是作为组件使用的,所以还需要添加@Component注解才能生效。然后使用了@Poincut注解来配置切入点表达式,并通过定义方法来表示切入点名称。接下来在每个通知相应的方法上添加了相应的注解,并将切入点名称"'myPointCut"作为参数传递给需要执行增强的通知方法。如果需要其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。

在com.wz.aspectj.annotation包中创建配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  xmlns:aop="http://www.springframework.org/schema/aop"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
  http://www.springframework.org/schema/aop 
  http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
  http://www.springframework.org/schema/context 
  http://www.springframework.org/schema/context/spring-context-4.3.xsd">
      <!-- 指定需要扫描的包,使注解生效 -->
      <context:component-scan base-package="com.wz" />
      <!-- 启动基于注解的声明式AspectJ支持 -->
      <aop:aspectj-autoproxy />
</beans>

首先引入了context 约束信息,然后使用元素设置了需要扫描的包,使注解生效。由于此案例中的目标类位于com.itheima.jdk 包中,所以这里设置base-package的值为"com.itheima"。 最后,使<aop:aspectj- autoproxy />来启动Spring对基于注解的声明式AspectJ的支持。

在com.wz.aspectj.annotation包中创建测试类TestAnotationAspectj.java

package com.wz.aspectj.annotation;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.wz.aspectj.target.UserDao;
//测试类
public class TestAnnotationAspectj {
	public static void main(String args[]) {
		String xmlPath = 
               "com/wz/aspectj/annotation/applicationContext.xml";
		ApplicationContext applicationContext = 
              new ClassPathXmlApplicationContext(xmlPath);
		// 1 从spring容器获得内容
		UserDao userDao = (UserDao) applicationContext.getBean("userDao");
		// 2 执行方法
		userDao.addUser();
		userDao.deleteUser();
	}
}

输出结果:
在这里插入图片描述

从上述的两个输出结果图可以看出**(为了更直观的看出基于注解的结果,加上了目标方法所在的UserImpl目标类,看似不同其实是一致的;都是添加用户加上通知的功能模块),基于注解的方式与基于XML的方式的执行结果相同,只是在目标方法前后通知的执行顺序发生了变化(如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知的执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序也是未知的。)**。相对来说,使用注解的方式更加简单、方便,所以在实际开发中推荐使用注解的方式进行AOP开发。

总结:在面向切面编程中,Spring AOP与ApectJ的目的一致,都是为了统一处理横切业务,但与Aspect1不同的是,Spring AOP并不尝试提供完整的AOP功能(即使它完全可以实现),Spring AOP更注重的是SpringIO容器的结合,并结合该优势来解决横切业务的问题,因此在AOP的功能完善方面,相对来AspectJ具 有更大优势,同时,Spring注意 到Aspect在AOP的实现方式上依赖于特殊编译器(ajc编译器),因此Spring很机智回避了这点,转向采用动态代理技术的实现原理来构建Spring AOP的内部机制(动态织入),这是与Aspect] (静态织入)最根本的区别。在Aspect 1.5后, 引入@Aspect形式的注解风格的开发,Spring也非常快地跟进了这种式,因此Spring 2.0后便使用了与AspectJ-样的注解。请注意,Spring只是使用了与Aspect] 5一样的注解,但仍然没有使用AspectJ的编译器,底层依是动态代理技术的实现,因此并不依赖于AspectJ的编译器。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值