目录
一、基于代理类的AOP实现
1、spring的通知类型
Spring按照通知在目标类方法的连接点位置,可以分为5种类型,具体如下:
org.springframework.aop.MethodBeforeAdvice(前置通知)
在目标方法执行前实施增强,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知)
在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知)
在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。.
org.springframework.aop.ThrowsAdvice(异常抛出通知)
在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor (引介通知)
在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。
2、ProxyFactoryBean
ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。
( 1 )在核心JAR包的基础上,再向chapter03项目的 lib目录中导入AOP的JAR包spring-aop-4.3.6.RELEASE.jar 和 aopalliance-1.0.jar
aopalliance-1.0.jar:是AOP联盟提供的规范包,该JAR包可以通过地址“http://mvnrepository.com/artifact/aopalliance/aopalliance/1.0”下载。
(2在src目录下,创建一个com.itheima.factorybean包,在该包中创建切面类MyAspect。由于实现环绕通知需要实现
org.aopalliance.intercept. Methodlnterceptor接口,所以MyAspect类需要实现该接口,并实现接口中的 invoke()方法,来执行目标方法
import org.aopalliance.intercept.MethodInterceptor;//注意路径
import org.aopalliance.intercept.MethodInvocation;public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//调用权限检查
check_Permissionss();
//调用目标方法
Object object = methodInvocation.proceed();
//调用日志记录
log();
return object;
}
//权限检查
public void check_Permissionss() {
System.out.println("模拟权限检查....");
}
//记录日志
public void log() {
System.out.println("模拟记录日志...");
}}
(3)在com.itheima.factorybean包中,创建配置文件 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 1.创建目标类实例 -->
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl"></bean>
<!-- 2.创建切面类实例 -->
<bean id="myAspect" class="com.itheima.factorybean.MyAspect"></bean>
<!-- 3.使用Spring代理工程定义一个代理对象,然后对代理对象的属性注入-->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1指定代理实现的接口 -->
<property name="proxyInterfaces" value="com.itheima.jdk.UserDao"></property>
<!--3.2指定目标对象 -->
<property name="target" ref="userDao"></property>
<!-- 3.3指定切面,植入环绕通知 -->
<property name="interceptorNames" value="myAspect"></property>
<!-- 3.4指定代理方式,默认false用jdk, true用cglib -->
<property name="proxyTargetClass" value="true"></property>
</bean>
</beans>
( 4)在com.itheima.factorybean包中,创建测试类 ProxyFactoryBeanTest,在类中通过Sprina容器获取代理对象的实例.并执行目标方法
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";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
userDao.addUser();
}}
执行程序后,控制台的输出结果
二、AspectJ开发
AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,SpringAOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架,也建议使用AspectJ来开发AOP。使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ。
1、基于XML的声明式AspectJ
基于XML的声明式AspectJ是指通过XML文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在<aop:config>元素内。
1.配置切面
在Spring 的配置文件中,配置切面使用的是<aop:aspect>元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。定义完成后,通过<aop:aspect>元素的ref 属性即可引用该 Bean。
配置<aop:aspect>元素时,通常会指定id和ref两个属性
2.配置切入点
在Spring 的配置文件中,切入点是通过<aop:pointcut>元素来定义的。当<aop:pointcut>元素作为<aop:config>元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当<aop:pointcut>元素作为<aop:aspect>元素的子元素时,表示该切入点只对当前切面有效。
在上述配置代码片段中,execution(* com.itheima.jdk.*.*(..))就是定义的切入点表达式,该切入点表达式的意思是匹配 com.itheima.jdk包中任意类的任意方法的执行。其中 execution()是表达式的主体,第1个*表示的是返回类型,使用*代表所有类型;com.itheima.jdk表示的是需要拦截的包名,后面第2个*表示的是类名,使用*代表所有的类;第3个*表示的是方法名,使用*表示所有方法;后面(..)表示方法的参数,其中的“..”表示任意参数。需要注意的是,第1个*与包名之间有一个空格。
3.配置通知
使用<aop:aspect>的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:
4、案例
(1)导入AspectJ框架相关的JAR包,具体如下。
spring-aspects-4.3.6.RELEASE.jar:Spring 为AspectJ提供的实现,Spring 的包中已经提供。
aspectjweaver-1.8.10.jar:是AspectJ框架所提供的规范,读者可以通过网址“http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.10”下载。
(2)在chapter03项目的src目录下,创建一个 com.itheima.aspectj.xml包,在该包中创建切面类MyAspect,并在类中分别定义不同类型的通知
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;/**
*切面类,在该类中编写通知
*通知也就是要增强的功能的那些方法*
*/
public class MyAspect {
//1.前置通知
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知:模拟执行权限检查...");
System.out.println("目标类:"+joinPoint.getTarget());//输出一下目标类
//被植入增强处理的目标方法名
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//2.后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为"+joinPoint.getSignature().getName());
}
//3.环绕通知
/**
*环绕通知有几个限定条件
*1.必须是0bject类型的返回值
*2.必须接收一个参数,类型为ProceedingJoinPoint
*3.必须throws Throwable
*/
public Object myArroud(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {
//开始
System.out.println("坏绕开始:执行目标方法前,模拟开始事务...");
//执行当前目标方法
Object object = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
return object;
}
//4.异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
System.out.println("异常通知:出错了"+e.getMessage());
}
//5.最终通知
public void myAfter() {
System.out.println("最终通知:最终通知:模拟方法结束后的释放资源...");
}}
在文中,分别定义了5种不同类型的通知,在通知中使用了JoinPoint 接口及其子接口ProceedingJoinPoint 作为参数来获得目标对象的类名、目标方法名和目标方法参数等。
需要注意的是,环绕通知必须接收一个类型为ProceedingJoinPoint的参数,返回值也必须是Object类型,且必须抛出异常。异常通知中可以传入Throwable类型的参数来输出异常信息。
( 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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 1.目标类 -->
<bean id="userDao" class="com.qingcheng.ssm.jdk.UserDaoImpl"></bean>
<!-- 2.切面类 -->
<bean id="myAspect" class="com.qingcheng.ssm.aspectj.xml.MyAspect"></bean>
<!-- 3.进行aop配置 -->
<aop:config>
<!-- 配置切面-->
<aop:aspect ref="myAspect">
<!-- 3.1配置切入点,通知最后增强哪些方法-->
<aop:pointcut expression="execution(* com.qingcheng.ssm.jdk.*.*(..))" id="myPointcut"/>
<!-- 3.2关联通知和切入点-->
<!-- 3.2.1前置通知-->
<!--myBefore是切面类中的前置方法名 -->
<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="myArroud" 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>
(4)在 com.itheima.aspectj.xml包下,创建测试类TestXmIAspectj,在类中为了更加清晰地演示几种通知的执行情况,这里只对 addUser()方法进行增强测试
public class TestAspectjXml {
public static void main(String[] args) {
String xmlPath = "com/itheima/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext =new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}}
执行程序后,控制台的输出结果
要查看异常通知的执行效果,可以在 UserDaolmpl类的 addUser()方法中添加错误代码,如int i = 10/0;”,重新运行测试类,将可以看到异常通知的执行
2、基于注解的声明式AspectJ
与基于代理类的AOP实现相比,基于XML的声明式ApectJ要便捷得多,但是它也存在着一些缺点,那就是要在Spring文件中配置大量的代码信息。为了解决这个问题,AspectJ框架为AOP的实现提供了一套注解,用以取代Spring配置文件中为实现AOP功能所配置的臃肿代码。
( 1)在chapter03项目的src目录下,创建com.itheima.aspectj.annotation包,将文件3-12的切面类MyAspect复制到该包下,并对该文件进行编辑
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;/***
*切面类,在该类中编写通知
*通知也就是要增强的功能的那些方法
*/
@Component //生成MyAspect的实例,也可以说生成它的bean
@Aspect //生成切面
public class MyAspect {
/**
* 定义一个切入点
* */
@Pointcut("execution(* com.qingcheng.ssm.jdk.*.*(..))")
private void myPointCut() {
}
//1.前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint) {
System.out.println("前置通知:模拟执行权限检查...");
System.out.println("目标类:"+joinPoint.getTarget());//输出一下目标类
//被植入增强处理的目标方法名
System.out.println("被植入增强处理的目标方法为:"+joinPoint.getSignature().getName());
}
//2.后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.println("后置通知:模拟记录日志...");
System.out.println("被植入增强处理的目标方法为"+joinPoint.getSignature().getName());
}
//3.环绕通知
/**
*环绕通知有几个限定条件
*1.必须是0bject类型的返回值
*2.必须接收一个参数,类型为ProceedingJoinPoint
*3.必须throws Throwable
*/
@Around("myPointCut()")
public Object myArroud(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {
//开始
System.out.println("坏绕开始:执行目标方法前,模拟开始事务...");
//执行当前目标方法
Object object = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务");
return object;
}
//4.异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
System.out.println("异常通知:出错了"+e.getMessage());
}
//5.最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:最终通知:模拟方法结束后的释放资源...");
}}
(2)在目标类com.itheima.jdk.UserDaolmpl中,添加注解@Repository("userDao")。
(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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--指定需要扫描的包,使注解生效-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--启动基于注解的声明式Aspect的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
( 4 )在 com.itheima.aspectj.annotation包中,创建测试类TestAnnotation
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;import com.qingcheng.ssm.jdk.UserDao;
public class TestAspectAnnotation {
public static void main(String[] args) {
String xmlPath = "com/itheima/aspectj/annotation/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}}
执行程序后,控制台的输出结果