一 、Spring AOP介绍
AOP的全称是Aspect Oriented Programming,即面向切面编程
AOP将重复的业务逻辑抽取到一个独立的模块中,提供了将其切入到其它业务逻辑中的方法AOP的使用使开发人员专心于核心业务的编写,不必关注其他业务逻辑的
AOP提高了开发效率,且
增强了代码的可维护性
二 、Spring AOP的实现机制
AOP的常用术语。
切面(Aspect):指关注点形成的类(关注点是指类中重复的代码)。通常是指封装的、用 于横向插入系统的功能类(如事务管理、日志记录等)。
**连接点(Joinpoint):**指程序执行过程中某个特定的节点。
切入点(Pointcut):当连接点满足预先指定的条件时,AOP在连接点处插入切面,此时的 连接点称为切入点。
通知/增强处理(Advice):指插入的切面程序代码。即:切面中的方法。 目标对象(Target):目标对象是指被插入切面的方法。
织入(Weaving):将切面代码插入到目标对象上,从而生成代理对象的过程。
代理(Proxy):将通知应用到目标对象之后,程序动态创建的通知对象就称为代理。 引介(Introduction):引介是一种特殊的通知,它为目标对象添加一些属性和方法。
AOP是基于代理机制基础上的,有JDK和CGLib动态代理两种机制
实验环境准备
第一步:在ssm_spring项目下建spring-chap08-aop模块,目录结构如下:
第二步:为项目的模块添加依赖,依赖06单元配置好的lib库
- JDK动态代理
Spring AOP默认使用JDK动态代理,在不修改源代码的情况下增强某些方法
- JDK动态代理实现步骤
第一步:创建代理类,此代理类要实现InvocationHandler接口
第二步:在此代理类中创建返回目标类代理对象的方法,此方法中用java.lang.reflect.Proxy 类的newProxyInstance()方法生成代理对象,其下3个参数说明
第1个参数是classLoader,表示当前类的类加载器
第2个参数是classes,表示被代理对象实现的所有接口 第3个参数是this,表示代理类JdkProxy本身
第三步:实现InvocationHandler接口的invoke()方法,其中完成通知的织入
- 示例
【示例】阅读代码,理解JDK动态代理实现过程。
- 在main\java目录中,创建com.aop.demo01包
- 创建接口UserDao,编写添加和删除的方法**(放com.aop.demo01包)**
package com.aop.demo01;
public interface UserDao { void addUser();
void deleteUser();
}
- 创建类UserDaoImpl,实现UserDao接口**(放com.aop.demo01包)**
package com.aop.demo01;
public class UserDaoImpl implements UserDao { @Override
public void addUser() { System.out.println("添加用户");
}
@Override
public void deleteUser() { System.out.println("删除用户");
}
}
- 创建切面类MyAspect,在该类中定义一个模拟权限检查的方法和一个模拟日志记录的方法**(两个方法就是切面中的通知,放com.aop.demo01包)**
package com.aop.demo01;
public class MyAspect {
public void check_Permissions(){ System.out.println("模拟检查权限...");
}
public void log(){
System.out.println("模拟记录日志...");
}
}
- 创建代理类MyJDKProxy,实现InvocationHandler接口设置代理类的调用处理程序,通过
newProxyInstance()生成代理方法 (放com.aop.demo01包)
注意:InvocationHandler接口是java.lang.reflect包下的!!
package com.aop.demo01;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyJDKProxy implements InvocationHandler { private UserDao userDao; //声明目标对象(被代理对象)
//创建代理方法
public Object createProxy(UserDao userDao){ this.userDao=userDao;
//获取代理对象
//第一步,获取类加载器
ClassLoader classLoader=MyProxy.class.getClassLoader();
//第二步,获取被代理对象实现的所有接口
Class<?>[] interfaces = userDao.getClass().getInterfaces();
//第三步,获取代理对象并返回
return Proxy.newProxyInstance(classLoader,interfaces,this);
}
/**
-
所有动态代理方法的调用,均会交由下面的invoke方法处理
-
proxy -- 被代理对象
-
method -- 将要被执行的方法信息
-
args -- 执行方法时需要的参数
*/ @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MyAspect aspect=new MyAspect(); //实例化切面对象
aspect.check_Permissions(); //前增强 -- 切入权限检查方法
Object obj=method.invoke(userDao,args); //目标对象(被代理对象)上调用方法,传入
参数
aspect.log(); //后增强 -- 切入记录日志方法
return obj;
}
}
- 创建测试类TestJDKProxy
import com.aop.demo01.MyJDKProxy; import com.aop.demo01.UserDao; import com.aop.demo01.UserDaoImpl;
public class TestJDKProxy {
public static void main(String[] args) { MyJDKProxy proxy = new MyJDKProxy(); UserDao userDao = new UserDaoImpl();
System.out.println("================代理前================"); userDao.addUser();
System.out.println(" ");
userDao.deleteUser();
userDao = (UserDao) proxy.createProxy(userDao); //为userDao生成代理对象,实
现增强
System.out.println("================代理后================"); userDao.addUser();
System.out.println(" ");
userDao.deleteUser();
}
}
运行结果如下图所示。
- CGLib动态代理
**JDK动态代理缺陷:**只能为接口创建代理对象
CGLib动态代理可以为类创建代理对象,采用底层的字节码技术,通过继承的方式动态创建代理对象
CGLib动态代理通过实现MethodInterceptor接口的intercept()方法完成代理CGLib动态代理已在spring核心包中集成,无需导入
CGLib动态代理实现步骤
第一步:创建代理类,此代理类要实现MethodInterceptor接口
第二步:在此代理类中创建返回目标类代理对象的方法,此方法中用Enhancer类的create()方法 目标类生成代理对象
第三步:实现MethodInterceptor接口的intercept()方法,其中完成通知的织入
- 示例
【示例】阅读代码,理解CGLib动态代理实现过程。
- 在main\java目录中,创建com.aop.demo02包
- 创建目标类UserDao,在该类中编写添加用户和删除用户的方法。(放com.aop.demo02包)
package com.aop.demo02;
public class UserDao { public void addUser() {
System.out.println("添加用户");
}
public void deleteUser() { System.out.println("删除用户");
}
}
-
创建切面类MyAspect,内容同上例**(放com.aop.demo02包)**
-
创建代理类MyCglibProxy,该代理类需要实现MethodInterceptor接口用于设置代理类的调用处理程序,并实现intercept()方法。 (放com.aop.demo02包)
package com.aop.demo02;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyCglibProxy implements MethodInterceptor {
//创建代理方法
public Object createProxy(Object target){
//创建动态对象
Enhancer enhancer = new Enhancer();
//确定需要增强的类,设置其为父类enhancer.setSuperclass(target.getClass());
//添加回调函数enhancer.setCallback(this);
//返回创建的代理类
return enhancer.create();
}
/**
- proxy -- cglib根据指定父类生成的代理对象
- method -- 拦截的方法
- args -- 拦截方法的参数
- methodProxy -- 方法的代理对象,用于执行父类的方法
*/ @Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
MyAspect aspect=new MyAspect(); //实例化切面对象
aspect.check_Permissions(); //前增强 -- 切入权限检查方法
Object obj=methodProxy.invokeSuper(proxy,args); //执行目标方法,并传入参数aspect.log(); //后增强 -- 切入记录日志方法
return obj;
}
}
创建测试类TestCglibProxy
import com.aop.demo01.UserDao; import com.aop.demo01.UserDaoImpl; import com.aop.demo02.MyCglibProxy;
public class TestCgllibProxy {
public static void main(String[] args) { MyCglibProxy proxy = new MyCglibProxy(); UserDao userDao = new UserDaoImpl();
System.out.println("================代理前================"); userDao.addUser();
System.out.println(" ");
userDao.deleteUser();
userDao = (UserDao) proxy.createProxy(userDao); //为userDao生成代理对象,实
现增强
System.out.println("================代理后================");
userDao.addUser(); System.out.println(" ");
userDao.deleteUser();
}
}
运行结果同上例。
三** ****、基于XML的AOP实现 **
Spring AOP中的代理对象由IoC容器自动生成
在Spring配置文件中做相关配置:目标类(被代理类)的bean,切面类的bean,切面,连接点, 各类通知等
Spring AOP的配置元素如下
元素<br> | 描述<br> |
---|---|
aop:config<br> | Spring AOP配置的根元素<br> |
aop:aspect<br> | 配置切面<br> |
aop:advisor<br> | 配置通知器<br> |
aop:pointcut<br> | 配置切点<br> |
aop:before<br> | 配置前置通知,在目标方法执行前实施增强,可以应用于权限管理等功能<br> |
<br>aop:after<br> | 配置后置通知,在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能<br> |
<br>aop:around<br> | 配置环绕方式,在目标方法执行前后实施增强,可以应用于日志、事务管理等功能<br> |
<aop:after- returning><br> | 配置返回通知,在目标方法成功执行之后调用通知<br> |
<aop:after- throwing><br> | 配置异常通知,在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能<br> |
- 基于XML的Spring AOP配置实现步骤
第一步:导入相关依赖包spring-aop.jar、aopalliance.jar、aspectjweaver.jar,并引入aop约束
lib中的jar包**(必须包含下图中的jar包)**
引入aop约束
第二步:配置目标类(被代理类)的Bean**(在spring配置文件中完成) 第三步:配置切面类的Bean(在spring配置文件中完成)**
第四步:配置切面**(在spring配置文件中完成)**
使用aop:aspect元素配置切面,其属性ref指向切面类bean的id值第五步:配置切入点**(在spring配置文件中完成)**
使用aop:config元素下的子元素aop:pointcut配置切入点时,表示该切入点是全局的,它可被 多个切面共享
使用aop:aspect元素下的子元素aop:pointcut配置切入点时,表示该切入点只对当前切面有效 子元素aop:pointcut的属性有id和expression
属性名称<br> | 描述<br> |
---|---|
id<br> | 用于指定切入点的唯一标识<br> |
<br> | 用于指定切入点关联的切入点表达式。示例:expression="切入点表达式"<br> |
<br> | **切入点表达式通用语法:**execution(modifiers-pattern?ret-type-pattern<br> |
<br> | declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)<br> |
<br> | 说明:<br> |
<br> | modifiers-pattern:定义的目标方法的访问修饰符,如public、private等。<br> |
<br> | ret-type-pattern:定义的目标方法的返回值类型,如void、String等。<br> |
<br> | declaring-type-pattern:定义的目标方法的类路径,如<br> |
<br> | com.itheima.jdk.UserDaoImpl。<br> |
<br> | name-pattern:表示具体需要被代理的目标方法,如add()方法。<br> |
<br> | param-pattern:表示需要被代理目标方法包含的参数,本章示例中目标方法参数<br> |
<br> | 都为空。<br> |
<br> | throws-pattern:表示需要被代理的目标方法抛出的异常类型。<br> |
<br> | ----------------------------------------------------------------------------------------------------------------<br> |
<br> | --------<br> |
<br> | **切入点表达式常用语法:*execution( 包名..(..))<br> |
expression<br> | **说明:**表达式分5个部分。<br> |
<br> | (1)execution(): 表达式主体。<br> |
<br> | (2)第一个*号:方法返回类型,*号表示所有的类型。<br> |
<br> | (3)包名:表示需要拦截的包名。<br> |
<br> | (4)第二个*号:表示类名,*号表示所有的类。<br> |
<br> | (5)*(..):最后这个星号表示方法名,*号表示所有的方法;括号里面表示方法的参<br> |
<br> | 数,两个句点表示任何参数 其中除了返回类型模式、方法名模式和参数模式外,<br> |
<br> | 其它项都是可选的。<br> |
<br> | 示例:<br> |
<br> | execution(public * *(..)) 匹配所有的public修饰符的方法。<br> |
<br> | execution(* set*(..)) 匹配所有”set”开头的方法。<br> |
<br> | execution(* com.xyz.service.AccountService.*(..)) 匹配AccountService 接口/类<br> |
<br> | 的所有方法。<br> |
<br> | ---------------------------------------------------------------------------------------------------<br> |
<br> | **切入点表达多个时,之间用逻辑符 |
<br> | **示例:*execution( *.addUser(..)) |
第六步:配置通知
有5种常用通知:前置通知、后置通知、环绕通知、返回通知和异常通知。
使用元素aop:aspect的子元素**aop:before配置前置通知**、aop:after后置通知、
aop:around配置环绕通知、aop:after-returning配置返回通知、aop:after-throwing
配置异常通知
各类通知元素属性均相同,具体如下。
属性<br> | 描述<br> |
---|---|
<br>pointcut<br> | 该属性用于指定一个切入点表达式,Spring将在匹配该表达式的连接点时织入该通知。<br> |
pointcut- ref<br> | 该属性指定一个已经存在的切入点名称,如配置代码中的myPointCut。通常<br>pointcut和pointcut-ref两个属性只需要使用其中一个即可。<br> |
method<br> | 该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理。<br> |
<br>throwing<br> | 该属性只对<after-throwing>元素有效,它用于指定一个形参名,异常通知方法可 以通过该形参访问目标方法所抛出的异常。<br> |
<br>returning<br> | 该属性只对<after-returning>元素有效,它用于指定一个形参名,后置通知方法 可以通过该形参访问目标方法的返回值。<br> |
- 示例
【示例】阅读代码,理解基于XML的AOP配置过程。
- 在main\java目录中,创建com.aop.demo03包
- 导入依赖包spring-aop-5.3.12.jar、aopalliance-1.0.jar、aspectjweaver-1.8.10.jar
- 创建接口UserDao,编写添加、删除、修改和查询的方法。(放com.aop.demo03包)
package com.aop.demo03;
public interface UserDao { void addUser();
void deleteUser(); void updateUser(); void selectUser();
}
- 创建类UserDaoImpl,实现UserDao接口**(放com.aop.demo03包)**
package com.aop.demo03;
public class UserDaoImpl implements UserDao { @Override
public void addUser() { System.out.println("--添加用户");
}
@Override
public void deleteUser() { System.out.println("--删除用户");
}
@Override
public void updateUser() { System.out.println("--修改用户");
}
@Override
public void selectUser() {
System.out.println("--查询用户");
}
}
- 创建XmlAdvice类,用于定义通知。(此类即为切面,放com.aop.demo03包)
package com.aop.demo03;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class XmlAdvice {
//前置通知
public void before(JoinPoint point){ System.out.println(" 这 是 前 置 通 知 ..."); System.out.println("目标对象:"+point.getTarget());
System.out.println("被增强的方法:"+point.getSignature().getName());
}
//返回通知
public void afterReturning(JoinPoint point){ System.out.println("这是返回通知...(方法不抛出异常时调用)");
}
/**
- 环绕通知要求:
- (1)必须返回Object类型值;
- (2)必须接收一个参数,类型为ProceedingJoinPoint
- (3)必须throws Throwable
- ProceedingJoinPoint -- 是JoinPoint的子接口,可以执行目标方法
*/
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分...");
Object obj = point.proceed(); System.out.println("这是环绕通知之后的部分..."); return obj;
}
//异常通知
public void afterException(){
System.out.println("异常通知...(抛出异常时调用!)");
}
//后置通知
public void after(){ System.out.println("这是后置通知...");
}
}
创建spring的配置文件spring-config01.xml,在该文件中引入aop约束,完成AOP配置(放
resources文件夹)
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="Index of /schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置UserDao类的Bean-->
<bean id="userDao" class="com.aop.demo03.UserDaoImpl"/>
<!--配置切面类的Bean-->
<bean id="aspect" class="com.aop.demo03.XmlAdvice"/>
<!--配置Spring AOP-->
aop:config
<!--指定切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.aop.demo03.UserDaoImpl.*(..))"/>
<!--指定切面-->
<aop:aspect id="aspect" ref="aspect">
<!--指定前置通知-->
<aop:before method="before" pointcut-ref="pointcut"/>
<!--指定环绕通知-->
<aop:around method="around" pointcut-ref="pointcut"/>
<!--指定返回通知-->
<aop:after-returning method="afterReturning" pointcut- ref="pointcut"/>
<!--指定异常通知-->
<aop:after-throwing method="afterException" pointcut- ref="pointcut"/>
<!--指定后置通知-->
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
创建测试类TestXML
import com.aop.demo03.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXml {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring- config01.xml");
UserDao userDao = context.getBean(UserDao.class);
userDao.addUser(); System.out.println("=========================="); userDao.deleteUser(); System.out.println("=========================="); userDao.updateUser(); System.out.println("=========================="); userDao.selectUser();
}
}
运行结果如下图所示。
**思考:**如果只对addUser和selectUser()两个方法增强,该怎么办?
四** ****、基于注解的AOP实现 **
Spring AOP允许使用注解方式实现AOP,以简化Spring配置文件Spring AOP的注解
元素<br> | 描述<br> |
---|---|
@Aspect<br> | 配置切面<br> |
@Pointcut<br> | 配置切点<br> |
@Before<br> | 配置前置通知<br> |
@After<br> | 配置后置通知<br> |
@Around<br> | 配置环绕方式<br> |
@AfterReturning<br> | 配置返回通知<br> |
@AfterThrowing<br> | 配置异常通知<br> |
- 基于XML的Spring AOP配置实现步骤
第一步:导入相关依赖包spring-aop.jar、aopalliance.jar、aspectjweaver.jar,并引入aop约束 第二步:创建带注解的切面类
第三步:在spring配置文件进行如下配置
引入aop约束
配置目标类的bean 配置切面类的Bean
开启@aspectj的自动代理支持
示例如下。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="Index of /schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册Bean -->
<bean name="userDao" class="com.aop.demo03.UserDaoImpl"/>
<bean name="AnnoAdvice" class="com.aop.demo04.AnnoAdvice"/>
<!-- 开启@aspectj的自动代理支持 -->
aop:aspectj-autoproxy/
</beans>
- 示例
【示例】阅读代码,理解基于注解L的AOP配置过程。
- 在main\java目录中,创建com.aop.demo04包
- 导入依赖包spring-aop-5.3.12.jar、aopalliance-1.0.jar、aspectjweaver-1.8.10.jar (3)创建接口UserDao,内容同上例**(放com.aop.demo04包)**
- 创建类UserDaoImpl,实现UserDao接口,内容同上例**(放com.aop.demo04包)**
创建带注解的AnnoAdvice类,用于定义通知(此类即为切面,放com.aop.demo04包)
package com.aop.demo04;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*;
@Aspect
public class AnnoAdvice {
//切点
@Pointcut("execution(* com.aop.demo04.UserDaoImpl.*(..))") public void pointCut(){}
//前置通知
//通知的切入点:为之前定义的pointCut切点@Before("pointCut()")
public void before(JoinPoint point){ System.out.println("这是前置通知...");
}
//返回通知
//临时定义通知的切点:切入点为任何包下任何类的addUser()方法和selectUser()方法@AfterReturning("execution(* .addUser(..))||execution( *.selectUser(..))") public void afterReturning(JoinPoint point){
System.out.println("这是返回通知...(方法不抛出异常时调用)");
}
/**
- 环绕通知要求:
- (1)必须返回Object类型值;
- (2)必须接收一个参数,类型为ProceedingJoinPoint
- (3)必须throws Throwable
- ProceedingJoinPoint -- 是JoinPoint的子接口,可以执行目标方法
*/
//临时定义通知的切点:切入点为任何包中任何类的deleteUser()方法@Around("execution(* *.deleteUser(..))")
public Object around(ProceedingJoinPoint point) throws Throwable { System.out.println("这是环绕通知之前的部分...");
Object obj = point.proceed(); System.out.println("这是环绕通知之后的部分..."); return obj;
}
//异常通知
//临时定义通知的切点:切入点为com.aop.demo03包下任何类的任何方法@AfterThrowing("execution(* com.aop.demo03..(..))") public void afterException(){
System.out.println("异常通知...(抛出异常时调用!)");
}
//后置通知
//临时定义通知的切点:切入点为任何包下任何类的updateUser()方法@After("execution(* *.updateUser(..))")
public void after(){ System.out.println("这是后置通知...");
}
}
创建spring配置文件spring-config02.xml,引入aop约束,配置目标类和切面类的bean,开启@aspectj的自动代理支持。
<?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="Index of /schema/aop" xsi:schemaLocation="Index of /schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd Index of /schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置UserDao类的Bean-->
<bean id="userDao" class="com.aop.demo04.UserDaoImpl"/>
<!--配置切面类的Bean-->
<bean id="aspect" class="com.aop.demo04.AnnoAdvice"/>
<!--开启@Aspect自动扫描支持-->
<aop:aspectj-autoproxy />
</beans>
- 创建试类TestAnnotation
import com.aop.demo04.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnotation {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring- config02.xml");
UserDao userDao = context.getBean(UserDao.class);
userDao.addUser(); System.out.println("=========================="); userDao.deleteUser(); System.out.println("=========================="); userDao.updateUser(); System.out.println("=========================="); userDao.selectUser();
}
}
运行结果如下图所示。
五** ****、课后练习 **
上机:考察知识点为Spring AOP概述、Spring AOP术语、JDK动态代理、CGLib动态代理、基于XML的AOP实现、基于注解的AOP实现)
形式:单独完成题目:
代码演示CGLib动态代理的实现过程。具体要求如下:
- ssm_spring项目下,创建模块名称为spring-chap08-aop;
- 创建包名为com.aop.task01;
- 创建代理类名称为CglibProxy;
- 创建测试类名称为CglibTest; 在控制台输出如下结果:
- 代码演示基于注解的AOP的实现过程。
- ssm_spring项目下,创建模块名称为spring-chap08-aop;
- 创建包名为com.aop.task02;
- 创建配置文件名称为applicationContext-Anno.xml。