目录
一、概念
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
二、原理
OOP:面向对象编程(纵向编程-->继承)
AOP:面向切面编程(横向编程-->代理机制)类似于生活中的插队,可以很轻易的对一个程序的前后进行功能的增强或者取消。
常用于:对程序进行功能增强,权限校验,日志记录,事务控制,性能监控。
三、入门程序
步骤:
- 导入jar包
- 导入约束
- 写增强方法
- 横向切入
- 测试
需求:使用aop在dao层中的findAll方法前面添加一个权限校验的功能。
3.1 导入jar包和约束
- pom.xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
- application.xml
xmlns:aop="http://www.springframework.org/schema/aop" ...... http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
<?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:p="http://www.springframework.org/schema/p"
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
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
3.2 写切面类
public class MyAspect {
public void checkPri(){
System.out.println("此处进行权限校验");
}
}
3.3 配置AOP(横向切入)
此处需特别注意的是:如果切入点返回类型为List,那么execution返回类型模式为java.util.List
application.xml
<!--1、配置目标对象-->
<bean id="productDaoImpl" class="com.jc.dao.impl.ProductDaoImpl"></bean>
<!--2、配置切面类-->
<bean id="myAspect" class="com.jc.aspect.MyAspect"></bean>
<!--3、配置AOP(切面、通知、切入点)-->
<aop:config>
<!--3.1 配置切面-->
<aop:aspect ref="myAspect">
<!--3.2 配置通知和切入点-->
<aop:before method="checkPri" pointcut="execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())"></aop:before>
</aop:aspect>
</aop:config>
3.4 测试
由图可知,权限校验方法插在了查询的前面执行
四、AOP术语详解
- Joinpoint:连接点,可以被拦截的点(所有的方法都可以成为连接点)
- Introduction:引介,类层面的增强(不作为研究)
- Target:目标,被增强的对象
- Weaving:织入,将通知(advice)应用到目标(target)的过程
- Proxy:代理对象,一个类被织入后产生的那个对象
- Aspect:切面,多个通知和多个切入点的结合
- Pointcut:切入点,真正被拦截到的点
- Advice:通知/增强,对目标方法进行的操作(权限,日志),方法层面
- 前置通知:目标方法之前操作,可以获得切入点的信息,如权限的校验
- 后置通知:目标方法之后操作,可以获得目标方法返回值的信息,在方法执行return后执行,抛出异常时不执行,如日志的记录
- 环绕通知:目标方法前后都操作,可阻止目标方法的执行,如性能分析,事务操作
- 异常抛出通常:程序出现异常的时候,可以获得异常信息,比如事务出异常的时候,得回滚
- 最终通知:相当于finally,在方法返回前执行
- 引介通知:
五、execution表达式
execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())
[访问控制修饰符] 方法返回值 包名.类名.方法名(参数)
*代表任意
..代表任意参数
+代表当前类及其子类
eg:* * com.jc.dao.*.*(..) dao包以及其子包下的所有所有方法
六、AOP通知示例
6.1 配置版
1)前置通知、环绕通知、后置通知、异常抛出通知
Dao方法ProductDaoImpl.java
public class ProductDaoImpl implements ProductDao {
@Override
public List<Product> findAll() {
System.out.println("此处为查询数据库操作");
return null;
}
@Override
public String add() {
System.out.println("此处为添加数据库操作");
return "张三";
}
@Override
public String update() {
int i=1/0;
System.out.println("dao层更新用户");
return "小白更新的";
}
}
AOP配置application.xml
<!--1、配置目标对象-->
<bean id="productDaoImpl" class="com.jc.dao.impl.ProductDaoImpl"></bean>
<!--2、配置切面类-->
<bean id="myAspect" class="com.jc.aspect.MyAspect"></bean>
<!--3、配置AOP(切面、通知、切入点)-->
<aop:config>
<!--3.1 配置切面-->
<aop:aspect ref="myAspect">
<!--3.2 配置通知和切入点-->
<aop:before method="checkPri" pointcut="execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())"></aop:before>
<aop:after-returning method="writeLog" pointcut="execution(public String com.jc.dao.impl.ProductDaoImpl.add())" returning="result"></aop:after-returning>
<aop:around method="around" pointcut="execution(public String com.jc.dao.impl.ProductDaoImpl.update())"></aop:around>
<aop:after-throwing method="error" pointcut="execution(public String com.jc.dao.impl.ProductDaoImpl.update())" throwing="th"></aop:after-throwing>
</aop:aspect>
</aop:config>
切面类MyAspect.java
public class MyAspect {
// 前置通知:权限设置
public void checkPri(JoinPoint joinPoint){
System.out.println("此处进行权限校验======"+joinPoint.getSignature().getName());
}
// 后置通知:日志记录
public void writeLog(Object result){
System.out.println("日志记录======"+result);
}
// 环绕通知:性能监控
public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
System.out.println("性能分析1======"+System.currentTimeMillis());
Object proceed = joinPoint.proceed();//调用目标方法
System.out.println("性能分析2======"+System.currentTimeMillis());
System.out.println(proceed);
return proceed;
}
// 异常抛出通知
//result就是返回值信息,必须和配置文件中一致
public void error(Throwable th){
System.out.println("进行异常通知====>"+th.getMessage());
}
}
测试类
public class TestApplication {
@Test
public void TestAspect(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
// ProductDaoImpl productDao=(ProductDaoImpl)applicationContext.getBean("productDaoImpl");
ProductDao productDao=(ProductDao)applicationContext.getBean("productDaoImpl");
productDao.findAll();
productDao.add();
productDao.update();
}
}
测试结果
前置通知、后置通知、环绕通知、异常抛出通知
2)最终通知:
<aop:after method="after" pointcut="execution(public *com.jc.dao.impl.ProductDaoImpl.*(..))"></aop:after>
// 最终通知
public void after(){
System.out.println("最终通知======相当于finally");
}
6.2 注解版
步骤:
- 开启AOP注解支持
- 切面类使用注解
- 测试
程序代码:
application.xml
<!--1、开启AOP注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--1、配置目标对象-->
<bean id="productDaoImpl" class="com.jc.dao.impl.ProductDaoImpl"></bean>
<!--2、配置切面类-->
<bean id="myAspect" class="com.jc.aspect.MyAspect"></bean>
MyAspect.java
@Aspect
public class MyAspect {
/**
* 切入点注解
* 如果有多个地方引用了该表达式,可提取一个方法
* 每次使用时,类名.方法名就可
*/
@Pointcut(value = "execution(public String com.jc.dao.impl.ProductDaoImpl.update())")
private void update(){}
// 前置通知:权限设置
@Before(value = "execution(public java.util.List com.jc.dao.impl.ProductDaoImpl.findAll())")
public void checkPri(JoinPoint joinPoint){
System.out.println("此处进行权限校验======"+joinPoint.getSignature().getName());
}
// 后置通知:日志记录
@AfterReturning(value = "execution(public String com.jc.dao.impl.ProductDaoImpl.add())",returning = "result")
public void writeLog(Object result){
System.out.println("日志记录======"+result);
}
// 环绕通知:性能监控
@Around(value = "MyAspect.update()")
public Object around(ProceedingJoinPoint joinPoint)throws Throwable{
System.out.println("性能分析1======"+System.currentTimeMillis());
Object proceed = joinPoint.proceed();//调用目标方法
System.out.println("性能分析2======"+System.currentTimeMillis());
System.out.println(proceed);
return proceed;
}
// 异常抛出通知
//result就是返回值信息,必须和配置文件中一致
@AfterThrowing(value = "MyAspect.update()",throwing = "th")
public void error(Throwable th){
System.out.println("进行异常通知====>"+th.getMessage());
}
// 最终通知
@After(value = "execution(public * com.jc.dao.impl.ProductDaoImpl.*(..))")
public void after(){
System.out.println("最终通知======相当于finally");
}
}
七、bug修复
7.1 AOP动态代理类型转换出错
问题:
spring aop中的动态代理时,碰到了一个类型转换的问题:
java.lang.ClassCastException: com.sun.proxy.$Proxy5 cannot be cast to com.jc.dao.impl.ProductDaoImpl
原因:
spring使用的动态代理有两种:JDK Proxy 和CGLIB。使用前者必须实现至少一个接口才能实现对方法的拦截。使用后者需要两个jar包:asm.jar和cglib.jar,并修改spring配置文件。当代理对象实现了至少一个接口时,默认使用JDK动态创建代理对象;当代理对象没有实现任何接口时,就会使用CGLIB方法。
如果实现了接口,强制转换必须用父类接口来定义
解决: