AOP: (Aspect Oriented Programming) 面向切面编程
OOP:(Object Oriented Programming) 面向对象编程
面向切面编程: 基于oop基础的新编程思想;
指在程序运行期间, 将某段代码动态的切入到指定方法的指定位置进行运行的编程方式, 面向切面编程
场景: 计算器运行计算方法的时候进行日志记录;
加日志记录:
1) 直接编写在方法内部; 不推荐, 维护麻烦.
日志记录: 系统的辅助功能;
业务逻辑: 核心功能;
耦合度高
2) 使用动态代理
public class CalculatorProxy {/** @description 为传入参数对象创建一个动态代理对象* @param calculator* @return com.yguilai.inter.Calculator*/public static Calculator getProxy(Calculator calculator) {ClassLoader classLoader = calculator.getClass().getClassLoader();Class<?>[] classes = calculator.getClass().getInterfaces();// 方法执行器, 帮助目标对象执行目标方法InvocationHandler invocationHandler = new InvocationHandler() {/** @description* @param proxy 代理对象* @param method* @param args* @return java.lang.Object*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object invoke = null;try {System.out.println("[" + method.getName() + "]方法执行, 参数" + Arrays.asList(args));invoke = method.invoke(calculator, args);System.out.println("[" + method.getName() + "]方法正常执行完成, 结果[" + invoke + "]");} catch (Exception e) {System.out.println("[" + method.getName() + "]方法执行异常, 异常信息: " + e.getCause());//e.printStackTrace();} finally {System.out.println("[" + method.getName() + "]方法结束");}return invoke;}};// Proxy为目标对象创建代理对象Object proxy = Proxy.newProxyInstance(classLoader, classes, invocationHandler);return (Calculator) proxy;}}
jdk默认动态代理缺陷: 如果目标对象没有实现任何接口是无法为他创建代理对象的. 代理对象和被代理对象唯一能产生的关联就是实现了相同的接口.
3) 利用SpringAOP创建动态代理, 实现简单, 没有强制要求目标对象必须实现接口
AOP专业术语:
切面类
- 横切关注点
- 通知方法
连接点
- 切入点: 真正动态切入代码的位置
- 切入点表达式
AOP使用步骤:
导包 spring-aspects (基础版)
- 基础版
xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.0.0.RELEASE</version> </dependency>
- 基础版
- 加强版```xml
cglib cglib 2.2 aopalliance aopalliance 1.0 ```org.aspectj aspectjweaver 1.6.8
2.配置
1> 将目标类和切面类加入到容器中
2> 告诉spring哪个是切面类
3> 告诉spring, 切面类的通知方法何时何地运行
1)添加注解:
@Before: 在目标方法之前运行 前置通知
@After: 在目标方法结束之后 后置通知
@AfterReturning: 在目标方法正常返回之后 返回通知
@AfterThrowing: 在目标方法抛出异常之后运行 异常通知
@Around: 环绕 环绕通知
2)写切入点表达式
execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))")
3)开启基于注解的AOP模式
3.测试
细节零. 从ioc容器拿到目标对象, 注意:如果目标对象的本类实现了接口, 想要用类型获取, 一定要用它的接口类型, 不能用它本类
细节一. AOP底层就是动态代理, 容器中保存的组件是目标对象的代理对象, 不是其本类; 如果目标类实现了接口, Spring就会使用动态代领创建代理对象, 如果没有实现接口则会使用cglib创建代理对象
细节二. 切入点不表达式的写法: 固定格式execution(访问权限符 返回值类型 方法全类名(参数表)), 具体参数名不写
通配符:
- : 1)匹配任意个字符 2)匹配任意类型参数 3)如果
*放在路径位置只能匹配一层路径 4)*不能放在访问权限符位置, 权限位置不写即为任意类型(实际上也只能切入public)
.. : 1)匹配任意个任意类型参数execution(public int com.yguilai.impl.MyMathCalculator.*(..))2)匹配任意多层路径execution(public int com.yguilai..MyMathCalculator.*(..))
其他特殊符:&&,||,!细节三. 通知方法的执行顺序
正常执行: @Before —> @After —> @AfterReturning
异常执行: @Before —> @After —> @AfterThrowing细节四. 通知方法运行时, 使用JoinPoint拿到目标方法的详细信息
细节五. 指定通知方法的参数来接收returning, throwing的返回值和异常值
接收返回值:
@AfterReturning(value = "execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))", returning = "result") public static void logReturn(JoinPoint joinPoint, Object result)
接收异常值:
@AfterThrowing(value = "execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))", throwing = "e") public static void logException(JoinPoint joinPoint, Exception e)细节六. Spring对通知方法的约束
1.Spring对通知方法的要求不严格.
2.唯一的要求是方法的参数猎豹不能乱写! 通知方法是spring利用反射调用的, 每次方法调用得确定这个方法的参数表的值, 因此参数表上的每一个参数都必须让spring能够识别出
3.接收异常或返回值时,应该将接收范围扩大, 如使用Exception, Object细节七. 抽取可重用的切入点表达式:
1.随便生命一个没有实现的返回void的空方法
2.给方法标注@Pointcut注解
3.原通知方法注解的value值改为空方法名的调用
@After(value = "pointCut()")
public static void logFinish(JoinPoint joinPoint) {
System.out.println("[" + joinPoint.getSignature().getName() + "]方法结束");
}
@Pointcut("execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))")
public void pointCut(){}
细节八. @Around环绕通知: Spring中最强大的通知方法(根本上就是动态代理)
环绕通知就是: 前置通知,后置通知,返回通知,异常通知四合一. 环绕通知中有一个参数
环绕通知是优先于普通通知的
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
// 利用反射调用目标方法
Object proceed = null;//相当于method.invoke(obj, args)
try {
//@Before()
proceed = pjp.proceed(pjp.getArgs());
//@AfterReturning
} catch (Throwable throwable) {
//@AfterThrowing
throwable.printStackTrace();
} finally {
//@After
}
return proceed;
}
细节九. 多切面运行顺序
先进后出, 后进先出
@Order(int) 通过注解修改切面运行顺序, 数值越小 优先级越高
环绕通知只影响当前切面的通知方法运行顺序
AOP使用场景:
1> AOP加日志保存到数据库
2> AOP做权限验证
3> AOP做安全检查
4> AOP做事务控制
基于注解的AOP模式小结:
- 将目标类和切面类加入到ioc容器中(添加@Component) )
- 告诉spring哪个是切面类(添加@Aspect) )
- 在切面类内配置通知方法的注解(@Before, @After, @AfterReturning, @AfterThrowing, @Around) )
- 在spring配置文件中开启基于注解的AOP功能(aop:aspectj-autoproxy/)
基于配置的AOP模式:
<aop:config>
<!--指定切面类, 相当于@Aspect-->
<aop:aspect ref="logUtils">
<!--指定通知方法-->
<aop:before method="logStart" pointcut-ref="pointcut"/>
<aop:after method="logFinish" pointcut-ref="pointcut"/>
<aop:after-returning method="logReturn" returning="result" pointcut-ref="pointcut"/>
<aop:after-throwing method="logException" throwing="e" pointcut-ref="pointcut"/>
<aop:pointcut id="pointcut"
expression="execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))"/>
</aop:aspect>
</aop:config>
注解: 快速方便
配置: 功能完善; 重要的用配置, 不重要的用注解
