1.AOP的基本概念
AOP Aspect-Oriented Programming 面向切面编程,它的另一种翻译是面向方面编程(即将一方面一方面的业务逻辑增强到原有业务逻辑的方式)。它是一种编程思想能使软件简单,容易维护,更加容易被复用的一种编程方式;AOP只是OOP编程方式的一种补充,并非什么新的技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP编程方式解决问题的关键在于对关注点的抽象。对系统中的一些分散在多个不相关业务模块中的共同问题(关注点),将这些关注点从各个业务模块中抽象出来是AOP编程的精髓。如日志记录功能,事务管理功能,方法的性能分析等功能从系统业务模块的分离。
2.OOP和AOP开发流程的简单对比 :
OOP 编程基本流程
(1)归纳分析系统,抽象领域模型;
(2)使用class 来封装领域模型,实现类的功能;
(3)把各个类相互协作,组装形成一个完整的功能。
AOP 编程基本流程
(1)归纳分析系统中的关注点;
(2)分离方面对象,使用OOP 将这些方面封装成一个公用的类;
(3)织入到业务功能,按配置织入规则将切面对象(织入或集成)到系统功能中,形成一个增强后的功能。
3.对AOP的原理的简单了解
在《精通Spring4.x 企业应用开发实战》中提到过:Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类植入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。
在《Spring 实战 (第4版)》中也提到过:Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
Spring AOP在项目中主要完成什么?主要完成的是:把被我们横切出来的逻辑代码融合到业务逻辑中,使用动态代理技术来完成和之前(没抽取前)一样的功能(但是经过这样一处理便可以降低耦合度,提高程序的可重用性,同时提高了开发的效率)。其实在没学Spring AOP之前我们也可以使用代理来实现,代理可以帮我们增强对象的行为,使用动态代理实质上就是调用时拦截对象的方法,然后再对方法进行改造和增强。其实Spring AOP的底层原理就是动态代理。
在JAVA中有两种动态代理的方式:
(1)JDK动态代理;
(2)CGLib动态代理。
JDK动态代理和CGLib动态代理的简单对比:
(1)代理的对象不一样
JDK动态代理只能对实现了接口的类生成代理,而不能针对类;
而CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
(2)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。
(3)CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
(4)JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低,所以如果是单例的代理,最好采用CGLib代理。
Spring AOP默认是使用JDK动态代理,如果代理类没有接口则会使用CGLib代理。
4.AOP中的一些关键的专业术语
(1)关注点:就是系统中要解决的问题。比如在电子商务系统中,安全验证、日志记录等都属于关注点(Core Concerns);
(2)切面(Aspect):用来组织多个Advice,负责管理切入点和增强的关系;
(3)增强(Advice):定义何时在切入点执行“织入关注点”;如,在执行方法前执行before,在执行方法后执行after等等。
(4)连接点(Joinpoint):明确的某一个被织入代码的点,如方法调用,异常抛出,方法返回等等时间点;
(5)切入点(cutpoint):可以织入增强方法的连接点。当连接点满足被增强的要求时,会在该连接点添加增强,此时的连接点就成为切入点。
(6)织入(weaving) :织入是指把解决横切问题的切面模板,与系统中的其它核心模块通过一定策略或规则组合到一起的过程;具体的技术方案有:a.动态代理;b.类加载器织入c.编译器织入:AspectJ主要就是是使用这种织入方式
5.AOP的具体流程示意图
6.配置使用Spring AOP
在Spring的AOP开发中,程序员需要做以下三件事情:
(1)定义普通的业务类;
(2)定义切入点,对多个关注点进行横切;
(3)定义增强,在普通的业务功能中织入关注点的处理。
Spring依然提供两种对AOP的配置:
1)XML的方式
2)使用注解进行标注的方式
6.1基于XML的配置使用
项目目录
需要导入相关的包:
业务类OperateLogger.java,此处用于记录操作
package com.springstudy.service;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.springframework.stereotype.Service;
@Service
public class OperateLogger {
//操作前
public void beforeOperate() {
System.out.println("日志:操作开始前");
System.out.println("----------------");
}
//操作后
public void afterOperate() {
System.out.println("日志:操作结束后");
System.out.println("----------------");
}
/**
* 执行返回通知的函数
* @param jp 连接点,包含目标方法的信息
* @param ret 返回值对象
*/
public boolean afterRetrunOperate(JoinPoint jp,Object ret) {
System.out.println("目标方法的参数是:"+Arrays.toString(jp.getArgs()));
System.out.println("目标方法的返回值是:"+ret+"");
System.out.println("----------------");
return true;
}
}
EmployeeService.java
@Service
public class EmployeeService {
public int addEmployee(Employee emp) {
System.out.println("添加员工成功");
System.out.println("----------------");
return 0;
}
....
}
Spring配置文件beans.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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- Spring容器自动装配bean,配置相关搜索路径 -->
<context:component-scan base-package="com.springstudy" />
<aop:config>
<!-- 定义一个切点 -->
<aop:pointcut expression="execution(* com.springstudy..*Service.*(..))" id="pointcut1"/>
<!-- 配置切面 -->
<aop:aspect ref="operateLogger">
<!-- 前置通知 -->
<aop:before method="beforeOperate" pointcut-ref="pointcut1"/>
<!-- 后置通知 -->
<aop:after method="afterOperate" pointcut-ref="pointcut1"/>
<!-- 返回通知 -->
<aop:after-returning method="afterRetrunOperate" pointcut-ref="pointcut1" returning="ret"/>
<!-- <aop:after-returning method=""/>
<aop:after-throwing method=""/>
<aop:around method=""/> -->
<!-- < aop:aspect>定义切面时,只需要定义一般的bean就行,而定义< aop:advisor>中引用的通知时,通知必须实现Advice接口。 -->
<!-- < aop:aspect>大多用于日志,缓存 ,而< aop:advisor>大多用于事务管理。 -->
<!-- <aop:advisor advice-ref=""/> -->
</aop:aspect>
</aop:config>
</beans>
补充:< aop:aspect>和< aop:advisor>的简单对比:
(1)< aop:aspect>定义切面时,只需要定义一般的bean就行,而定义< aop:advisor>中引用的通知时,通知必须实现Advice接口。
(2)< aop:aspect>大多用于日志,缓存 ,而< aop:advisor>大多用于事务管理。
测试结果:
6.2基于注解的配置使用
大体步骤:
(1)需要导入引用 aspectJ 的 jar 包: aspectjweaver.jar aspectjrt.jar;
(2)使用注解@Aspect来定义一个切面,在切面中定义切入点(@Pointcut),通知类型(前置通知 @Before, 后置通知 @AfterReturning, 最终通知 @After, 异常通知 @AfterThrowing, 环绕通知 @Around).。
(3)切面配置到Spring的beans.xml中,可以使用自动扫描Bean的方式,并设置其自动执行: <aop:aspectj-autoproxy/>。
示例:
EmployeeService.java
@Service
public class EmployeeService {
public int addEmployee(Employee emp) {
System.out.println("添加员工成功");
System.out.println("----------------");
return 0;
}
public int delEmployee(Employee emp) {
System.out.println("删除员工成功");
System.out.println("----------------");
return 0;
}
//...
}
OperateLogger.java
package com.springstudy.service;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Service;
@Service
@Aspect
public class OperateLogger {
@Pointcut("execution(* com.springstudy..*Service.add*(..))")
public void pointAdd() {}
@Pointcut("execution(* com.springstudy..*Service.del*(..))")
public void pointDel() {}
@Before("pointAdd() or pointDel()")
public void beforeOperate() {
System.out.println("日志:操作开始前");
System.out.println("----------------");
}
@After("pointAdd() or pointDel()")
public void afterOperate() {
System.out.println("日志:操作结束后");
System.out.println("----------------");
}
/**
* 执行返回通知的函数
* @param jp 连接点,包含目标方法的信息
* @param ret 返回值对象
*/
@AfterReturning(pointcut="pointAdd() or pointDel()",returning="ret")
public boolean afterRetrunOperate(JoinPoint jp,Object ret) {
System.out.println("目标方法的参数是:"+Arrays.toString(jp.getArgs()));
System.out.println("目标方法的返回值是:"+ret+"");
System.out.println("----------------");
return true;
}
}
beans.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-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- Spring容器自动装配bean,配置相关搜索路径 -->
<context:component-scan base-package="com.springstudy" />
<!-- 自动执行AOP -->
<aop:aspectj-autoproxy/>
</beans>
测试结果:(简单测试添加一个员工和删除一个员工)
测试代码:
package com.springstudy.test;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.springstudy.pojo.Employee;
import com.springstudy.service.EmployeeService;
public class SpringAOPTest {
private static ApplicationContext context;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
context = new ClassPathXmlApplicationContext("beans.xml");
}
@Test
public void test() {
EmployeeService empService = context.getBean(EmployeeService.class);
Employee emp = new Employee();
emp.setEmpId(1001);
emp.setEmpName("张三");
empService.addEmployee(emp);
empService.delEmployee(emp);
}
}