文章目录
定义
AOP: 全称是 Aspect Oriented Programming 即: 面向切面编程
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强
AOP 的作用及优势
作用:在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
实现方式
使用动态代理技术
术语
Joinpoint(连接点)
所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点
ProceedingJoinPoint的使用和作用
ProceedingJoinPoint继承了Joinpoint,所有ProceedingJoinPoint具有连接点的特性
注意:ProceedingJoinPoint只能在循环通知中使用,并且将结果返回,如果不返回相当于结果再此被拦截,请求方收到的就是空数据
使用:ProceedingJoinPoint作为通知方法的参数被传入
代码案例:
import com.dianping.cat.Cat;
import com.dianping.cat.message.Transaction;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class CatAopService {
@Around(value = "@annotation(catAnnotation)")
public Object aroundMethod(ProceedingJoinPoint pjp, CatAnnotation catAnnotation) throws Throwable {
MethodSignature joinPointObject = (MethodSignature) pjp.getSignature();
Method method = joinPointObject.getMethod();
Transaction t = Cat.newTransaction(method.getName(), method.getName());
try {
Object res = pjp.proceed();
t.setSuccessStatus();
return res;
} catch (Throwable e) {
t.setStatus(e);
Cat.logError(e);
throw e;
} finally {
t.complete();
}
}
}
- 获取切入点方法的名字:getSignature());是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
- 获取方法上的注解:
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
xxxxxx annoObj= method.getAnnotation(xxxxxx.class);
}
- 获取方法的参数:Object[] args = joinPoint.getArgs(); ,此处是获取方法上的参数,而不是HttpServletRequest对象的请求参数,所以GET和POST方法的请求参数都可以通过此方法获取
- 放行并获取返回结果:Object result = proceedingJoinPoint.proceed();
Pointcut(切入点)
所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
切入点表达式说明
表达式语法: execution([修饰符] 返回值类型 包名.类名.方法名(参数))
全匹配方式:
public void com.service.impl.AccountServiceImpl.saveAccount(com.itheima.domain.Account)
- 访问修饰符可以省略
- 包名可以使用
*
号,表示任意包,但是有几级包,需要写几个*
,使用..
来表示当前包,及其子包 - 类名可以使用
*
号,表示任意类 - 方法名可以使用
*
号,表示任意方法 - 参数列表可以使用
*
,表示参数可以是任意数据类型,但是必须有参数,也可以使用..
表示有无参数均可,有参数可以是任意类型 - 全通配方式:
* *..*.*(..)
- 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
execution(* com.service.impl.*.*(..))
注解的方式
使用within表达式匹配包类型
//匹配ProductServiceImpl类里面的所有方法
@Pointcut("within(com.aop.service.impl.ProductServiceImpl)")
public void matchType() {}
//匹配com.aop.service包及其子包下所有类的方法
@Pointcut("within(com.aop.service..*)")
public void matchPackage() {}
使用this、target、bean表达式匹配对象类型
//匹配AOP对象的目标对象为指定类型的方法,即ProductServiceImpl的aop代理对象的方法
@Pointcut("this(com.aop.service.impl.ProductServiceImpl)")
public void matchThis() {}
//匹配实现ProductService接口的目标对象
@Pointcut("target(com.aop.service.ProductService)")
public void matchTarget() {}
//匹配所有以Service结尾的bean里面的方法
@Pointcut("bean(*Service)")
public void matchBean() {}
使用args表达式匹配参数
//匹配第一个参数为Long类型的方法
@Pointcut("args(Long, ..) ")
public void matchArgs() {}
使用@annotation、@within、@target、@args匹配注解
//匹配标注有AdminOnly注解的方法,比较灵活,也常用
@Pointcut("@annotation(com.aop.annotation.AdminOnly)")
public void matchAnno() {}
//匹配标注有Beta的类底下的方法,要求annotation的Retention级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void matchWithin() {}
//匹配标注有Repository的类底下的方法,要求annotation的Retention级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void matchTarget() {}
//匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void matchArgs() {}
Advice(通知/增强)
所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
通知的类型: 前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象)
代理的目标对象
Weaving(织入)
是指把增强应用到目标对象来创建新的代理对象的过程。
spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
Proxy(代理)
一个类被 AOP 织入增强后,就产生一个结果代理类
Aspect(切面)
是切入点和通知(引介)的结合
代码案例
注解的方式
需要用到的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- AOP注解用到的 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.6.RELEASE</version>
</dependency>
applicationContext.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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/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.springtest"></context:component-scan>
<bean id="chechAgeAspect" class="com.aspect.ChechAgeAspect"></bean>
<!--下面这段是强制使用CGLIB代理模式
分步讲解:aop:aspectj-autoproxy的作用开启AOP的注解
proxy-target-class="true"是强制使用CGLIB创建代理
expose-proxy="true"实现类的内部调用也可以增强方法,
不过就不能直接调用内部方法了,
需要用((Student) AopContext.currentProxy()).internalMethod();,(Student)为需要类本身,internalMethod()为Student的内部类,当Student的其他方法调用internalMethod方法,
且internalMethod方法要实现增强-->
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
</beans>
切面类
@Component
@Aspect
public class ChechAgeAspect {
@Pointcut("execution(* com.springtest.*.*(..))")
public void expressionMethod() {
}
@Before("expressionMethod()") // 表达式的方式。此种方式下面的方法中不能有参数,两种方式都可以
//@annotation属性的参数可以引用方法中的参数,必须和方法中的参数名一致,也可以使用全限定类名
@Before("@annotation(myAspectAnnotation)")// 注解的方式
public void pointCutMethod(MyAspectAnnotation myAspectAnnotation) {
System.out.println("执行了切面中的方法");
}
}
Student测试类
@Data
@Component
public class Student {
@MyAspectAnnotation
public void testAspect() {
System.out.println("测试方法");
}
}
自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspectAnnotation {
}
测试类
public class SpringMainClass {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Teacher student = context.getBean(Teacher.class);
student.invokeTestAspect();
}
}
xml配置文件的方式
applicationContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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/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.springtest"></context:component-scan>
<!--切面类-->
<bean id="checkAspect" class="com.aspect.CheckAspect"></bean>
<!--切面配置-->
<aop:config>
<!--id将被aop:before的pointcut-ref属性值引用-->
<aop:pointcut id="adviseMethod" expression="execution(* com.springtest..*.*(..))"/>
<!--id起的有意义即可,ref引用的是切面类-->
<aop:aspect id="checkAspectAdviseClass" ref="checkAspect">
<!--method即增强的方法,pointcut-ref引用的是aop:pointcut的id属性值-->
<aop:before method="pointCutMethod" pointcut-ref="adviseMethod"></aop:before>
</aop:aspect>
</aop:config>
</beans>