什么是AOP?
- AOP:(Aspect Oriented Programming)面向切面编程
- OOP:(Object Oriented Programming )面向对象编程
- 面向切面编程:基于OOP基础之上新的编程思想,是指:在程序运行期间,将某段代码
动态的切入
到指定方法
的指定位置
进行运行的编程方式。
典型案例
-
场景:计算器运行四种计算方法(加减乘除)的时候进行日志记录。
-
加日志记录:
①直接编写在方法内部:修改维护麻烦,牵一发动全身,不推荐。- 日志记录:系统的辅助功能。
- 业务逻辑:(核心功能)。
- 这两者不能耦合在一起。
②我们希望的是:
- 业务逻辑:(核心功能)。
- 日志模块;在核心功能运行期间,日志功能可以自己动态的加上。
- 解耦。
所以:我们可以使用动态代理来将日志代码动态的在目标方法执行前后先进行执行。
package com.atguigu.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import com.atguigu.inter.Calculator;
import com.atguigu.utils.LogUtils;
/**
* 帮Calculator.java生成代理对象的类
* Object newProxyInstance
* (ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
*
* @author lfy
*
*/
public class CalculatorProxy {
/**
* 为传入的参数对象创建一个动态代理对象
* @param calculator
* @return
*
* Calculator calculator:被代理对象;(宝宝)
* 返回的:宋喆
*/
public static Calculator getProxy(final Calculator calculator) {
// TODO Auto-generated method stub
//方法执行器。帮我们目标对象执行目标方法
InvocationHandler h = new InvocationHandler() {
/**
* Object proxy:代理对象;给jdk使用,任何时候都不要动这个对象
* Method method:当前将要执行的目标对象的方法
* Object[] args:这个方法调用时外界传入的参数值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//System.out.println("这是动态代理将要帮你执行方法...");
Object result = null;
try {
LogUtils.logStart(method, args);
// 利用反射执行目标方法
//目标方法执行后的返回值
result = method.invoke(calculator, args);
LogUtils.logReturn(method, result);
} catch (Exception e) {
LogUtils.logException(method,e);
}finally{
LogUtils.logEnd(method);
}
//返回值必须返回出去外界才能拿到真正执行后的返回值
return result;
}
};
Class<?>[] interfaces = calculator.getClass().getInterfaces();
ClassLoader loader = calculator.getClass().getClassLoader();
//Proxy为目标对象创建代理对象;
Object proxy = Proxy.newProxyInstance(loader, interfaces, h);
return (Calculator) proxy;
}
}
动态代理
- 普通的动态代理,写起来难。
- 问题:jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为他创建代理对象的。
- 所以Spring实现了AOP功能;底层就是动态代理。将某段代码(日志)
动态的切入
(不把日志代码写死在业务逻辑方法中)到指定方法
(加减乘除)的指定位置
(方法的开始、结束、异常。。。)进行运行的这种编程方式。
①可以利用Spring一句代码都不写的去创建动态代理。
②实现简单,而且没有强制要求目标对象必须实现接口。
专业术语
AOP使用步骤
- 导包
commons-logging-1.1.3.jar
spring-aop-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
Spring支持面向切面编程的包是:
spring-aspects-4.0.0.RELEASE.jar:基础版
加强版的面向切面编程(即使目标对象没有实现任何接口也能创建动态代理)
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- 写配置
①将目标类和切面类(封装了通知方法(在目标方法执行前后执行的方法))加入到ioc容器中。(加注解)
②还应该告诉Spring到底哪个是切面类@Aspect
③告诉Spring,切面类里面的每一个方法,都是何时何地运行。
④开启基于注解的AOP模式
-
5个通知注解
@Before
:在目标方法之前运行;(前置通知)@After
:在目标方法结束之后 (后置通知)@AfterReturning
:在目标方法正常返回之后 (返回通知)@AfterThrowing
:在目标方法抛出异常之后运行 (异常通知)@Around
:环绕 (环绕通知)
-
切入点表达式:
execution(访问权限符 返回值类型 方法签名)
,方法签名:直接选中方法右键选择Copy Qualified Name
复制即可。
@Before("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logStart(){
System.out.println("【xxx】方法开始执行,用的参数列表【xxx】");
}
//想在目标方法正常执行完成
@AfterReturning("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logReturn(){
System.out.println("【xxxx】方法正常执行完成,计算结果是:");
}
//想在目标方法出现异常的时候执行
@AfterThrowing("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logException() {
System.out.println("【xxxx】方法执行出现异常了,异常信息是:;这个异常已经通知测试小组进行排查");
}
//想在目标方法结束的时候执行
@After("execution(public int com.atguigu.impl.MyMathCalculator.*(int, int))")
public static void logEnd() {
System.out.println("【xxx】方法最终结束了");
}
在配置文件中写:
<!-- 开启基于注解的AOP功能,aop名称空间-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
- 测试
//这里按照类型取对象一定要用接口去取。没有接口就用本类原型
Calculator bean = ioc.getBean(Calculator.class);
bean.add(2, 1);
注意事项
- 从ioc容器中拿到目标对象;注意:如果想要用类型,一定用 他的接口类型,不要用它本类。
- cglib为没有接口的组件也可以创建代理对象。
- 切入点表达式:
execution(访问权限符 返回值类型 方法签名)
- 通知顺序:
① 正常执行:@Before(前置通知)
=@After(后置通知)
=@AfterReturning(正常返回)
②异常执行:@Before(前置通知)
=@After(后置通知)
=@AfterThrowing(方法异常)
- JoinPoint获取目标方法的信息。
//想在执行目标方法之前运行;写切入点表达式
//execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int com.atguigu.impl.MyMathCalculator.*(..))")
public static void logStart(JoinPoint joinPoint){
//获取到目标方法运行是使用的参数
Object[] args = joinPoint.getArgs();
//获取到方法签名
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println("【"+name+"】方法开始执行,用的参数列表【"+Arrays.asList(args)+"】");
}
AOP使用场景
- AOP加日志保存到数据库
- AOP做权限验证
- AOP做安全检查
- AOP做事务控制