若要使用Spring,谁人不知AOP
Spring中我们之前学习了IOC和DI,其实我们发现在不了解的情况下,我们的真的会很难理解,AOP也一样,博主第一次学也是云里雾里,AOP的概念很多都抽象难理解,但希望通过这篇博客可以让不知道的也可以先简单通俗的理解.
AOP面向切面编程(Aspect Oriented Programming)
一.定义:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二.AOP涉及到的相关概念:
1. 连接点(join point)
- 定义:
程序运行时的一些时间点,可以是一个方法在执行,或者是一个异常的处理,这些都是为连接点
在Spring AOP中连接点总是方法,因此方法组成了连接点
2. 切点(Point cut)
- 定义:
Spring AOP提供了一组规则(切点表达式),使用这种规则去匹配连接点中的方法,得到的组成了切点,这里就可以看出为什么叫切了,因为我们的这个匹配规则可以看成刀之类的去连接点这块蛋糕中切一块我们需要的.
3. 增强(Advice)
- 定义:
这个其实就是我们定义的代码,具有一些功能,我们使用AOP就是想要把这些增加功能的代码添加到很多我们需要的方法上面去,这里指的是核心代码
4. 织入(Weaving)
- 定义:
这代表了一个动作,将我们定义的增强代码加入到核心代码的过程
5. 目标对象(Target)
- 定义:
当我们完成织入后产生的代理对象就是目标对象,那个才是最终实现的需求
6. 切面(Aspect)
- 定义:
这只是一个设计概念,它是由增强(Advice)和切点组成
Advice定义了切面的任务和什么时候执行,切点定义了在哪里切入
最终切面(Aspect)定义了一个什么样的增强方法功能切入到哪个核心方法的哪个地方去
7. 切点表达式
- 定义:
用来匹配切点的规则
- 使用规则
8. SpringAOP中有五种通知:
通知类型 | 具体概念 |
---|---|
before | 前置通知,在核心业务代码执行前执行,可以配置开启事务 |
afterReturning | 后置通知,核心代码执行后执行,出现异常不执行,可配置提交事务 |
afterThrowing | 异常处理通知,核心代码出现异常后执行,可配置回滚事务 |
after | 最终通知,无论是否发生异常,都执行,可用来关闭事务 |
around | 环绕通知,可以实现上述通知功能 |
三.使用AOP来实现一个具体的案例:
需求:用户登录前后打印输出相关的信息,比如用户准备登录,用户已经登录
- 第一步:创建项目,添加依赖:
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<!-- AOP相关JAR -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
- 第二步:创建项目结构:
- 第三步:书写登录核心业务代码(这里简单写一个登录方法)
package com.offcn.service;
import org.springframework.stereotype.Service;
@Service
public class CustomerServiceImpl {
public void login(){
System.out.println("用户登录成功...login方法");
}
}
- 第四步:写增加更能方法:
package com.offcn.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
public class CustomerAdvice {
public void before(){
System.out.println("用户准备登录...before...前置通知代码");
}
public void after(){
System.out.println("用户已经登录...after...最终通知执行");
}
public void afterreturning(){
System.out.println("afterreturing...后置通知");
}
public void afterthrowing(){
System.out.println("afterthrowing...异常处理通知");
}
public void around(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("用户准备登录...before...前置通知代码...a");
proceedingJoinPoint.proceed();
System.out.println("afterreturing...后置通知..a");
} catch (Throwable throwable) {
System.out.println("afterthrowing...异常处理通知.aa");
} finally {
System.out.println("用户已经登录...after...最终通知执行..aa");
}
}
}
- 第五步:使用Spring容器来切入:
<?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 id="customerService" class="com.offcn.service.CustomerServiceImpl"></bean>
<!--通知对象(增强方法)-->
<bean id="advice" class="com.offcn.advice.CustomerAdvice"></bean>
<!--切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="pc" expression="execution(* com.offcn..service.*Impl.*(..))"></aop:pointcut>
<!--切面-->
<aop:aspect ref="advice">
<aop:before method="before" pointcut-ref="pc"></aop:before>
<aop:after-returning method="afterreturning" pointcut-ref="pc"></aop:after-returning>
<aop:after-throwing method="afterthrowing" pointcut-ref="pc"></aop:after-throwing>
<aop:after method="after" pointcut-ref="pc"></aop:after>
</aop:aspect>
</aop:config>
</beans>
- 第六步:书写测试类:
@Test
public void m1(){
ApplicationContext context=new ClassPathXmlApplicationContext("beans.xml");
CustomerServiceImpl bean = context.getBean(CustomerServiceImpl.class);
bean.login();
}
- 第七步:测试结果:
三.使用注解来开发,改写上面案例:
- 新建新的容器文件,配置如下:
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.offcn"></context:component-scan>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
- 去增加代码中添加注解即可:
package com.offcn.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class CustomerAdvice {
@Pointcut("execution(* com.offcn..service.*Impl.*(..))")
public void pc(){}
@Before("pc()")
public void before(){
System.out.println("用户准备登录...before...前置通知代码");
}
@After("pc()")
public void after(){
System.out.println("用户已经登录...after...最终通知执行");
}
@AfterReturning("pc()")
public void afterreturning(){
System.out.println("afterreturing...后置通知");
}
@AfterThrowing("pc()")
public void afterthrowing(){
System.out.println("afterthrowing...异常处理通知");
}
/* @Around("pc()")
public void around(ProceedingJoinPoint proceedingJoinPoint){
try {
System.out.println("用户准备登录...before...前置通知代码...a");
proceedingJoinPoint.proceed();
System.out.println("afterreturing...后置通知..a");
} catch (Throwable throwable) {
System.out.println("afterthrowing...异常处理通知.aa");
} finally {
System.out.println("用户已经登录...after...最终通知执行..aa");
}
}*/
}