学习Java框架的笔记(Spring AOP)基于代理类的AOP实现、AspectJ开发(2)、ApectJ注解式声明

1.基于代理类的AOP实现

Spring AOP框架所需要的基本JAR包下载

前面的描述中,Spring 中的AOP代理默认使用的是 JDK动态代理方式。而在Spring中,使用ProxyFactory才是创建AOP代理的最基本方式。

1.1Spring的通知类型

Spring 中的通知按照在目标类方法的连接点位置,可分为以下5种类型:

  • org.aopalliance.intercept.MethodLntercepter(环绕通知)
    在目标方法执行前后实施增强,可以应用于日志、事务管理等功能(例如:web项目中的fiflter过滤器)。
  • org.springframework.aop.MethodBeforeAdvice(前置通知)
    在目标方法执行前实施增强,可以应用于权限管理等功能。(登录的密码验证)
  • org.springframework.aop.AfterReturningAdvice(后置通知)
    在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。(关闭数据库连接)
  • org.springframework.aop.ThrowsAdvice(异常通知)
    在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。(对异常进行解释)
  • org.springframework.aop.IntroductionInterceptor(引介通知)
    在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。(增强类)

1.2 ProxyFactoryBean

ProxyFactoryBean是FactoryBean接口的实现类。
FactoryBean负责实例化一个Bean, 而ProxyFactoryBean负责为其他Bean创建代理实例。
在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。
程序演示:

(1)在src目录下:创建一个com.itheima.jdk包,在该包下创建接口UserDao ,并在其中编写添加和删除的方法

package com.itheima.jdk;
public interface UserDao {
	public void addUser();		//添加
	public void deleteUser();		//删除
}

(2)在src目录下,创建一个com.itheima.factorybean包,在该包中创建切面类MyAspect 。由于实现环绕通知需要实现org.aopalliance.intercept.MethodInterceptor接口,所以MyAspect需要实现该接口。

package com.itheima.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
// 切面类(实现环绕通知)
public class MyAspect implements MethodInterceptor {
	@Override
	//执行被代理的方法
	public Object invoke(MethodInvocation mi) throws Throwable {
		check_Permissions();         //前
		// 执行目标方法
		Object obj = mi.proceed();
		log();		//后
		return obj;
	}
	public void check_Permissions(){
		System.out.println("模拟检查权限...");
	}
	public void log(){
		System.out.println("模拟记录日志...");
	}
}

(3)在com.itheima.factorybean包中,创建配置文件applicationContext.xml,并制定代理对象(代理、被代理对象、代理工厂 三个对象通过.xml配置文件在Spring容器中生成)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
	<!-- 1 目标类       id="userDao"  -->
	<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
	<!-- 2 切面类       id="myAspect"  -->
	<bean id="myAspect" class="com.itheima.factorybean.MyAspect" />
	
	<!-- 3 使用Spring代理工厂**定义一个**名称为userDaoProxy的**代理对象** -->
	<bean id="userDaoProxy" 
            class="org.springframework.aop.framework.ProxyFactoryBean">
		<!-- 3.1 指定代理实现的接口,也就是实现UserDao接口中的内容-->
		<property name="proxyInterfaces" 
                      value="com.itheima.jdk.UserDao" />
		<!-- 3.2 指定目标对象 -->
		<property name="target" ref="userDao" />
		<!-- 3.3 指定切面,织入环绕通知 值为切面对象-->
		<property name="interceptorNames" value="myAspect" />
		<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
		<property name="proxyTargetClass" value="true" />
		
	</bean>
</beans>

(4)在com.itheima.factorybean包中,创建测试类ProxyFactoryBeanTest 。

package com.itheima.factorybean;
import org.springframework.context.ApplicationContext;
import 
    org.springframework.context.support.ClassPathXmlApplicationContext;
import com.itheima.jdk.UserDao;
// 测试类
public class ProxyFactoryBeanTest {
	public static void main(String args[]) {
	   String xmlPath = "com/itheima/factorybean/applicationContext.xml";
	   //生成、加载Spring容器
	   ApplicationContext applicationContext = 
                                 new ClassPathXmlApplicationContext(xmlPath);
	   // 从Spring容器获得内容。获得代理对象userDao
	   UserDao userDao = 
                       (UserDao) applicationContext.getBean("userDaoProxy");
	   // 执行方法
	   userDao.addUser();
	   userDao.deleteUser();
	}
}

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

2. AspectJ开发

实现AOP(切面编程)的两种方法:

  • 1.通过XML 声明式
  • 2.通过注解 声明式
    在新版本的Spring框架中,也建议使用AspectJ来开发AOP(面向切面编程)

2.1 基于XML的声明式 AspectJ

基于XML的声明式AspectJ是指通过XML文件来定义切面、切入以及通知。
所有的切面、切入点和通知都必须定义在< aop:config >元素类。
先看代码:

<?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 目标类 (生成目标对象:userDao)-->
	<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
	<!-- 2 切面 (生成切面对象:myAspect)-->
	<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect" />
	<!-- 3 aop编程 -->
	<aop:config>
        <!-- 配置切面 (ref="myAspect"意思是:指定的切面对象为myAspect)-->
		<aop:aspect ref="myAspect">
		  <!-- 3.1 配置切入点,通知最后增强哪些方法 -->
		  <!-- expression="execution(* com.itheima.jdk.*.*(..)的意思是:在com.itheima.jdk包中所有方法都增强 -->
		  <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>

1. 配置切面

在Spring 的配置文件中,配置切面使用的是< aop:aspect >元素,该元素将一个定义好的Spring Bean转换成切面Bean,因此要在配置文件中先定义一个普通的Spring Bean(例如:上面的myAspect)。定义完成后,通过< aop:aspect ref=“myAspect” >中的ref属性 即可引用该Bean。
配置< aop:aspect >元素时,通常会指定id 和 ref两个属性。

属性名称描述
id用于定义该切面的唯一标识名称
ref用于引用普通的Spring Bean

2. 配置切入点

在Spring 的配置文件中,切入点是通过< aop:pointcut >元素来定义的。当< aop:pointcut >元素作为< aop:config > 元素的子元素来定义时,表示该切入点是全局切入点,可被多个切面所共享,当< aop:pointcut >元素作为< aop:aspect > 元素的子元素来时,表示该切入点只对当前切面有效。
< aop:pointcut > 元素的属性及其描述

属性名称描述
id用于指定该切入点的唯一标识名称
expression用于指定切入点关联的切入点表达式

在上述配置代码的片段中,execution(* com.itheima.jdk..(…))就是定义切入点表达式,该切入点的意思是:匹配com.itheima.jdk包中,任意类的任意方法的执行。
execution() 是表达式的主体
第一个 * 表示的是返回类型,使用 * 代表所有类型
第二个 * 表示的是类名,使用 * 代表所有类
第三个 * 表示的是方法名, 使用 * 表示所有方法
后面的(…)表示方法的参数,使用“…” 表示任意参数
第一个 * 与包名之间有一个空格


基于XML的生命式 AspectJ的具体代码:
(1)导入 AspectJ 框架相关的JAR包
spring-aspects-4.3.6.RELEASE.jar
aspectjweaver-1.8.10.jar
(2)在src目录下,创建com.itheima.aspectj.xml包,在该包中创建切面类MyAspect(增强类),在类中定义不同类型的通知。

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("最终通知:模拟方法结束后的释放资源...");
	}
}

在上述代码中定义了5种不同类型的通知,使用了JoinPoint接口及其子接口ProceedingJoinPoint作为参数来获得目标对象的类名、目标方法名和目标方法参数

(3)在com.itheima.aspectj.xml包中,创建配置文件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"
        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>

在AOP的配置信息中,使用< aop:after-returning >配置后置通知和使用< aop:after >最终通知的区别是,< aop:after-returning >只在目标方法执行成功之后才会织入,而最终通知是无论目标方法如何结束(包括成功执行和异常中止如何)都会织入
(4)在com.itheima.aspectj.xml包中,创建测试类TestXmlAspectj

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();
	}
}

2.1 基于注解的声明式 AspectJ

与基于代理类的AOP实现相比,基于XML的声明式ApectJ要便捷很多,但是它也存在一些缺点:要在Spring文件中配置大量的代码信息。为了解决这一问题,ApectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码.
关于ApectJ注解的介绍

注解名称描述
@Aspect用于定义一个切面
@Pointcut用于定义切入点表达式1
@Before用于定义前置通知
@AfterReturning用于定义后置通知
@Around用于定义环绕通知
@After Throwing用于定义异常通知来处理程序中未处理的异常
@After用于定义最终final通知,不管是否异常,该通知都会执行
@DeclareParents用于定义引介通知

(1)在src目录下,创建com.itheima.aspectj.annotation包,

package com.itheima.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
@Component
public class MyAspect {
	// 定义切入点表达式
	@Pointcut("execution(* com.itheima.jdk.*.*(..))")
	// 使用一个返回值为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注解才能生效。
之后使用了@Pointcut注解来配置切入点表达式,并通过定义方法来表示切入点名称。

(2)在目标类UserDaoImpl中,添加注解@Repository(“userDao”)

package com.itheima.jdk;

import org.springframework.stereotype.Repository;

// 目标类
@Repository("userDao")
public class UserDaoImpl implements UserDao {
	public void addUser() {
//		int i = 10/0;
		System.out.println("添加用户");
	}
	public void deleteUser() {
		System.out.println("删除用户");
	}
}

(3)在com.itheima.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.itheima" />
      <!-- 启动基于注解的声明式AspectJ支持 -->
      <aop:aspectj-autoproxy />
</beans>

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

(4)在com.itheima.aspectj.annotation包下,创建测试类TestAnnotationAspectj

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

ApectJ注解式声明结果截图:
在这里插入图片描述


  1. 在使用时,还需要定义一个方法签名来表示切入点名称。方法签名就是:一个返回值为void,且方法体为空的普通方法。 ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值