AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。
AOP是指在程序运行期间,将代码(如日志类型的代码)动态的切入到指定方法中的指定位置(核心业务方法运行位置的前面,后面,异常,finally结束)来运行,目的是避免这种日志类的代码和核心业务代码耦合,增强了重用性和扩展性。
使用“横切”技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。Aop 的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来,AOP的核心思想是将应用程序中的商业逻辑同对其提供支持的通用服务进行分离
实现AOP的技术,主要分为两大类:一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。
案例:为计算器程序添加日志功能
如果直接加到程序里或封装一个LogUtils会造成业务层(核心功能)与日志层(辅助层)的耦合,这是javase的传统写法,大家应该都很熟悉了。
而我们需要的是,日志记录可以在调用方法的时候就自动加上 => 要使用用动态代理,将日志代码动态的在目标方法的前后执行,这是使用动态代理的代码:动态代理实现计算器类日志功能,大家可以学习一下增强内功,否则下面的内容不好理解
传统的动态代理(已经是面向切面了)存在的缺点:1 对应大型的项目来说每一个模块都需要写一次动态代理,麻烦 2 一个致命缺点是,我们无法为没有实现接口的类进行动态代理,针对这两种缺点,spring对该模式做了全面的升级,可以不写一行代码去创建动态代理且,大大降低了使用难度,且不强制要求被代理类一定要实现某个接口
六个专业名词
连接点:类似于Mysql数据表中的数据,上面的每个点代表一个连接点,本例数目为核心方法数*4
切入点:类似我们想要查询得到的结果,也就是我们真正需要执行日志记录的地方(标红的点)
切入点表达式:类似于sql语句,在众多的连接点中选出我们感兴趣的地方
横切关注点:每一个核心方法的插入的位置代表,共四个位置
通知方法:每一个横切关注点对应一个通知方法,有四个
切面类:集中定义了通知方法(logUtils)
spring实际开发中我们更多要考虑切面类中的通知方法动态的在目标方法的各个位置进行插入,而不是一次次的重复书写各个动态代理类,也不需要每次为代理类创建一个接口,实现方式如下
1 导包或引入pom依赖
2 ①写配置 ,将目标类和切面类加入到ioc容器中(通过那四类注解@Component等)②告诉spring到底是哪个切面类(通过注解@Aspect + @Component,相当于告诉了spring,只要有切面存在那么就给根据目标类的接口给目标类创建代理类对象) ③告诉spring切面类中的每一个方法到底何时何地运行 ,即通知注解,共有5类具体参考如下
//当添加完注解,那么通知方法左边会有@小箭头提示证明你写对了
try{
@Before:使切面类方法在目标方法前运行,前置通知 //无论何时都第一个执行
mothed.invoke(obj,args);
@AfterReturning :使切面类方法在目标方法正常返回后,返回通知 //正常情况第三个执行,出现异常不执行
}catch{
@AfterThrowing:使切面类方法在目标放抛出异常后执行,异常通知 //正常情况不执行,出现异常第三个执行
}fianlly{
@After 在目标方法后运行 ,后置通知 //无论何时都第二个执行
}
关于通知方法有5种,其中最强大的通知注解是@Around,该注解通过出现在不同的位置取代任意以上四种注解使用,以上四种注解只是标明了方法的运行时机,即表名了四个横切关注点,但是没有说明这四个横切关注点到底依赖于哪个方法(没有说明到底打印哪个方法的运行日志),我们需要用切入点表达式通过execute(访问权限修饰符 返回值类型 方法全类名(参数列表)),案例在下面。
3 Spring配置文件头部
<!-- 自动包扫描,扫描相关的组件-->
<context:component-scan base-package="caculator.Interface"></context:component-scan>
<!-- 开启基于注解的aop功能-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
4 案例代码
① 先看下我的层级结构,这一点很重要
② 切面类(LogUtils)
@Aspect
@Component
public class logUtils {
@Pointcut("execution(public int caculator.Interface.MyCaculator.*(int,int))")
public void test(){
}
@Before("test()")
public static void logStart(JoinPoint joinPoint){
System.out.println("logUtil前置方法【"+joinPoint.getSignature().getName()+"】开始执行" + "调用的参数是:" + Arrays.asList(joinPoint.getArgs()));
}
@AfterReturning(value = "test()",returning = "result")
public void logReturn(JoinPoint joinPoint,Object result){
System.out.println("logUtil返回方法【"+joinPoint.getSignature().getName()+"】执行完毕" + "返回的结果是:" + result);
}
@AfterThrowing(value= "test()",throwing = "exception")
public void logException(JoinPoint joinPoint,Exception exception){
System.out.println("logUtil异常方法【"+joinPoint.getSignature().getName()+"】出现异常" + "异常的原因是:" + exception);
}
@After("test()")
public static void logAfter(JoinPoint joinPoint){
System.out.println("logUtil结束方法【"+joinPoint.getSignature().getName()+"】结束执行");
}
③ 接口
/*
接口不需要加载到容器,即使加载了也不会被创建bean组件,但是相当于告诉了spring ioc容器中可能有相关类型的组件
*/
public interface Caculator {
public int add(int op1, int op2);
public int sub(int op1, int op2);
public int mul(int op1, int op2);
public int div(int op1, int op2);
}
④ 被代理类
@Component
public class MyCaculator implements Caculator{
@Override
public int add(int op1, int op2) {
return op1 + op2;
}
@Override
public int sub(int op1, int op2) {
return op1 - op2;
}
@Override
public int mul(int op1, int op2) {
return op1 * op2;
}
@Override
public int div(int op1, int op2) {
return op1 / op2;
}
}
⑤ 测试类
@Test
public void test() {
//基于有接口注解的aop
ApplicationContext ioc = new ClassPathXmlApplicationContext("config/annotation.xml");
//这相当于在容器中寻找Caculator类型的bean组件,最后找到的是组件caculator.MyCaculator@3b5fad2d(本质上也是Caculator类型);
Caculator caculator = ioc.getBean(Caculator.class);
System.out.println(caculator);//caculator.MyCaculator@3b5fad2d
System.out.println(caculator.getClass());//class com.sun.proxy.$Proxy27
caculator.add(1, 1);
//底层原理可概括为Proxy calculator = new MyCaculator();验证如下
if(caculator instanceof Proxy){
System.out.println("Caculator是Proxy的子接口");
}
}
结果
补充:此时接口不是必须的,我们去掉接口(过程略)然后进行测试
@Test
public void test1(){
//基于无接口注解的aop,前提是导入cglib包或依赖
ApplicationContext ioc = new ClassPathXmlApplicationContext("config/annotation.xml");
//经测试可知即使没有实现任何接口的目标类也可以被创建相应的代理对象,并且可以通过execute指定切面类的切面方法执行的时机
MyCaculator bean = ioc.getBean(MyCaculator.class);
//输出结果是class caculator.MyTCaculator$$EnhancerBySpringCGLIB$$dce8466b,这是典型的内部类
//也就是相当于在MyCaculator运行期间创建了MyCaculator的动态内部类作为代理类
System.out.println(bean.getClass());
bean.add(1,1);
}
结果
推荐学习:AOP5类通知注解,同样的案例,同样的经典!