目录
一、SpringAOP_AOP简介
AOP的全称是Aspect Oriented Programming,即面向切面编程。是实现功能统一维护的一种技术,它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
作用:在不修改源码的基础上,对已有方法进行增强。实现原理:动态代理技术。优势:减少重复代码、提高开发效率、维护方便应用场景:事务处理、日志管理、权限控制、异常处理等方面。
二、SpringAOP_术语
AOP的相关术语:(前4个常用,后3个不常用)
名称 说明 JoinPoint(连接点) 指能被拦截到的点,在Spring中只有方法能被拦截 Pointcut(切点) 指要对那些连接点进行拦截,即被增强的方法 Advice(通知) 指拦截后要做的事情,即切点被拦截后执行的(后置、前置、、)方法 Aspect(切面) 切点+通知称为切面 Target(目标) 被代理的对象
Proxy(代理) 代理对象 Weaving(织入) 生成代理对象的过程
以下一段配置文件的嵌套代码更好地理解上述的相关术语:
<!--通知对象-->
<bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean>
<!--配置AOP-->
<aop:config >
<!--配置切面-->
<aop:aspect ref="myAspectJAdvice">
<!--配置切点-->
<aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
<!--配置通知-->
<!--前置通知-->
<!-- <aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>-->
<!--后置通知1-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"></aop:after-returning>
<!--异常通知-->
<!-- <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"></aop:after-throwing>-->
<!--最终通知-->
<!-- <aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>-->
<!--环绕通知-->
<!-- <aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>-->
</aop:aspect>
</aop:config>
三、SpringAOP_AOP入门
1.引入依赖:
<dependencies>
<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
<!--aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
2.编写连接点
@Repository("userDao")//注解方式将UserDao放入Spring容器中
public class UserDao {
public void add(){
System.out.println("添加方法");
}
public void delete(){
System.out.println("删除方法");
}
public void update(){
int i=1/0;
System.out.println("修改方法");
}
}
3.编写通知类
//通知类
public class MyAspectJAdvice {
// 后置通知
public void myAfterReturning() {
System.out.println("打印日志...");
}
}
4.配置文件中配置切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 扫描包,为包下的所有类配置扫描包,扫描到该注解才能生效 -->
<context:component-scan base-package="com.itbaizhan"></context:component-scan>
<!--设置通知对象 id:对象名 class:通知类路径名-->
<bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean>
<!--配置AOP-->
<aop:config >
<!--配置切面 ref:指定通知类是哪一个类-->
<aop:aspect ref="myAspectJAdvice">
<!--配置切点 id:为切点起名字 expression:设置哪些方法为切点-->
<aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
<!--配置通知-->
<!--后置通知1 method:设置哪个方法为后置通知 pointcut-ref:引入要切入的切点(将通知切入到要切入的切点中)-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"></aop:after-returning>
</aop:aspect>
</beans>
5.测试:
public class UserDaoTest {
@Test
public void testAdd(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.add();
}
@Test
public void testDelete(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.delete();
}
@Test
public void testUpdate(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.update();
}
6.测试testAdd方法结果:
如上所示:我们可以看到,执行userDao.add()方法本来只会打印添加方法,可最后却打印出了MyAspectJAdvice类中的方法“打印日志”语句,说明AOP后置通知切入到切点中了。
也就表明,AOP可以在不修改源码的情况下对已有方法进行增强。
四、SpringAOP_五种通知类型
AspectJ实现AOP共有五种通知类型:
通知类型 描述 前置通知 在方法(切点)执行前添加功能 后置通知 在方法(切点)正常执行后添加功能 异常通知 在方法抛出异常后添加功能 最终通知 无论方法是否抛出异常,都会执行该通知,相当于finally 环绕通知 在方法执行前后添加功能
测试五种通知类型
1.编写通知方法
//通知类
public class MyAspectAdvice {
// 后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("切点方法名"+joinPoint.getSignature().getName());
System.out.println("目标对象"+joinPoint.getTarget());
System.out.println("打印日志"+joinPoint.getSignature().getName()+"方法被执行了!");
}
// 前置通知
public void myBefore(){
System.out.println("前置通知。。。");
}
// 异常通知
public void myAfterThrowing(Exception ex){
System.out.println("异常通知。。。");
System.err.println(ex.getMessage());//err:打印红色字体
}
// 最终通知
public void myAfter(){
System.out.println("最终通知");
}
// 环绕通知
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
System.out.println("环绕前");
Object obj = proceedingJoinPoint.proceed();//执行方法
System.out.println("环绕后");
return obj;
}
}
2.配置文件bean.xml中配置切面
<!--设置通知对象 id:对象名 class:通知类路径名-->
<bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean>
<!--配置AOP-->
<aop:config >
<!--配置切面 ref:指定通知类是哪一个类-->
<aop:aspect ref="myAspectJAdvice">
<!--配置切点 id:为切点起名字 expression:设置哪些方法为切点-->
<aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
<!--配置通知-->
<!--前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointcut"></aop:before>
<!--后置通知1 method:设置哪个方法为后置通知 pointcut-ref:引入要切入的切点(将通知切入到要切入的切点中)-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"></aop:after-returning>
<!--异常通知 注意:要配置throwing:传入通知方法中的参数,将返回内容封装到参数中-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointcut" throwing="ex"></aop:after-throwing>
<!--最终通知-->
<aop:after method="myAfter" pointcut-ref="myPointcut"></aop:after>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointcut"></aop:around>
</aop:aspect>
</aop:config>
3.测试方法
public class UserDaoTest {
@Test
public void testAdd(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.add();
}
@Test
public void testDelete(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.delete();
}
@Test
public void testUpdate(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.update();
}
五、SpringAOP_切点表达式
注意要点:1.访问修饰符可以省略。2.返回值使用 * 代表任意类型。3.包名使用 * 表示任意包,多级包结构要写多个 * ,使用 *.. 表示任意包结构4.类名和方法名都可以用 * 实现通配。5.参数列表基本数据类型直接写类型,如:int、float引用类型写 包名.类名 ,如:java.lang.String* 表示匹配一个任意类型参数.. 表示匹配任意类型任意个数的参数6.全通配: * *..*.*(..)
<!--设置通知对象 id:对象名 class:通知类路径名-->
<bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean>
<!--配置AOP-->
<aop:config >
<!--配置切面 ref:指定通知类是哪一个类-->
<aop:aspect ref="myAspectJAdvice">
<!--配置切点 id:为切点起名字 expression:设置哪些方法为切点-->
<aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/>
<!--配置通知-->
<!--后置通知1 method:设置哪个方法为后置通知 pointcut-ref:引入要切入的切点(将通知切入到要切入的切点中)-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"></aop:after-returning>
</aop:aspect>
如上切点表达式中表示:com.itbaizhan.dao.UserDao类下的任意返回值类型的方法的所有方法的任意参数类型。
int com.itbaizhan.dao.UserDao.*(..)表示只有返回值为int类型的方法可以被拦截
* com.*.*UserDao.*(int) 表示com包下的任意包下的UserDao类的任意返回值类型参数类型为int方法
* *..UserDao.*(..)表示任意包结果下的UserDao类下的所有任意类型任意参数的方法
六、SpringAOP_多切面配置
我们可以为切点配置多个通知,形成多切面,比如希望dao层的每个方法结束后都可以打印日志并发送邮件:
1.编写发送邮件的通知类
//通知类2 public class MyAspectAdvice2 { // 后置通知 public void myAfterReturning2(){ System.out.println("发送邮件"); } }
2.编写打印日志的通知类
public class MyAspectAdvice { // 后置通知 public void myAfterReturning(JoinPoint joinPoint){ System.out.println("打印日志。。"); } }
3.配置多切面
<!--设置通知对象 id:对象名 class:通知类路径名--> <bean id="myAspectJAdvice" class="com.itbaizhan.advice.MyAspectAdvice"></bean> <bean id="myAspectJAdvice2" class="com.itbaizhan.advice.MyAspectAdvice2"></bean> <!--配置AOP--> <aop:config > <!--配置切面(第一个切面) ref:指定通知类是哪一个类--> <aop:aspect ref="myAspectJAdvice"> <!--配置切点 id:为切点起名字 expression:设置哪些方法为切点--> <aop:pointcut id="myPointcut" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/> <!--配置通知--> <!--后置通知1 method:设置哪个方法为后置通知 pointcut-ref:引入要切入的切点(将通知切入到要切入的切点中)--> <aop:after-returning method="myAfterReturning" pointcut-ref="myPointcut"></aop:after-returning> </aop:aspect> <!--配置切面(第二个切面) ref:指定通知类是哪一个类--> <aop:aspect ref="myAspectJAdvice2"> <!--配置切点--> <aop:pointcut id="myPointcut2" expression="execution(* com.itbaizhan.dao.UserDao.*(..))"/> <!--后置通知2--> <aop:after-returning method="myAfterReturning2" pointcut-ref="myPointcut2"></aop:after-returning> </aop:aspect> </aop:config>
4.测试
@Test public void testAdd(){ ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); UserDao userDao = (UserDao)ac.getBean("userDao"); userDao.add(); }
5.测试结果
七、SpringAOP_注解配置AOP
1.Spring可以使用注解代替配置文件配置切面:
<!--开启注解配置AOP-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
2.在通知类上方加入注解 @Aspect
3.在通知方法上方加入注解@Before/@AfterReturning/@AfterThrowing/@After/@Around
@Aspect//注解方式配置通知需要在类上方标明@Aspect
@Component//把此类放入容器中
public class MyAspectAdvice {
// 后置通知
@AfterReturning("execution(* com.itbaizhan.dao.UserDao.*(..))")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("切点方法名"+joinPoint.getSignature().getName());
System.out.println("目标对象"+joinPoint.getTarget());
System.out.println("打印日志"+joinPoint.getSignature().getName()+"方法被执行了!");
}
// 前置通知
@Before("execution(* com.itbaizhan.dao.UserDao.*(..))")
public void myBefore(){
System.out.println("前置通知。。。");
}
// 异常通知
@AfterThrowing(value = "execution(* com.itbaizhan.dao.UserDao.*(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知。。。");
System.err.println(ex.getMessage());//err:打印红色字体
}
// 最终通知
@After("execution(* com.itbaizhan.dao.UserDao.*(..))")
public void myAfter(){
System.out.println("最终通知");
}
// 环绕通知
@Around("execution(* com.itbaizhan.dao.UserDao.*(..))")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint)throws Throwable{
System.out.println("环绕前");
Object obj = proceedingJoinPoint.proceed();//执行方法
System.out.println("环绕后");
return obj;
}
}
4.测试方法:
@Test
public void testAdd2(){
ApplicationContext ac = new ClassPathXmlApplicationContext("bean1.xml");
UserDao userDao = (UserDao)ac.getBean("userDao");
userDao.add();
}
2.如何为一个类下的所有方法统一配置切点:
1.在通知类中添加方法配置切点
2.在通知方法上使用定义好的切点
@Aspect//注解方式配置通知需要在类上方标明@Aspect
@Component//把此类放入容器中
public class MyAspectAdvice {
// 切点方法
@Pointcut("execution(* com.itbaizhan.dao.UserDao.*(..))")
public void pointCut(){
}
// 后置通知
@AfterReturning("pointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("切点方法名"+joinPoint.getSignature().getName());
System.out.println("目标对象"+joinPoint.getTarget());
System.out.println("打印日志"+joinPoint.getSignature().getName()+"方法被执行了!");
}
// 前置通知
@Before("pointCut()")
public void myBefore(){
System.out.println("前置通知。。。");
}
// 异常通知
@AfterThrowing(value = "pointCut()",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知。。。");
System.err.println(ex.getMessage());//err:打印红色字体
}
}
3.配置类如何代替xml中AOP注解支持?
<!--开启注解配置AOP--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
在配置类上方添加@EnableAspectJAutoProxy即可@Configuration//标明此类为配置类 @ComponentScan("com.itbaizhan")//包扫描 @EnableAspectJAutoProxy//用注解的方式开启配置类代替xml文件配置AOP public class SpringConfig { }
测试方法:
@Test public void testAdd3(){ ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); UserDao userDao = (UserDao)ac.getBean("userDao"); userDao.add(); }
八、知识点整理:
1.AOP的实现原理是“动态代理技术”
2.AOP的作用是“在不修改源代码的情况下对已有方法进项增强”
3.在AOP中,切面指“切点+通知”
4.在AOP中,通知指“切点被拦截后执行的方法”
5.AOP五种通知类型“前置通知”、“后置通知”、“异常通知”、“最终通知”、“环绕通知”
6.AOP切点表达式的标准写法是“访问修饰符 返回值类型 包名.类名.方法名(参数类型)”
7.使用AspectJ实现AOP时,使用<aop:aspect>标签配置切面
8.在xml中开启AOP注解支持的标签为:“<aop:aspectj-autoproxy> ”