AOP:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
其实我最讨厌这种官方的话语,什么叫AOP?什么是面向切面编程?
举个常见的例子来说,比如,我们一般的开发项目,主要就是前段界面,后端逻辑部分再加上数据库的三层基本架构,这种从前端到逻辑层再到数据的过程,叫做纵向开发。那么问题来了,如果想在纵向开发的项目中,横向插入一些功能,该怎么实现?什么叫横向插入?举个例子,比如,项目功能都已经全部写好了,现在突然要插入日志功能,这个日志功能,只需要在代码逻辑里面插入,不需要在前端数据库插入,这就相当于在三层的纵向结构中,横向在中间逻辑层插入日志功能,这就是所谓的面向切面编程。也叫作AOP。
Spring的AOP功能强大在哪里?
解释完什么是AOP,那么,为什么Spring要专门研究AOP,并且AOP还是Spring的核心功能,不就是横向插入一些功能吗?加个日志,写就完事了,有这么简单吗?当然不会这么简单。试想一下,如果代码非常庞大,加日志这个功能,是不是要一个个类一个个方法的添加?除了麻烦的问题,稍微有点代码经验的都知道,不能随便更改已经写好的代码功能,经常会有牵一发动全身的问题,稍微改一个小地方,可能会导致到处报错,这些都是开发过程的大忌。
那么,怎么在不更改已有代码的情况下实现横向添加功能呢?这就是Spring帮我们解决的了。
其实,Spring底层是根据动态代理来实现AOP功能的,关于动态代理的设计模式和思想,我在上一篇博客中已经详细介绍了,不懂的可以去看看。
几个关键词:
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
切面(ASPECT):切入点那里需要插入的被模块化的特殊对象。说白了,就是需要往横切关注点横向插进去的那个类
通知(Advice):切面必须要完成的工作。说白了,它是类中的一个具体方法。
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
切入点(PointCut):代码中需要被横向插入的地方。
连接点(JointPoint):与切入点匹配的执行点。就是可以获取切入点的一些信息。
需要在Maven中添加依赖:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
Spring中三种实现AOP的方式:
第一种:
想在切入点前面插入功能,就得写一个类去实现MethodBeforeAdvice接口,然后重写before方法。
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("切入点前面的功能");
}
}
想在切入点后面插入功能,就得写一个类去实现AfterReturningAdvice接口,然后重写afterReturning方法,其中returnValue可以获取方法执行后的返回值。
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("在后面执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
然后,去xml文件中配置bean依赖,这里用到的是Spring中的xml注入bean,就是在xml中配置对象,不懂的可以看我前面介绍IOC的博客。注意,需要添加几个AOP依赖。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.log.BeforeLog"/>
<bean id="afterLog" class="com.log.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="beforelog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
第二种:
不再需要实现插入前和插入后的接口,直接可以自己定义一个类:
public class MyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
在xml中配置,相当于把自己写的类加载进Spring中后,直接通过aop中before和after标签来对应需要插入的具体方法:
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="mypointcut" class="com.MyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="mypointcut">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
第三种:
使用注解实现
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
在类中加入注解后,只需要将类注入Spring容器就行了,当然,注入容器其实也可以通过注解注入,不过,我这里用xml注入,因为使用注解总是要在xml中增加支持注解的配置,这点别忘了:aop:aspectj-autoproxy/
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
最后补充一个小知识点:
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。