一、AOP简介
AOP,Aspect Oriented Programming,面向切面编程,是对面向对象编程0OP的升华。OOP是纵向对一个事物的抽象,一个对象包括静态的属性信息,包括动态的方法信息等。而AOP是横向的对不同事物的抽象,属性与属性、方法与方法、对象与对象都可以组成一个切面, 而用这种思维去设计编程的方式叫做面向切面编程。
二、AOP思想的实现方案
动态代理技术,在运行期间,对目标对象的方法进行增强,代理对象同名方法内可以执行原有逻辑的同时嵌入执行其他增强逻辑或其他对象的方法。
例如:
A对象为我们要增强的目标对象,B对象为增强方法所在的对象。在动态代理后,我们会获得一个A对象的
接下来我们通过这种方式来增强spring容器中的一个Bean
(项目创建成功之后的目录)不要忘了,每个bean都需要被spring容器管理,所以需要在类上添加注解@Component或者@Service
① 首先创建一个项目,创建Userservice接口,并实现它,这个UserServiceImpl对象就相当于A对象
public interface UserService {
public void show1();
public void show2();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void show1() {
System.out.println("UserServiceImpl show1");
}
@Override
public void show2() {
System.out.println("UserServiceImpl show2");
}
}
②然后创建增强类Advice,相当于B对象
@Component
public class MyAdvice {
public void beforeAdvice(){
System.out.println("前置增强...");
}
public void afterAdvice(){
System.out.println("后置增强...");
}
}
③最后创建一个Bean的后置处理类,这个后置处理类会返回一个被增强的bean,相当于A对象的Proxy对象
@Component
public class MockAopBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//目的:对UserServiceImpl中的show1和show2方法进行增强,增强方法存在于MyAdvice中
//问题1:筛选service.impl包下的所有方法都可以进行增强,解决方法是if-else
//问题2:如果获取MyAdvice,解决方案从Spring容器中获取
//加一个条件判断,只有在"com.itheima.service.impl"包下的bean才能够被增强
if(bean.getClass().getPackage().getName().equals("com.itheima.service.impl")){
//使用Proxy类生成当前获取到的对象的代理对象
Object beanProxy = Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取增强方法所在的对象
MyAdvice advice = applicationContext.getBean(MyAdvice.class);
//执行前置增强方法
advice.beforeAdvice();
//执行目标方法
Object result = method.invoke(bean,args);//执行bean的目标方法,携带参数
//执行后置增强方法
advice.afterAdvice();
return result;
}
});
return beanProxy;
}
return bean;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
三、Spring_AOP的相关概念
四、基于xml配置的AOP
1.xml方式AOP快速入门
2.步骤
1.导入AOP相关坐标
2.准备目标类、准备增强类、并配置给Spring管理
3.配置切点表达式(哪些方法被增强)
4.配置织入(切点被哪些方法增强,是前置增强还是后置增强)
五、基于注解的方式配置的AOP
1.AOP的注解介绍
注解 | 作用 |
---|---|
@Aspect | 切面声明 ,标注在类、接口(包括注解类型)或枚举上。 |
@Pointcut | 切入点声明 ,即切入到哪些目标类的目标方法。既可以用 execution 切点表达式, 也可以是annotation 指定拦截拥有指定注解的方法。value 属性指定切入点表达式,默认为 “”,用于被通知注解引用,这样通知注解只需要关联此切入点声明即可,无需再重复写切入点表达式 |
@Before | 前置通知 , 在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式。注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知。 |
@After | 后置通知 , 在目标方法(切入点)执行之后执行 |
@AfterReturning | 返回通知 , 在目标方法(切入点)返回结果之后执行。pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 “” |
@AfterThrowing | 异常通知 , 在方法抛出异常之后执行, 意味着跳过返回通知pointcut 属性绑定通知的切入点表达式,优先级高于 value,默认为 " " 注意:如果目标方法自己 try-catch 了异常,而没有继续往外抛,则不会进入此回调函数 |
@Around | 环绕通知 :目标方法执行前后分别执行一些代码,类似拦截器,可以控制目标方法是否继续执行。通常用于统计方法耗时,参数校验等等操作。 |
正常流程:【环绕通知-前】-> 【前置通知】-> 【返回通知】-> 【后置通知】->【环绕通知-后】。
2.AOP的切点表达式
切点表达式是配置要对哪些连接点(哪些类的哪些方法)进行通知的增强,语法如下:
execution ( [访问修饰符] 返回值类型 包名.类名.方法名(参数) )
其中:
1.访问修饰符可以省略不写
2.返回值类型、某一级的包名、类名、方法名可以使用*
表示任意
3.包名与类型之间使用单点.
表示该包下的类,使用双点 ..
表示该包及其子包下的所有类
4.参数列表可以使用两个点..
表示任意参数
例如 "execution(* com.itheima.service.impl.*.*(..))"
表示权限修饰符默认、任意返回值类型、com.itheima.service.impl包下、所有类、所有方法、任意个参数
3.注解方式AOP快速入门
4.步骤
1.导入AOP相关坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.准备目标类并配置给Spring管理
@Service
public class UserServiceImpl implements UserService {
@Override
public void show1() {
// int a = 1/0;
System.out.println("UserServiceImpl show1");
}
@Override
public void show2() {
System.out.println("UserServiceImpl show2");
}
}
3.使用注解配置切点、切面类、通知类型等
@Component
@Aspect
public class MyAdvice2 {
@Before("execution(* com.itheima.service.impl.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("当前增强的对象是:"+joinPoint.getTarget());
System.out.println("表达式:"+joinPoint.getStaticPart());
System.out.println("注解配置的前置增强...");
System.out.println("======================");
}
@AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
public void afterReturningAdvice(JoinPoint joinPoint){
System.out.println("当前增强的对象是:"+joinPoint.getTarget());
System.out.println("表达式:"+joinPoint.getStaticPart());
System.out.println("注解配置的后置增强...");
System.out.println("======================");
}
@Around("execution(* com.itheima.service.impl.*.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("当前增强的对象是:"+proceedingJoinPoint.getTarget());
System.out.println("表达式:"+proceedingJoinPoint.getStaticPart());
System.out.println("环绕的前置增强");
Object proceed = proceedingJoinPoint.proceed();//执行目标方法
System.out.println("环绕的后置增强");
System.out.println("======================");
return proceed;
}
@AfterThrowing(pointcut = "execution(* com.itheima.service.impl.*.*(..))",throwing = "e")
public void afterThrowingAdvice(Throwable e){
System.out.println("当前的异常信息为:"+e);
System.out.println("异常抛出通知...报异常才执行");
System.out.println("======================");
}
@After("execution(* com.itheima.service.impl.*.*(..))")
public void afterAdvice(){
System.out.println("最终的增强");
System.out.println("======================");
}
}
4.启动类注解@EnableAspectJAutoProxy开启Spring AOP
@SpringBootApplication
@EnableAspectJAutoProxy
public class SpringAopTest01Application {
public static void main(String[] args) {
SpringApplication.run(SpringAopTest01Application.class, args);
}
}
在第三步,使用注解配置通知类型和切点时,五种通知类型中都使用了"execution(* com.itheima.service.impl.*.*(..))"
。这样显然是不够优雅的。
我们可以将切点抽取出来:
@Component
@Aspect
public class MyAdvice2 {
//抽取切点出来
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
public void myPointCut(){}
// @Before("execution(* com.itheima.service.impl.*.*(..))")
@Before("myPointCut()")
public void beforeAdvice(JoinPoint joinPoint){
System.out.println("当前增强的对象是:"+joinPoint.getTarget());
System.out.println("表达式:"+joinPoint.getStaticPart());
System.out.println("注解配置的前置增强...");
System.out.println("======================");
}
// @AfterReturning("execution(* com.itheima.service.impl.*.*(..))")
@AfterReturning("myPointCut()")
public void afterReturningAdvice(JoinPoint joinPoint){
System.out.println("当前增强的对象是:"+joinPoint.getTarget());
System.out.println("表达式:"+joinPoint.getStaticPart());
System.out.println("注解配置的后置增强...");
System.out.println("======================");
}
// @Around("execution(* com.itheima.service.impl.*.*(..))")
@Around("myPointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("当前增强的对象是:"+proceedingJoinPoint.getTarget());
System.out.println("表达式:"+proceedingJoinPoint.getStaticPart());
System.out.println("环绕的前置增强");
Object proceed = proceedingJoinPoint.proceed();//执行目标方法
System.out.println("环绕的后置增强");
System.out.println("======================");
return proceed;
}
// @AfterThrowing(pointcut = "execution(* com.itheima.service.impl.*.*(..))",throwing = "e")
@AfterThrowing(value = "myPointCut()",throwing = "e")
public void afterThrowingAdvice(Throwable e){
System.out.println("当前的异常信息为:"+e);
System.out.println("异常抛出通知...报异常才执行");
System.out.println("======================");
}
// @After("execution(* com.itheima.service.impl.*.*(..))")
@After("myPointCut()")
public void afterAdvice(){
System.out.println("最终的增强");
System.out.println("======================");
}
}
这样基于注解方式的AOP就编写完成了,我们测试一下,调用com.itheima.service.impl包下的类的方法
运行结果:
可以看到这些增强方法都执行了,而且我们并没有修改目标方法。一点原代码都没动,直接在切面类上配置需要被增强的方法的签名,就实现了方法增强,这就是AOP。
六、使用AOP实现日志功能
上面我们使用AOP实现了对方法的增强,这里我们继续使用AOP实现项目中常用的日志记录功能。