Spring-Aop
目录
1.概念
Aop(Aspect Oriented Programming):面向切面编程
OOP(Object Oriented Programming):面向对象编程
面向切面编程:基于OOP基础之上新的编程思想
指在程序运行期间,将某段代码动态的切入到指定方法的指定位置进行运行的这种编程方式,面向切面编程;
2.应用场景
场景:计算器运行计算方法的时候进行日志记录
实现方式:
1)、直接编写在方法内部;不推荐,修改维护麻烦
日志记录:系统的辅助功能
业务逻辑:(核心功能)
耦合
public class CalculatorImpl implements Calculator {
int result;
public int add(int i, int l) {
LogUntil.logStart("add", i, l);
result = i + l;
LogUntil.logEnd("add", result);
return result;
}
public int sub(int i, int l) {
System.out.println("sub方法前开始调用" + "参数为" + i + "," + l);
result = i - l;
System.out.println("sub方法调用结束" + "结果为" + result);
return result;
}
public int mul(int i, int l) {
System.out.println("mul方法前开始调用" + "参数为" + i + "," + l);
result = i * l;
System.out.println("mul方法调用结束" + "结果为" + result);
return result;
}
public int div(int i, int l) {
System.out.println("div方法前开始调用" + "参数为" + i + "," + l);
result = i / l;
System.out.println("div方法调用结束" + "结果为" + result);
return result;
}
}
2)、动态代理:
业务逻辑:(核心功能);在核心功能运行期间,日志模块自己动态的加上
可以使用动态代理来将日志代码
/**
* 帮calculator生成代理的类
* jdk默认的动态代理,如果目标对象没有实现任何接口,就无法创建代理对象(代理对象和被代理对象实现同一接口)
*/
public class CalculatorProxy {
/**
* 为传入的参数对象创建一个动态代理对象
*/
public static Calculator getProxy(final Calculator calculator) {
//方法执行期,帮我们执行目标方法
/**
* Object proxy:代理对象,给jdk使用,任何时候都不要动这个对象
* Method method:当前将要执行的目标对象的方法
* Object[] args:这个方法调用时外界传入的参数值
*/
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 利用反射执行目标方法
* 目标方法执行后的返回值
*/
Object result = null;
try {
LogUntil2.logStart(method, args);
result = method.invoke(calculator, args);
LogUntil2.logReturn(method, result);
} catch (Exception e) {
LogUntil2.logError(method, e);
} finally {
LogUntil2.logEnd(method);
}
//返回值必须返回出去,外界才能拿到方法执行后的返回值
return result;
}
};
//被加载对象实现的接口
Class<?>[] interfaces = calculator.getClass().getInterfaces();
//被加载对象的类加载器
ClassLoader classLoader = calculator.getClass().getClassLoader();
//proxy为目标对象创建的代理对象
Object proxy = Proxy.newProxyInstance(classLoader, interfaces, h);
return (Calculator) proxy;
}
}
特点:
1)写起来难;
2)、jdk默认的动态代理,如果目标对象没有实现任何接口,就无法为它创建代理对象
AOP(Spring动态代理):
Spring实现了Aop功能;底层就是动态代理
1)、可以利用Spring一句代码都不写的去创建动态代理
实现简单,而且没有强制要求必须实现接口
面向切面编程:将某段代码(日志)动态的切入(不把日志代码写死在业务逻辑方法中)到指定方法(业务逻辑)的指定位置(方法的开始、结束、异常。。)进行运行的 这种编程方式(Aop是Spring简化了面向切面编程)
aop术语:
- 横切关注点:跨越应用程序多个模块的方法或功能,即,与我们业务逻辑无关,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等
- 切面(aspect):横切关注点被模块化的特殊对象,即----一个类
- 通知(advice):切面必须要完成的工作。即----类中的一个方法
- 目标(target):被通知的对象
- 代理(Proxy):向目标对象引用通知之后创建的对象
- 切入点(pointcut):切面通知执行的“地点”的定义
- 连接点(jointPoint):与切入点匹配的执行点
AOP使用步骤:
1.导包
Spring支持面向切面编程的包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
加强版面向切面编程(即使目标对象没有实现任何接口,也能实现动态代理)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
2.写配置
1.将目标类和切面类(封装了通知方法(在目标方法前后执行的方法)加入到ioc容器中)
2.告诉Spring哪一个是切面类(@Aspect)
3.告诉Spring,切面类里面的每一个方法,都是何时何地运行
/**
* 将这个类(切面类)中的这些方法(通知方法)
*/
@Aspect
@Component
public class LogUtil {
/**
* 告诉Spring每个方法什么时候运行
* 五个通知注解:
*
* @Before:在目标方法之前运行 前置通知
* @After:在目标方法结束之后 后置通知
* @AfterReturning:在目标方法正常返回之后 返回通知
* @AfterThrowing:在目标方法抛出异常之后运行 异常通知
* @Around:环绕 环绕通知
* <p>
* try{
* @Before method.invoke(obj, args);
* @AfterReturning }catch(e){
* @AfterThrowing }finally{
* @After }
*/
//执行目标方法之前运行
//切入表达式:execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int com.zh.impl.CalculatorImpl.*(int,int))")
public static void logStart() {
System.out.println("xxxxx方法开始调用,参数为xxxxx");
}
@AfterReturning("execution(public int com.zh.impl.CalculatorImpl.*(int,int))")
//目标方法正常执行完成之后执行
public static void logReturn() {
System.out.println("xxxxx方法调用完成,结果为");
}
@AfterThrowing("execution(public int com.zh.impl.CalculatorImpl.*(int,int))")
//目标方法出现异常的时候执行
public static void logError() {
System.out.println("xxxxx方法执行出错" + "错误信息为");
}
@After("execution(public int com.zh.impl.CalculatorImpl.*(int,int ))")
//目标方法结束时执行
public static void logEnd() {
System.out.println("xxxxx方法执行完成");
}
}
4).开启基于注解的AOP功能
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
AOP细节一:IOC容器中保存的是组件的代理对象
被代理对象实现接口时:jdk创建代理对象
/**:com.zh.impl.CalculatorImpl@f5958c9
class com.sun.proxy.$Proxy13
AOP的底层就是动态代理,容器中保存的组件是它的代理对象,所以不能用本类的类型获取
容器启动时,创建的是代理对象,不是CalculatorImpl,代理对象和目标对象实现同一接口,所以用接口对象获取*/
Calculator bean = context.getBean(Calculator.class);
Calculator bean = (Calculator)context.getBean("calculatorImpl");
被代理对象没有实现接口时:cglib创建对象
/** 没有接口时,用本类类型或者id
* com.zh.impl.CalculatorImpl@65f095f8
* class com.zh.impl.CalculatorImpl$$EnhancerBySpringCGLIB$$3d6a6504
* cglib.jar自动创建一个代理对象
*/
CalculatorImpl bean = context.getBean("calculatorImpl", CalculatorImpl.class);
CalculatorImpl bean = context.getBean(CalculatorImpl.class);
注:
接口不加载在容器中
加在容器中相当于告诉Spring,ioc容器中可能有这种类型的组件
不能直接获取
AOP细节二:切入点表达式的写法:
固定格式:execution(访问权限符 返回值类型 方法全类名(参数表))
通配符:
*:
1).匹配一个或者多个字符:execution(public int com.zh.impl.Calculator*.*.*(int,int))
2).匹配任意一个参数:第一个是int,第二个任意类型
execution(public int com.zh.impl.Calculator*.*.*(int,*))
3).只能匹配一层路径
4).权限位置不能写*:权限位置不写就行
..:1)匹配任意多个参数,任意类型参数
execution(public int com.zh.impl.Calculator*.*.*(..))
2)匹配任意多层路径:
execution(public int com.zh..Calculator*.*.*(..))
记住两种:
最精确的:execution(public int com.zh.impl.CalculatorImpl.add(int,int))
最模糊的:execution(* *(..))//任意包下任意类的任意方法
&&、||、!
&&:同时满足两个表达式
execution(public int com.zh.impl.Calculator*.*.*(int,*))&&execution(public int com.zh.impl.Calculator*.*.*(int,int))
||:满足任意一个表达式就行
execution(public int com.zh.impl.Calculator*.*.*(int,*))||execution(public int com.zh.impl.Calculator*.*.*(int,int))
!:只要不是这个位置都行
execution(public int com.zh.impl.Calculator*.*.*(int,*))
AOP细节三:通知方法的执行顺序
try{
@Before
mehotd.invoke()
@AfterReturing
}catch(e){
@AfterThrowing
}finally{
@After
}
正常执行:@Before(前置通知)=》@After(后置通知)==》@AfterReturning(返回通知)
异常执行:@Before(前置通知)》@After(后置通知)》@AfterThrowing(异常通知)
AOP细节四:通知方法运行时,获取目标方法详细信息
1.JoinPoint获取目标方法的详细信息
@Before("execution(public int com.zh..Calculator*.*(..))")
public static void logStart(JoinPoint joinPoint) {
//获取到目标方法运行时使用的参数
Object[] args = joinPoint.getArgs();
//获取方法名
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName+"方法开始调用,参数为"+Arrays.asList(args));
}
2.获取返回值:
/**
*告诉Spring哪个参数接收返回值
*returning="reslut"
*Object result:指定方法接收哪种类型返回值
*/
@AfterReturning(returning ="result")
//目标方法正常执行完成之后执行
public static void logReturn(JoinPoint joinPoint,Object result) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { System.out.println(joinPoint.getSignature().getName()+"方法调用完成,结果为"+result);
}
3.接收异常信息:
/**
*告诉Spring哪个参数获取异常
* throwing="exception"
*Exception exception:指定方法可以接收哪些异常
*/
@AfterThrowing(value="execution(public int com.zh.impl.CalculatorImpl.*(int,int))",throwing="exception")
//目标方法出现异常的时候执行
public static void logError(JoinPoint joinPoint,Exception exception) {
System.out.println(joinPoint.getSignature().getName()+"方法执行出错" + "错误信息为"+exception);
}
4.Spring对通知方法的约束
Spring对通知方法要求不严格(修饰符,返回值)
唯一要求就是通知方法的参数列表一定不能乱写
通知方法是Spring利用反射调用的,每次方法调用的确定这个方法参数列表的值
参数表上的每个参数,Spring都得知道是什么
returning=“result”
throwing=“exception”
AOP细节五:抽取可重用的切点表达式
1.随便声明一个没有实现的返回void的空方法
public void myPoint(){};
2.给方法上添加注解@Pointcut("execution("切入点表达式")")
@Pointcut("execution(public int com.zh..Calculator*.*(..))")
public void myPoint(){};
3.使用
@Before(value="myPoint()")
public static void logStart(JoinPoint joinPoint) {}
AOP细节六:@Around(环绕通知)
@Around:环绕通知(Spring中最强大的通知)
使用:
@Around("myPoint()")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
Object proceed=null;
try {
//@Before
System.out.println("环绕前置通知"+pjp.getSignature().getName()+"方法执行,参数为"+Arrays.asList(pjp.getArgs()));
//利用反射调用目标方法,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturning
System.out.println("环绕返回通知"+pjp.getSignature().getName()+"方法调用完成,返回值为"+proceed);
} catch (Exception e) {
//@AfterThrowing
System.out.println("环绕异常通知,"+pjp.getSignature().getName()+"方法异常,异常信息为"+e);
// 为了让外界能知道这个异常,一定要抛出去
throw new RuntimeException(e);
} finally {
//@After
System.out.println("环绕后置通知"+pjp.getSignature().getName()+"运行结束");
}
//返回值一定是方法调用后的返回值
return proceed;
}
环绕通知执行顺序:
/**
* {
* try{
* 环绕前置
* 环绕执行:ProceedingJoinPoint执行目标方法
* 环绕返回
* }catch(){
* 环绕异常
* }finally{
* 环绕后置
* }
* }
* 【普通后置】
* 【普通返回/异常】
*执行顺序:
* (环绕前置===》普通前置)===》环绕异常/环绕返回===》环绕后置===》普通后置===》普通返回/异常
*/
多切面运行顺序:先进后出
环绕通知只影响当前切面
切面默认执行顺序,按照类名首字母顺序
可以使用@Order()改变切面执行顺序,数字越小,优先级越高
AOP使用场景:
1.AOP加日志保存到数据库
2.AOP做权限验证
3.AOP做安全检查
4.AOP做事务控制
基于配置的AOP
1.基于注解的AOP步骤:
1.将切面类和目标类加入到ioc容器
2.告诉Spring哪个是切面类(@Aspect)
3.在切面类中使用通知注解,来配置切面中的通知方法何时何地运行
4.开启基于注解的AOP功能
<aop:aspectj-autoproxy/>
2.基于配置的AOP
1.目标类:CalculatorImpl
public class CalculatorImpl {
public int add(int i, int l) {
return i+l;
}
public int sub(int i, int l) {
return i-l;
}
public int mul(int i, int l) {
return i*l;
}
public int div(int i, int l) {
return i/l;
}
}
2-1.切面类:LogUtil
package com.zh.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
public class LogUtil {
public void logStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName + "方法开始调用,参数为" + Arrays.asList(args));
}
public static void logReturn(JoinPoint joinPoint, Object result) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
System.out.println(joinPoint.getSignature().getName() + "方法调用完成,结果为" + result);
}
public static void logError(JoinPoint joinPoint, Exception exception) {
System.out.println(joinPoint.getSignature().getName() + "方法执行出错" + "错误信息为" + exception);
}
public static void logEnd(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + "方法执行完成");
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
Object proceed = null;
try {
//@Before
System.out.println("环绕前置通知" + pjp.getSignature().getName() + "方法执行,参数为" + Arrays.asList(pjp.getArgs()));
//利用反射调用目标方法,就是method.invoke(obj,args)
proceed = pjp.proceed(args);
//@AfterReturning
System.out.println("环绕返回通知" + pjp.getSignature().getName() + "方法调用完成,返回值为" + proceed);
} catch (Exception e) {
//@AfterThrowing
System.out.println("环绕异常通知," + pjp.getSignature().getName() + "方法异常,异常信息为" + e);
// 为了让外界能知道这个异常,一定要抛出去
throw new RuntimeException(e);
} finally {
//@After
System.out.println("环绕后置通知" + pjp.getSignature().getName() + "运行结束");
}
//返回值一定是方法调用后的返回值
return proceed;
}
}
2-2.切面类:ValidatorAspect
package com.zh.utils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
public class ValidatorAspect {
public void valStart(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println("ValidatorAspect" + methodName + "方法开始调用,参数为" + Arrays.asList(args));
}
public void valReturn(JoinPoint joinPoint, Object result) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//获取方法名
System.out.println("ValidatorAspect" + joinPoint.getSignature().getName() + "方法调用完成,结果为" + result);
}
//目标方法出现异常的时候执行
public void ValError(JoinPoint joinPoint, Exception exception) {
System.out.println("ValidatorAspect" + joinPoint.getSignature().getName() + "方法执行出错" + "错误信息为" + exception);
}
//目标方法结束时执行
public void valEnd(JoinPoint joinPoint) {
System.out.println("ValidatorAspect" + joinPoint.getSignature().getName() + "方法执行完成");
}
}
3.Application-context:Spring配置文件
<!-- 基于配置的AOP-->
<bean id="calculatorImpl" class="com.zh.impl.CalculatorImpl"/>
<bean id="logUntil" class="com.zh.utils.LogUtil"/>
<bean id="validatorAspect" class="com.zh.utils.ValidatorAspect"/>
<aop:config>
<!--可重用切点表达式-->
<aop:pointcut id="mypPointcut" expression="execution(* com.zh..Calculator*.*(..))"/>
<!--指定切面-->
<aop:aspect ref="logUntil">
<!--前置通知-->
<aop:before method="logStart" pointcut-ref="mypPointcut"/>
<!--返回通知-->
<aop:after-returning method="logReturn" pointcut-ref="mypPointcut" returning="result"/>
<!--异常通知-->
<aop:after-throwing method="logError" pointcut-ref="mypPointcut" throwing="exception"/>
<!--后置通知-->
<aop:after method="logEnd" pointcut-ref="mypPointcut"/>
<!--环绕通知-->
<aop:around method="myAround" pointcut-ref="mypPointcut"/>
</aop:aspect>
<aop:aspect ref="validatorAspect" order="-1">
<aop:before method="valStart" pointcut-ref="mypPointcut"/>
<aop:after-returning method="valReturn" pointcut-ref="mypPointcut" returning="result"/>
<aop:after-throwing method="ValError" pointcut-ref="mypPointcut" throwing="exception"/>
<aop:after method="valEnd" pointcut-ref="mypPointcut"/>
</aop:aspect>
</aop:config>
4.测试:
public class test{
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("Application-config.xml");
CalculatorImpl bean = context.getBean(CalculatorImpl.class);
bean.div(1,2);
}
}