学习Java框架的笔记(Spring AOP)基于代理类的AOP实现、AspectJ开发(2)、ApectJ注解式声明
1.基于代理类的AOP实现
前面的描述中,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注解式声明结果截图:
在使用时,还需要定义一个方法签名来表示切入点名称。方法签名就是:一个返回值为void,且方法体为空的普通方法。 ↩︎
- [原创]java WEB学习笔记108:Spring学习---基于配置文件的形式实现AOP
- Spring学习-21:Spring的AOP:基于AspectJ的注解开发
- Spring学习-22:Spring的AOP:基于AspectJ的XML配置方式开发
- 学习笔记之springAOP的aspectJ实现注意点总结
- Spring AOP之基于代理类的AOP实现和AspectJ开发
- 学习《spring 3.x企业应用开发实战》之基于@AspectJ和Schema的AOP
- spring 学习笔记 使用aspectj开发aop
- JAVAWEB开发之Spring详解之——AOP底层剖析(基于JDK和cglib)、Spring中的AOP以及基于AspectJ的AOP实现、Spring的JDBCTemplate详解
- Spring基础学习(九)——基于AspectJ的两种AOP实现方式
- Spring笔记之六:AOP基于@AspectJ配置切面
- Spring学习笔记(14)----使用CGLIB实现AOP功能
- Netty学习笔记(六)Springboot实现基于http协议的简单服务器---浏览器和客户端访问
- Spring AOP的实现机制(三)---@AspectJ形式的Spring AOP和基于Schema的AOP
- SpringMVC学习笔记(二)使用IntelliJ IDEA开发Spring MVC HelloWorld(基于Maven)
- Spring_Spring与AOP_AspectJ基于XML的实现
- spring学习笔记(13)基于Schema配置AOP详解
- 【spring学习笔记三】aop思想介绍及实现原理
- spring框架的学习(五)——Spring使用AspectJ进行AOP的开发:注解方式
- Spring_Spring与AOP_AspectJ基于注解的AOP实现
- spring 学习笔记 使用pojo+xml的方式开发aop