一、OOP与AOP
1、OOP(Object Oriented Programming)即面向对象编程
面向对象编程(OOP)方法是在面向过程的编程方法基础上进行的改进,OOP引入封装、继承、多态等概念来建立一种对象层次结构。
OOP针对问题领域中以及业务处理过程中存在的实体及其属性和操作进行抽象和封装,面向对象的核心概念是纵向结构的,其目的是获得更加清晰高效的逻辑单元划分
2、AOP(Aspect Oriented Programming),即面向切面编程
面向方面编程(AOP)方法是在面向对象编程(OOP)方法的基础上进行改进而来的一种创新的软件开发方法。AOP是OOP的延续和补充。
AOP则是针对业务处理过程中的切面进行提取,例如:日志记录、权限控制、性能监测、异常处理、事务控制等等等,在许多情况下,这些操作都是与业务逻辑相关性不强或者不属于逻辑操作的必须部分,而面向对象的方法很难对这种情况做出处理。
AOP则将这些操作与业务逻辑分离,使程序员在编写程序时可以专注于业务逻辑的处理,而利用 AOP将贯穿于各个模块间的横切关注点自动耦合进来。
AOP的核心思想就是将应用程序中的业务逻辑处理部分同对其提供支持的通用服务,即所谓的“横切关注点”进行分离,这些“横切关注点”贯穿了程序中的多个纵向模块的需求。
3、AOP的术语
1) Joinpoint 连接点
连接点是在应用执行过程中需要插入“横切关注点”的一个点(需要被增强的方法),可理解为 where去哪里做增强。
2) Pointcut 切入点
切入点可理解为连接点的集合,一个或多个连接点,where 去哪些地方做增强。
3)Advice 通知或者增强
当拦截到 Joinpoint 连接点时,通知在什么时机(when)做什么样(what)的增强,
根据时机可分为:前置增强,后置增强,异常增强,最终增强和环绕增强。
4)Aspect 切面
切面是Advice和Pointcut的结合,通知在哪些地方(where)什么时机(when)做什么样(what)的增强
5)Target 目标对象,即被代理的目标对象
6)Weaving 织入
织入可把它看做是一个动作,就是将哪些需要被增强的切入点织入目标对象中,创建出 Proxy对象的过程
7)Proxy 代理类:一个类被AOP织入增强后产生的代理类
4、Spring对AOP的支持
Spring AOP基于代理(JDK动态代理、CGLib动态代理)实现的AOP
Spring中AOP由Spring的IOC容器负责生成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。
Spring创建代理的规则为:
1)默认使用 JDK动态代理(只支持接口代理)来创建AOP代理,这样就可以为任何接口实例创建代理了
2)当需要代理的类不是代理接口的时候,Spring会切换为使用 CGLib代理(支持类的代理),也可强制使用CGLib
5、AOP编程其实是很简单的事情(其思想精髓需要好好理解),程序员只需要参与两个部分:
1)定义普通业务组件
2)定义增强处理的切面类,增强处理就是在AOP框架为普通业务组件织入的处理动作
2.1)定义切入点,一个切入点可能横切多个业务组件
所以进行AOP编程的关键就是定义切入点和定义增强处理的切面类,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。
AOP技术有很多,像AspectJ和JBoss等,下面是基于AspectJ的AOP简单实现。
二、使用xml配置AOP
引入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.0.8.RELEASE</version>
</dependency>
正常创建model、dao和service,方法没有做具体的业务实现
@Component
public class User {
}
@Repository
public class UserDao implements IUserDao {
@Override
public void save(User user) {
System.out.println("保存用户");
}
@Override
public void update(User user) {
System.out.println("修改用户");
}
@Override
public List<User> listAll() {
System.out.println("查询所有");
return new ArrayList<>();
}
}
@Service("UserService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void save(User user) {
userDao.save(user);
System.out.println("保存成功");
}
@Override
public void update(User user) {
userDao.update(user);
throw new RuntimeException("修改失败");
}
@Override
public List<User> listAll() {
System.out.println("查询成功");
return userDao.listAll();
}
}
创建对业务增强处理的切面类:事务控制和日志管理
import org.aspectj.lang.ProceedingJoinPoint;
//模拟事务管理器
public class MyTransctionManager {
public Object aroundMethod(ProceedingJoinPoint pjp) {
System.out.println("代理对象:" + pjp.getThis().getClass());
Object ret = null;
System.out.println("开启事务");
try {
ret = pjp.proceed();//调用真实对象的方法
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务,异常信息=" + e.getMessage());
} finally {
System.out.println("释放资源");
}
return ret;
}
}
import org.aspectj.lang.JoinPoint;
//模拟日志管理器
public class MyLoggerManager {
public void beforeMethod(org.aspectj.lang.JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName(); //获取方法名
List<Object> args = Arrays.asList(joinPoint.getArgs()); //获取参数数组
System.out.println(methodName+"方法执行之前,参数:" + args);
}
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName+"方法执行之后,参数:" + args);
}
public void afterReturningMethod(JoinPoint joinPoint, Object rs) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName+"方法(无异常)执行之后,参数:" + args + ",结果:"+ rs);
}
public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName+"方法执行时发生异常啦,参数:" + args + ",异常信息:" + ex.getMessage());
}
}
1、JDK动态代理
在 applicationContext.xml 核心配置文件中 xml配置 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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- ID注释解析器 -->
<context:annotation-config />
<!-- IOC注解解析器 -->
<context:component-scan base-package="cn.jq.springdemo"/>
<!-- AOP 配置: 在什么地点 + 什么时机 + 做什么增强 -->
<!-- 1、配置bean, WHAT,做什么增强,也可使用注解 -->
<bean id="txManager" class="cn.jq.springdemo.aop.MyTransctionManager" />
<bean id="loggerManlager" class="cn.jq.springdemo.aop.MyLoggerManager" />
<!-- 使用xml配置AOP -->
<aop:config proxy-target-class="false">
<!-- 2、配置切入点表达式: WHERE 在哪些包中的哪些类中的哪些方法上做增强 -->
<aop:pointcut expression="execution(* cn.jq.springdemo.service.*Service.*(..))" id="txManagerPointCut"/> <!-- 所有方法通知 -->
<aop:pointcut expression="execution(* cn.jq.springdemo.service.*Service.save(..))" id="loggerManlagerPointCut"/> <!-- save通知 -->
<!-- 3、配置切面和通知:WHEN 在方法执行的什么时机做增强 -->
<aop:aspect ref="txManager" order="1"><!-- 关联WHAT -->
<!-- 环绕通知 -->
<aop:around method="aroundMethod" pointcut-ref="txManagerPointCut"/>
</aop:aspect>
<aop:aspect ref="loggerManlager" order="2">
<!-- 前置通知 -->
<aop:before method="beforeMethod" pointcut-ref="loggerManlagerPointCut"/>
<!-- 后置通知 -->
<aop:after method="afterMethod" pointcut-ref="loggerManlagerPointCut"/>
<!-- 返回通知-->
<aop:after-returning method="afterReturningMethod" pointcut-ref="loggerManlagerPointCut" returning="rs"/>
<!-- 异常通知-->
<aop:after-throwing method="afterThrowingMethod" pointcut-ref="loggerManlagerPointCut" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
测试:
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("UserService",UserService.class);
userService.save(new User());
System.out.println("=========");
userService.update(new User());
}
}
2、CGLib动态代理
强制使用CGLib动态代理,设置 proxy-target-class 属性值为 true, 对没有接口的类代理,同时修改要切入点的表达式。
三、使用注解配置AOP
注释在类上
@Aspect 声明该类为 切面类
@Order(2) 当同一个目标方法有多个切面的时,哪个切面类先执行,取决于在切面类上的注解@order(值小的先执行)
注解的这个方法在AOP里叫:通知(advice)
@Before 前置通知(在目标方法执行之前执行)
@After 后置通知(在目标方法执行之后执行)无论有没有异常抛出都会执行
@AfterReturning 返回通知(在目标方法返回结果之后执行)
@AfterThrowing 异常通知(在目标方法跑出异常之后执行)
@Around 环绕通知(围绕着方法执行)
创建对业务增强处理的切面类:事务控制和日志管理
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
//模拟事务管理器
@Component
@Aspect
@Order(1)
public class MyTransctionManager {
//定义切入点表达式
@Pointcut("execution(* cn.jq.springdemo.service.*Service.*(..))")
public void txManagerPointCut() {}
@Around("txManagerPointCut()")
public Object aroundMethod(ProceedingJoinPoint pjp) {
System.out.println("代理对象:" + pjp.getThis().getClass());
Object ret = null;
System.out.println("开启事务");
try {
ret = pjp.proceed();//调用真实对象的方法
System.out.println("提交事务");
} catch (Throwable e) {
System.out.println("回滚事务,异常信息=" + e.getMessage());
} finally {
System.out.println("释放资源");
}
return ret;
}
}
//模拟日志管理器
@Component
@Aspect
@Order(2)
public class MyLoggerManager {
//定义切入点表达式
@Pointcut("execution(* cn.jq.springdemo.service.*Service.save(..))")
public void loggerManlagerPointCut() {}
@Before("loggerManlagerPointCut()")
public void beforeMethod(org.aspectj.lang.JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName(); //获取方法名
List<Object> args = Arrays.asList(joinPoint.getArgs()); //获取参数数组
System.out.println(methodName+"方法执行之前,参数:" + args);
}
@After("loggerManlagerPointCut()")
public void afterMethod(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName+"方法执行之后,参数:" + args);
}
@AfterReturning(value = "loggerManlagerPointCut()", returning = "rs")
public void afterReturningMethod(JoinPoint joinPoint, Object rs) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName+"方法(无异常)执行之后,参数:" + args + ",结果:"+ rs);
}
@AfterThrowing(value = "loggerManlagerPointCut()", throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(methodName+"方法执行时发生异常啦,参数:" + args + ",异常信息:" + ex.getMessage());
}
}
1、JDK动态代理
在 applicationContext.xml 核心配置文件中 注解配置 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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- ID注释解析器 -->
<context:annotation-config />
<!-- IOC注解解析器 -->
<context:component-scan base-package="cn.jq.springdemo"/>
<!-- AOP注解解析器 , 让切面类里的方法上的注释起作用 -->
<aop:aspectj-autoproxy proxy-target-class="false"></aop:aspectj-autoproxy>
</beans>
2、CGLib动态代理
强制使用CGLib动态代理,设置 proxy-target-class 属性值为 true, 对没有接口的类代理,同时修改要切入点的表达式。
测试及结果同上
注意:
1、Spring AOP提供了org.aspectj.lang.JoinPoint 类作为增强方法的第一个参数。
2、JoinPoint 类:提供了访问当前被增强方法的真实对象、代理对象、方法参数等数据。
3、ProceedingJoinPoint 类:是JoinPoint 的子类,只能用于环绕通知。
环绕通知:就是把前面4中通知全给整合在一起。使用它必须要求:
1)必须要带参数 ProceedingJoinPoint 类型的参数,这个参数可以直接调用原来的目标方法。
2)环绕通知方法必须有返回值,这个反正值就是目标方法的返回值。
参考文章:AspectJ切入点语法详解
站在前辈的肩膀上,每天进步一点点
ends~