一、存在的问题:为方法添加日志和验证功能
- 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
- 代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块
- AOP编程便是为了解决上述问题
二、解决方案
- 使用动态代理
- 代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上。
- AOP
- AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,是对传统OOP(Object-Oriented Programming)的补充
- AOP的主要编程对象是切面(aspect),而切面模块化横切关注点
- 在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能再哪里,以什么方式应用,并且不必修改受影响的类。这样一来横切关注点就不模块化到特殊的对象(切面)里。
- AOP的好处
- 每一个事物逻辑位于一个位置,代码不分散,便于维护和升级
- 业务模块更简洁,只包含核心业务代码
三、AOP术语
- 切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
- 通知(Advice):切面必须要完成的工作
- 目标(Target):被通知的对象
- 代理(Proxy):向目标对象应用通知之后创建的对象
- 连接点(Joinpoint):程序执行的某个特定位置;如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。
- 切点(pointcut):每个类都拥有多个连接点,连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件。
AspectJ:java社区里最完整最流行的AOP框架
在Spring2.0以上版本,可以使用基于AspectJ注解或基于XML配置的AOP
四、使用
- 加入jar包:AspectJ
- 在配置文件中加入aop的命名空间
- 在配置文件中加入配置(通过注解)
<!--使AspectJ注解起作用,自动为匹配的类生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 前置通知
-
把横切关注点的代码抽象到切面的类中
- 切面首先是一个IOC中bean,即加入@Component注解
- 切面还需要加入@Aspect注解
- 在切面类中声明各种通知。在方法前加入@Before注释。方法内可以使用JoinPoint参数,访问细节。
@Before("execution(public int com.atguigu.spring.aopimpl.ArithmeticCalculator.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method:"+methodName+" begin with:"+args);
}
- 后置通知:目标方法执行后(无论是否异常),执行的通知
与@Before一样,在通知方法前使用注释@After
后置通知中还不能访问目标方法执行的结果
@After("execution(public int com.atguigu.spring.aopimpl.ArithmeticCalculator.*(..))")
public void afterMethod(JoinPoint joinPoint){
String method = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method:"+method+" end with"+args);
}
- 返回通知:目标正常执行后执行的通知
可以访问到方法的返回值
@AfterReturning(value = "execution(public int com.atguigu.spring.aopimpl.ArithmeticCalculator.*(..))",
returning = "result")
public void aftetReturning(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println("The method:"+methodName+" end with"+result);
}
- 异常通知
目标方法出现异常会执行的diamante,可以访问到异常对象;其可以在出现指定异常时执行。 - 环绕通知
环绕通知需要携带ProceedingJoinPoint类型的参数。环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。且环绕通知必须有返回值,返回值即目标方法的返回值。
@Around("execution(public int com.atguigu.spring.aopimpl.ArithmeticCalculator.*(..))")
public Object around(ProceedingJoinPoint pjp){
Object result = null;
String methodName = pjp.getSignature().getName();
List<Object> args = Arrays.asList(pjp.getArgs());
try {
//前置通知
System.out.println("The method:"+methodName+" begin with:"+args);
result = pjp.proceed();
//返回通知
System.out.println("The method:"+methodName+" end with:"+result);
} catch (Throwable throwable) {
//异常通知
System.out.println("The method:"+methodName+" occurs with:"+throwable);
}
//后置通知
System.out.println("The method "+methodName+" ends");
return result;
}
五、切面的优先级
@Order(level)指定切面的优先级,值越小优先级越高
六、切点表达式
- 定义一个方法,用于声明切点表达式。一般地,该方法不需要再添入其他代码。
- 使用@Piintcut来声明切入点表达式
- 后面的其他通知直接使用方法名来引用切点表达式
//切点表达式
@Pointcut("execution(public int com.atguigu.spring.aopimpl.ArithmeticCalculator.*(..))")
public void declareJoinPoint(){}
//引用方式
@Before("declareJoinPoint()")
public void beforeMethod(JoinPoint joinPoint){
七、基于配置文件的方式来配置AOP
<aop:config>
<!--配置切点表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.atguigu.spring.aopxml.ArithmeticCalculator.*(..))"/>
<!--配置切面-->
<aop:aspect ref="loggingAspect" order="2">
<!--配置通知-->
<aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
<aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
<aop:after-returning method="aftetReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
<aop:after-throwing method="aftetThrowing" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
</aop:aspect>
</aop:config>