AOP 是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善 实际项目中我们通常将面向对象理解为一个静态过程(例如一个系统有多少模块,一个模块有哪些对象,对象有哪些属性),面向切面中包含一个一个动态过程(在对象运行时动态织入一些功能。)
实际项目中通常会将系统两大部分:核心关注点和非核心关注点 思考? 编程过程中首先要完成的是什么?核心关注点(核心业务) 非核心关注点如何切入到系统中?硬编码(违背OCP),AOP(推荐)
AOP就是要在基于OCP(开闭原则)在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以控制对象的执行(例如可以让这个业务继续执行,也可以让这个业务停止执行.)。
AOP 通常应用于日志的处理,事务处理,权限处理,缓存处理等等。
AOP底层基于代理机制实现功能扩展:(了解) 1)假如目标对象(被代理对象)实现接口,则底层默认采用JDK动态代理机制为目标对象创建代理对象(目标类和代理类会实现共同接口) 2)假如目标对象(被代理对象)没有实现接口,则底层默认采用CGLIB代理机制为目标对象创建代理对象(默认创建的代理类会继承目标对象类型)。
基于jdk动态代理的AOP原理
切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect声明) 连接点(joinpoint):程序执行过程中某个特定的点,一般指被拦截到的的方法 切入点(pointcut):对连接点拦截内容的一种定义,一般可以理解为多个连接点的结合. 通知(Advice):在切面的某个特定连接点上执行的动作(扩展功能),例如around,before,after等
step1:创建maven java 项目 step2:添加aop依赖(两个) step3:配置aop 核心(基于xml,基于注解:重点) step4:定义核心业务(核心关注点):推荐先写接口再写实现类 step5:定义扩展业务(非核心关注点):本质上就是切面 step6:基于配置实现非核心业务的切入(切入点,连接点,通知) step7:编写测试类进行单元测试
创建MAVEN JAVA 项目然后在POM文件中添加依赖
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.9.RELEASE</version> </dependency> <!-- Spring AOP的实现依托于Aspect框架 所以要引用1.8.5有问题 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency>定义一个订单接口,此接口中定义多个业务操作。
public interface OrderService { public void saveOrder(); public void deleteOrder(); }创建接口核心业务实现类,并使用@Service注解进行修饰
将此时间处理类作为核心业务增强(一个横切面对象)类,用于输出业务开始执行时间,以及业务结束执行时间。 横切面对象主要由两部分构成:切入点(用于@Pointcut标识),以及功能增强(用通知@Before,@After等进行标识)
@Aspect @Service public class TimeAspect { @Pointcut("bean(orderService)") public void pointcut(){} /**增强功能:前置通知(业务方法执行之前执行)*/ @Before("pointcut()") public void begin(){ System.out.println("start:"+System.nanoTime()); } /**增强功能:最终通知(业务方法执行最后执行, *无论业务方法是否执行成功,此功能都要执行)*/ @After("pointcut()") public void end(){ System.out.println("end:"+System.nanoTime()); } }其中: @Aspect 注解用于标识此类为一个AOP横切面对象 @Pointcut 注解用于定义本类中的切入点,本案例中切入点表达式用的是bean表达式,这个表达式以bean开头,bean括号中的内容为一个spring管理的某个bean对象的id。 @Before 用于定义一个前置通知(满足切入点表达式的核心业务方法执行之前要执行的一个操作) @After 用于定义一个后置通知(满足切入点表达式的核心业务方法执行之后要执行的一个操作)
术语增强: 切面:用于封装扩展业务的一个类的对象。 通知:切面扩展业务中的一个操作(方法)。
对于基于注解方式的配置一般有两种方式,一种是直接在xml核心配置文件中进行配置,还有一种在类中基于注解进行配置。例如 基于xml方式配置对注解的应用
<?xml version="1.0" encoding="UTF-8"?> <beans default-lazy-init="true" xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:util="http://www.springframework.org/schema/util" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 配置对类组件的扫描 --> <context:component-scan base-package="com.spring"/> <!-- 启用AOP注解(自动为目标对象创建代理对象) --> <aop:aspectj-autoproxy/> </beans>在类中基于注解方式的配置
@ComponentScan("com.spring.beans") @EnableAspectJAutoProxy public class AppConfig { }基于xml方式注解配置的测试实现
public class TestAOP01 { public static void main(String[] args) { //1.初始化容器 ClassPathXmlApplicationContext ctx= new ClassPathXmlApplicationContext( "applicationContext.xml"); //2.获取bean对象 OrderService os=(OrderService) ctx.getBean("orderService", OrderService.class); //3.执行业务方法 os.saveOrder(); os.deleteOrder(); //4.释放资源 ctx.close(); } }基于类中注解方式配置的测试实现
public class TestAOP02 { public static void main(String[] args) { //1.初始化容器对象 AnnotationConfigApplicationContext ctx= new AnnotationConfigApplicationContext( AppConfig.class); //2.获取Bean对象 OrderService orderService= ctx.getBean("orderService", OrderService.class); //3.执行业务 orderService.saveOrder(); //orderService.deleteOrder(); //4.释放资源 ctx.close(); } }基于类的注解配置,初始化工厂时需要初始化 AnnotationConfigApplicationContext对象。
Spring中通过切入点表达式定义具体切入点,其常用AOP切入点表达式定义及说明: 指示符 作用
bean 用于匹配指定bean id的的方法执行within 用于匹配指定包名下类型内的方法执行@annotation 用于匹配指定注解修饰的方法执行(细粒度)execution 用于进行细粒度方法匹配执行具体业务bean应用于类级别,实现粗粒度的控制: bean(“userServiceImpl”)) 指定一个类 bean("*ServiceImpl") 指定所有的后缀为service的类
within应用于类级别,实现粗粒度的切面表达式定义: within(“aop.service.UserServiceImpl”) 指定类,只能指定一个类 within(“aop.service.*”) 只包括当前目录下的类 within(“aop.service…**”) 指定当前目录包含所有子目录中的类
execution方法级别,细粒度的控制: 语法:execution(返回值类型 包名.类名.方法名(参数列表)) execution(void aop.service.UserServiceImpl.addUser()) 匹配方法 execution(void aop.service.PersonServiceImpl.addUser(String)) 方法参数必须为字符串 execution(* aop.service…星号.星号(…)) 万能配置
@annotaion应用于方法级别,实现细粒度的控制: @annotation(“com.jt.common.anno.RequestLog”)) 指定一个需要实现增强功能的方法
在AOP编程中有五种类型的通知: 1)前置通知 (@Before) 方法执行之前执行 2)返回通知 (@AfterReturning) 方法return之后执行 3)异常通知 (@AfterThrowing) 方法出现异常之后执行 4)后置通知 (@After) : 又称之为最终(始终要执行)通知(finally) 5)环绕通知 (@Around) :重点掌握 其结构如下: Try{ @Before 核心业务 @AfterReturning (先执行@After再Return) }catch(Exception e){ …. @AfterThrowing }finally{ …. @After } 如上四个一起使用时可以直接使用@Around通知替换
切入点及前置通知,后置通知,返回通知,异常通知,环绕通知的配置
@Aspect @Service public class LogAspect { @Pointcut("bean(orderServiceImpl)") public void doLog(){} @Before("doLog()") public void doBefore(){ System.out.println("log before"); } @After("doLog()") public void doAfter(){ System.out.println("log after"); } /**核心业务正常结束时执行 * 说明:假如有after,先执行after,再执行returning*/ @AfterReturning("doLog()") public void doAfterReturning(){ System.out.println("log doAfterReturning"); } /**核心业务出现异常时执行 说明:假如有after,先执行after,再执行Throwing*/ @AfterThrowing("doLog()") public void doAfterThrowing(){ System.out.println("log doAfterThrowing"); } }切入点及环绕通知的配置
@Component @Aspect public class TxManager { @Pointcut("execution(com.company.spring.service..*.*(..))") public void pointCut() {} @Around("pointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ try{ System.out.println("事务开始");//执行目标方法前执行 Object result = joinPoint.proceed();//执行目标方法 System.out.println("提交事务");//核心业务正常结束时执行 //说明:假如有after,先执行after,再执行returning return result; }catch(Exception e){ /*核心业务出现异常时执行 说明:假如有after,先执行after,再执行Throwing*/ System.out.println("回滚事务");//出现异常时执行 throw e; }finally{ System.out.println("释放资源");//始终要执行 } } }HAHAH