在以往的企业开发过程中,一些已经写完的功能可以会在原本的基础上进行扩展,这个时候就需要去修改原有的代码,将新扩展的内容完善进去。但是这个动作其实是企业级开发的大忌,因为原本好用的代码很可能因为新增的内容导致出现问题。
而解决这个问题会用到代理模式,将要新扩展的功能维护到代理角色中,再用代理角色去调用真实角色来实现功能的扩展,这样做不但将新扩展的功能实现类,原本的功能代码也无需变动,更加稳妥。而这也是 Spring AOP 的实现机制。
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
日志记录: 记录调用方法的入参和结果返参。
用户的权限验证: 验证用户的权限放到AOP中,与主业务进行解耦。
性能监控: 监控程序运行方法的耗时,找出项目的瓶颈。
事务管理: 控制Spring事务,Mysql事务等。
AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。
AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。
主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。
简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。
提供声明式事务;允许用户自定义切面
横切关注点: 跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
切面(ASPECT): 横切关注点被模块化的特殊对象。即,它是一个类。
通知(Advice): 切面必须要完成的工作。拦截到连接点之后,对切入点增强的内容。即,它是类中的一个方法。
目标(Target): 被代理的目标对象。
代理(Proxy): 指生成的代理对象
切入点(PointCut): 指需要对那些 JointPoint 进行拦截,即被拦截的连接点
连接点(JointPoint): 指那些被拦截到的点,在 Spring 中,指可以被动态代理拦截目标类的方法
植入点(Weaving): 指把增强代码应用到目标上,生成代理对象的过程
通俗理解
横切关注点:在业务代码种需要新增的业务,即为关注点
切面:将关注点维护成一个类,即新增的业务就是切面
通知:切面中具体的方法,也就是具体需要完成的业务就是通知。
目标:即需要在那段业务新增业务代码,即目标对象。
代理:代理目标对象的对象
切入点:需要通知在那个位置执行新的业务代码
注:目标和代理被 Spring 完成了,InvocationHandler、Proxy
通知类型 | 连接点 |
---|---|
Before(前置通知) | 通知方法在目标方法调用之前执行,前置通知不会影响目标方法的执行,除非此处抛出异常 |
After(后置通知) | 通知方法在目标方法返回或者异常后调用(必然会执行) |
After-Returning(最终通知) | 通知方法在目标方法返回后调用,如果连接点抛出异常,则不会执行 |
After-Throwing(抛出异常后通知) | 通知方法在目标方法抛出异常后调用 |
Around(环绕通知) | 环绕目标方法的通知,例如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它可以选择是否继续执行目标方法或直接返回自定义的返回值又或抛出异常将执行结束。 |
注:目标方法就是连接点
创建 Maven 项目并在 pom.xml 中引入如下依赖(使用 AOP 织入需要此依赖)
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
在service包下,定义UserService业务接口和UserServiceImpl实现类
UserService 接口
public interface UserService {
void add();
void query();
void delete();
void update();
}
UserServiceImpl 实现类
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增一个用户");
}
@Override
public void query() {
System.out.println("查询用户信息");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("修改用户信息");
}
}
在 log 包下定义 一个前置增强和一个后置增强类
前置通知增强类
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
//MethodBeforeAdvice : 前置通知
public class BeforeLog implements MethodBeforeAdvice {
/*
* method:要执行的目标对象的方法
* args:参数
* target:目标对象
* */
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
后置通知增强类
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
// AfterReturningAdvice : 后置通知
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("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
最后去 spring 的文件中注册 , 并实现aop切入实现 , 注意导入约束,配置 applicationContext.xml 文件
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="afterLog" class="com.sys.log.AfterLog"/>
<bean id="beforeLog" class="com.sys.log.BeforeLog"/>
<bean id="userService" class="com.sys.service.impl.UserServiceImpl"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.sys.service.impl.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
aop:config>
beans>
创建 MyTest 测试类
public class MyText {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 需要注意的是:AOP的代理是动态代理,也就是此处代理的并不是实现类,而是实现类实现的接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
userService.update();
userService.query();
userService.delete();
}
}
不使用 Spring 提供的 API 接口,使用自定义的 diy 类,搭配 xml 的 aop 配置实现
创建 diy 工具类
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前======");
}
public void after(){
System.out.println("======方法执行后======");
}
}
创建 Beans.xml
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="diyPointCut" class="com.sys.diy.DiyPointCut"/>
<bean id="userService" class="com.sys.service.impl.UserServiceImpl"/>
<aop:config>
<aop:aspect ref="diyPointCut">
<aop:pointcut id="pointcut" expression="execution(* com.sys.service.impl.UserServiceImpl.*(..))"/>
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
aop:aspect>
aop:config>
beans>
修改 MyTest,其他代码内容不变
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
// 需要注意的是:AOP的代理是动态代理,也就是此处代理的并不是实现类,而是实现类实现的接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
System.out.println("-------------------------------------------------");
userService.update();
System.out.println("-------------------------------------------------");
userService.query();
System.out.println("-------------------------------------------------");
userService.delete();
}
}
@Aspect : 被该注解标注的类就是一个切面
@Before: 标注切面类中的方法为前置通知
@After: 标注切面类中的方法为后置通知
@Around: 标注切面类中的方法为环绕通知
参数:需要传入切入点,即 execution 表达式
被标记为环绕通知的方法还需要搭配 proceed() 来完成通知,如果没有该方法,那么切入点对应的方法不执行,也可以理解为这个方法就是执行切入点中的方法用的
代码示例:(前置通知和后置通知)
新增 Diy 切面类
@Aspect // 将该类标记为一个切面
public class AnnotationPointCut {
// 将该方法标记为前置通知
@Before("execution(* com.sys.service.impl.UserServiceImpl.*(..))")
public void before(){
System.out.println("=====方法执行前=====");
}
// 将该方法标记为后置通知
@After("execution(* com.sys.service.impl.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====方法执行后=====");
}
}
修改 Beans.xml 开启注解支持
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.sys.service.impl.UserServiceImpl"/>
<bean id="annotationPointCut" class="com.sys.diy.AnnotationPointCut"/>
<aop:aspectj-autoproxy/>
beans>
其他代码不变,运行测试类,执行结果
环绕通知代码示例
修改 Diy 切面类
@Aspect // 将该类标记为一个切面
public class AnnotationPointCut {
@Around("execution(* com.sys.service.impl.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable{
System.out.println("环绕前");
Signature signature = jp.getSignature();// 获得签名
System.out.println("signature:"+signature);
jp.proceed(); //执行方法
System.out.println("环绕后");
}
}
其他代码不变,运行测试类,执行结果