目录
1.简介
AOP(Aspect Oriented Programming)面向切面编程。
OOP(0bject Oriented Programming)面向对象编程
AOP的作用:在改动原始设计的基础上为其进行功能增强
概念:代理(Proxy ) : SpringAOP的核心本质是采用代理模式实现的
连接点( 3oinPoint ) :在SpringA0P中,任意方法的执行
切入点( Pointcut ) :匹配连接点的表达式
通知(Advice ):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
切面(Aspect )︰描述通知与切入点的对应关系
目标对象( Target ) :被代理的原始对象成为目标对象
2.AOP案例
首先建立一个整合Mybatis的项目,可以参考 Spring整合Mybatis案例及详解。也可以自己创建一个基本的spring注解开发案例。这里就用Mybatis这个案例来讲解了。
2.1.增加依赖
在pom.xml文件中再添加一个依赖(注:可能会报错,照着写完或者直接复制粘贴过去后刷新Maven就OK了)
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
2.2.更改配置类
package org.example.config;
import org.springframework.context.annotation.*;
@Configuration
@PropertySource("jdbc.properties")
@ComponentScan("org.example")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableAspectJAutoProxy //增加的内容
public class SpringConfig {
}
在spring配置类中增加了@EnableAspectJAutoProxy,目的是告诉spring我们用到了注解开发的AOP,防止spring把AOP当做Bean来配置。
2.3.创建通知类(切面)
创建一个通知类Advice.java,这里先直接上代码了,里面的注解后面有讲解。
package org.example.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class Advice {
@Pointcut("execution(* org.example.dao.StuDao.*(..))")
private void servicept(){}
@Before("servicept()")
public void befor(){
System.out.println("我就是增强的内容,我在原始方法之前运行");
}
// @After("servicept()")
public void after() {
System.out.println("我就是增强的内容,我在原始方法之后运行");
}
// @Around("servicept()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("我就是增强的内容,我在原始方法之前运行(环绕)");
//对原始操作的调用
Object ret = pjp.proceed();
System.out.println("我就是增强的内容,我在原始方法之后运行(环绕)");
return ret;
}
// @AfterReturning("servicept()")
public void afterReturning() {
System.out.println("我就是增强的内容,我在原始方法不出现异常之后运行");
}
// @AfterThrowing("servicept()")
public void afterThrowing() {
System.out.println("我就是增强的内容,我在原始方法出现异常之后运行");
}
}
2.4.验证环节
上面做完之后在测试类中调用我们配置好的方法,在不改动原始方法的前提下也会显示相应内容。
3.AOP注解解释
这里解释前面Advice文件中的各个注解的含义
- @Component 声明这是一个通用Bean,注册到spring容器中
- @Aspect 告诉spring这是一个Aop,把这个类作为AOP来配置
- @Pointcut 设置切入点,参数为execution(执行),他里面可以指定方法名,同时他支持匹配方法。这个知识点放到第4大点讲。
- @Before("servicept()") 前置通知: 在servicept()这个切入点之前执行下面这个方法。
- @After("servicept()") 后置通知:在servicept()这个切入点之后执行下面这个方法。
- @Around("servicept()") 环绕通知:在servicept()这个切入点前后执行下面这个方法。这个方法用的比较多,它需要创建 ProceedingJoinPoint 对象来执行原始方法,然后它就会在原始方法前后执行相应的操作。如果你的方法是有返回值的情况下需要用一个数据类型来接收返回值,并且又通过这个环绕方法返回去,所以通常的做法就是使用Object来接收这个返回值,并且返回Object对象。同时也需要抛出异常。
- @AfterReturning("servicept()") 无异常后通知: 在servicept()这个切入点完成且没有抛出异常之后执行下面这个方法。
- @AfterThrowing("servicept()") 抛出异常后通知:在servicept()这个切入点抛出异常后执行下面这个方法。
4.AOP切入点表达式
标准格式∶动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
例如:execution(int org.example.dao.StuDao.add(int))execution就是动作关键字,执行到某个切入点。
访问修饰符:一般为public,可以省略。
返回值:就是这个方法返回的值类型。
其他的就不多说了。
表达式通配符:
*︰表示单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
如:execution (public * org.example.*. Service.find*(*))
表示匹配org.example包下的任意包中的Service类或接口中所有find开头的带有一个参数的方法。
.. :表示多个连续的任意符号,可以独立出现,用于简化包名与参数的书写
如:execution (public Student com..Service.findById (..) )
表示匹配com包下的任意包中的Service类或接口中所有名称为findByld的方法
5.AOP通知获取数据
@After("servicept()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("我就是增强的内容,我在原始方法之后运行");
}
给切入点执行后的方法里添加了一个参数来获取数据
环绕型的一样,可以更改数据后传给pjp.proceed(args),这样原始数据就更改了。可以在这里判断数据是否正确后再传入。
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
剩下两种方式
@AfterReturning(value = "servicept()",returning = "oj")
public void afterReturning(Object oj) { //如果有JoinPoint数据,必须先接收
System.out.println(oj);
System.out.println("我就是增强的内容,我在原始方法不出现异常之后运行");
}
@AfterThrowing(value = "servicept()",throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println(t);
System.out.println("我就是增强的内容,我在原始方法出现异常之后运行");
}
6.遇到的问题
使用
StuServiceImpl stuService = ctx.getBean(StuServiceImpl.class);获取Bean时报错☟
增强服务层方法时报No qualifying bean of type 'org.example.service.impl.StuServiceImpl' available错误,不知道什么原因,应该是动态代理的问题,但是可以通过接口来获取方法,也就是
StuService service=ctx.getBean(StuService.class);
System.out.println(service.allStu("1002"));
不知道这种方法会有什么问题。有大佬看见的话指点一二,谢谢。