文章目录
前言
本篇记录spring的AOP面向切面编程
一、AOP是什么?
AOP是spring框架的特点之一。AOP全称为:Aspect Oriented Programming,它是面向对象编程>OOP的延续。通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
即AOP是可以通过预编译方式和运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术
在程序开发中AOP主要应用于以下场景:
日志记录,性能统计,安全控制,事务处理,异常处理等等。
AOP把这些代码从正常的业务逻辑中抽调出来,进行统一的管理。改变这些代码,不会影响到核心功能的运行。
主要使用的专业术语
-
目标类(Target): 被代理的类
-
连接点(JoinPoint): 目标类中准备被切入的方法
-
切入点(Pointcut): 真正执行的目标方法
-
切面(Aspect) : 切面中定义中增强的方法
-
增强(Advice): 也叫通知,就是在目标方法执行前/后的方法
-
织入(Weaving): 将增强作用到切入点的过程
面向切面编程的作用:就是将项目中与核心逻辑无关的代码横向抽取成切面类,通过织入作用到目标方法,以使目标方法执行前后达到增强的效果。
原理: AOP底层使用的就是动态代理,给AOP指定哪些类型需要增强,就会产生对应的代理对象,代理对象执行方法前后会先执行增强的方法.
二、AOP的实现
1.环绕通知的实现
- 依赖spring-aop.jar
- 创建所需UserService和UserServiceImpl
- 创建切面类
- 配置文件配置切面
- 测试
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
创建接口和实现类
public interface UserService {
void addUser();
void deleteUserById();
}
public class UserServiceImpl implements UserService{
@Override
public void addUser() {
System.out.println("UserServiceImpl.addUser 执行" );
}
@Override
public void deleteUserById() {
System.out.println("UserServiceImpl.deleteUserById 执行" );
}
}
创建切面类
import org.aspectj.lang.ProceedingJoinPoint;
/**
* @author lzl
* @desc 切面类
* 切面类中定义各种增强的方法
*/
public class MyAspect {
/**
* 增强的方法: 环绕通知
* 参数ProceedingJoinPoint: 目标方法
* @return
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 目标方法前
System.out.println("前置增强: 开启事务" );
// 目标方法执行
Object ret = joinPoint.proceed( );
// 目标方法后
System.out.println("后置增强: 提交事务" );
return ret;
}
}
在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">
<beans>
<!-- 目标类 -->
<bean id="userService" class="com.qf.service.UserServiceImpl"/>
<!-- 目标类 -->
<bean id="houseService" class="com.qf.service.HouseServiceImpl"/>
<!-- 创建切面类对象 -->
<bean id="myAspect" class="com.qf.aspect.MyAspect"/>
<!-- 织入 -->
<aop:config>
<!-- 配置切面,引用自定义切面对象 -->
<aop:aspect ref="myAspect">
<!-- 下面要做的是将通知作用到目标方法上 -->
<!-- 配置环绕通知 -->
<!--
method: 切面类中的方法名
pointcut: 切入点,内写切入点表达式
execution(* com.qf.service.*.*(..))
第一个*, 返回值的意思,*的意思是返回值任意
com.qf.service.* , 通过路径确定切入点所在类,*的意思是service包下所有类
.* ,是方法名,*是指所有方法
() 方法的参数列表
.. 方法内的任意参数
-->
<aop:around method="around" pointcut="execution(* com.lzl.service.*.*(..))"/>
</aop:aspect>
</aop:config>
</beans>
编写测试类
public class TestAOP {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);
// 目标方法
userService.addUser();
userService.deleteUserById();
}
}
运行略
2.其它通知
- 前置增强(通知)
- 场景:一般用来做权限校验
- 后置增强(通知)
- 场景: 释放资源,或者记录日志
- 环绕增强(通知)
- 场景:数据库事务
- 后置返回增强(通知)
- 场景:得到目标方法返回值再处理
- 异常增强(通知)
- 场景:可以获得目标方法的异常信息,用于记录日志,或者进行异常拦截
创建切面类
public class MyAspect {
/**
* 前置增强
* 参数: JoinPoint 目标类对象
*/
public void before(JoinPoint joinPoint){
Object target = joinPoint.getTarget( );
System.out.println("前置增强,获得目标类对象"+target );
Signature signature = joinPoint.getSignature( );
System.out.println("前置增强,获得目标方法"+signature);
String name = signature.getName( );
System.out.println("前置增强,获得目标方法名"+name);
System.out.println("前置增强: 权限校验" );
}
/**
* 后置通知
*/
public void after(){
System.out.println("后置通知:记录执行的日志" );
// 应用场景: 还可以做一些关流,释放资源的动作
}
/**
* 增强的方法: 环绕通知
* 参数ProceedingJoinPoint: 目标方法
* @return
*/
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 目标方法前
System.out.println("环绕-前置增强: 开启事务" );
// 目标方法执行
Object ret = joinPoint.proceed( );
System.out.println("环绕-获得目标方法返回值: "+ ret );
// 目标方法后
System.out.println("环绕-后置增强: 提交事务" );
return ret;
}
/**
* 后置返回通知
* 后置返回能得到目标方法的返回值
*/
public Object afterReturn(Object ret){
System.out.println("后置返回通知: "+ ret );
return ret;
}
/**
* 异常通知
*/
public void myThrow(Exception e){
// 可以接收到目标方法执行的异常信息
System.out.println("异常通知,获得异常信息"+e.getMessage() );
// 可以做全局异常处理,记录异常信息到日志
}
}
配置切面
<?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="userService" class="com.lzl.service.UserServiceImpl"/>
<!-- 目标类 -->
<bean id="houseService" class="com.lzl.service.HouseServiceImpl"/>
<!-- 创建切面类对象 -->
<bean id="myAspect" class="com.lzl.aspect.MyAspect"/>
<!-- 织入 -->
<aop:config>
<!-- 配置切面,引用自定义切面对象 -->
<aop:aspect ref="myAspect">
<!-- 下面要做的是将通知作用到目标方法上 -->
<!-- 配置环绕通知 -->
<!--
method: 切面类中的方法名
pointcut: 切入点,内写切入点表达式
execution(* com.lzl.service.*.*(..))
第一个*, 返回值的意思,*的意思是返回值任意
com.qf.service.* , 通过路径确定切入点所在类,*的意思是service包下所有类
.* ,是方法名,*是指所有方法
() 方法的参数列表
.. 方法内的任意参数
-->
<aop:around method="around" pointcut-ref="pointcut"/>
<!-- 将切入点表达式抽取,以便复用 -->
<aop:pointcut id="pointcut" expression="execution(* com.lzl.service.*.*(..))"/>
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="pointcut"/>
<!--后置返回
returning指定增强方法的参数名ret
-->
<aop:after-returning method="afterReturn" pointcut-ref="pointcut" returning="ret"/>
<!-- 异常通知
throwing指定增强方法的参数名e
-->
<aop:after-throwing method="myThrow" pointcut-ref="pointcut" throwing="e"/>
</aop:aspect>
</aop:config>
</beans>
三、springAOP的动态代理
springAOP的动态代理底层使用的是两种技术来实现
默认情况下使用JDK动态代理.即目标类必须有接口,JDK代理接口.当目标类没有实现接口时自动切换使用CGLIB实现动态代理。
四、用注解实现AOP的开发
- 创建maven项目
- 导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
</dependencies>
- 编写配置文件
使用的注解:
- bean id=“” class=“目标类路径” --> @Service
- bean id=“切面对象” class=“切面类路径” --> @Component
- aop:confing
- aop:aspect ref=“切面对象” --> @Aspect
- aop:before -->@Before
- aop:after -->@After
- aop:pointcut -->@Pointcut
创建接口和实现类
public interface UserService {
void findUserById();
}
@Service // 让spring创建对象,将该类交给Spring托管
public class UserServiceImpl implements UserService {
@Override
public void findUserById() {
System.out.println("com.lzl.service.UserServiceImpl.findUserById()");
}
}
切面类
@Component // 让spring创建其对象,将该类交给Spring托管
@Aspect // 声明该类是一个切面类
public class MyAspect {
/**
* 将切入点表达式抽取,以便复用
*/
@Pointcut("execution(* com.lzl.service.*.*(..))")
public void pc(){}
/**
* 前置通知注解,其中写的是切入点表达式的方法名,注意不要忘了括号()
*/
@Before("pc()")
public void myBefore(){
System.out.println("触发前置通知" );
}
@After("pc()")
public void myAfter(JoinPoint joinPoint){
System.out.println("触发后置通知"+joinPoint.getSignature().getName() );
}
@Around("pc()")
public void myAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("这是环绕通知");
joinPoint.proceed();
System.out.println("再次触发环绕通知");
}
}
配置xml文件使注解生效
<?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.lzl"/>
<!-- 开启AOP的自动代理配置 -->
<aop:aspectj-autoproxy/>
</beans>
编写测试类
public static void main(String[] args) {
// 获得容器对象
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器获得对象,对象名一定是类名首字母小写
UserService userService = context.getBean("userServiceImpl", UserService.class);
// 执行目标方法,目标方法前后都会执行增强方法
userService.findUserById();
}
注解实现AOP完毕
总结
AOP的实现是为了降低代码耦合,提高代码复用率,和增强方法。底层使用的是动态代理技术,同时使用了JDK动态代理和CJLIB代理。在实际开发中使用注解进行AOP开发。