Spring AOP
Spring AOP的简单了解、使用。
目录
3. 在applicationContext.xml配置Aspect Bean
1. 在applicationContext.xml中增加IOC组件扫描和启用AOP注解模式
一、Spring AOP是什么?
AOP (Aspect Orient Programming),直译过来就是 面向切面编程。AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。面向对象编程将程序抽象成各个层次的对象,而面向切面编程是将程序抽象成各个切面。
二、了解Spring AOP
1、Spring AOP与AspectJ的关系
Eclipse AspectJ 是一种基于Java平台的面向切面变成的语言,Spring AOP使用AspectJWeaver实现类的方法匹配,当匹配上后,AOP利用代理模式实现对象运行时的功能扩展
2、几个关键概念
例如:
//切面类
public class MethodAspect {
//切面方法,用于扩展额外功能
//JoinPoint 连接点,通过连接点可以获取目标类/方法的信息
public void printExecutionTime(JoinPoint joinPoint){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
String methodName = joinPoint.getSignature().getName();//获取目标方法名称
System.out.println("---->" + now + ":" + className + "." + methodName);
Object[] args = joinPoint.getArgs();
System.out.println("---->参数个数:" + args.length);
for(Object arg:args){
System.out.println("---->参数:" + arg);
}
}
public void doAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("<----返回后通知:" + ret);
}
public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
System.out.println("<----异常通知:" + th.getMessage());
}
public void doAfter(JoinPoint joinPoint){
System.out.println("<----触发后置通知");
}
}
三、使用步骤
1.maven中引入依赖
<repositories>
<repository>
<id>aliyun</id>
<name>aliyun</name>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!--aspectjweaver是Spring AOP的底层依赖-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
2. 定义切面类和切面方法
//切面类
public class MethodAspect {
//切面方法,用于扩展额外功能
//JoinPoint 连接点,通过连接点可以获取目标类/方法的信息
public void printExecutionTime(JoinPoint joinPoint){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
String methodName = joinPoint.getSignature().getName();//获取目标方法名称
System.out.println("---->" + now + ":" + className + "." + methodName);
Object[] args = joinPoint.getArgs();
System.out.println("---->参数个数:" + args.length);
for(Object arg:args){
System.out.println("---->参数:" + arg);
}
}
public void doAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("<----返回后通知:" + ret);
}
public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
System.out.println("<----异常通知:" + th.getMessage());
}
public void doAfter(JoinPoint joinPoint){
System.out.println("<----触发后置通知");
}
}
在切面方法中需要配置JoinPoint joinPoint连接点参数。
2.1 JointPoint核心方法
//切面类
public class MethodAspect {
//切面方法,用于扩展额外功能
//JoinPoint 连接点,通过连接点可以获取目标类/方法的信息
public void printExecutionTime(JoinPoint joinPoint){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
String className = joinPoint.getTarget().getClass().getName();//获取目标类的名称
String methodName = joinPoint.getSignature().getName();//获取目标方法名称
System.out.println("---->" + now + ":" + classNane + "." + methodName);
Object[] args = joinPoint.getArgs();//获取目标方法参数
System.out.println("---->参数个数:" + args.length);
for(Object arg:args){
System.out.println("---->参数:" + arg);
}
}
}
3. 在applicationContext.xml配置Aspect Bean
在配置文件中,比IOC的表头增加了aop命名空间,如下:
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns="http://www.springframework.org/schema/beans"
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">
</bean>
在配置文件下配置切面类bean
<!-- AOP配置 -->
<bean id="methodAspect" class="com.imooc.spring.aop.aspect.MethodAspect"></bean>
4. 定义PointCut
4.1 了解PointCut切点表达式
<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"></aop:pointcut>
<aop:config>
<!-- PointCut 切点,使用execution表达式描述切面的作用范围 -->
<!-- execution(public * com.imooc..*.*(..)) 说明切面作用在com.imooc包下的所有类的所有方法上 -->
<!--<aop:pointcut id="pointcut" expression="execution(public * com.imooc..*.*(..))"></aop:pointcut>-->
<!--只对所有Service类生效-->
<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(..))"></aop:pointcut>
<!--只对所有返回值为String类型方法生效-->
<!--<aop:pointcut id="pointcut" expression="execution(String com.imooc..*Service.*(..))"></aop:pointcut>-->
<!--对方法名进行约束 -->
<!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.create*(..))"></aop:pointcut>-->
<!-- 对参数进行约束 -->
<!--<aop:pointcut id="pointcut" expression="execution(* com.imooc..*Service.*(String,*))"></aop:pointcut>-->
<!-- 定义切面类 -->
<aop:aspect ref="methodAspect">
<!-- before通知(Advice),代表在目标方法运行前先执行methodAspect.printExecutionTime() -->
<aop:before method="printExecutionTime" pointcut-ref="pointcut"/>
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
<aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
<aop:after method="doAfter" pointcut-ref="pointcut"></aop:after>
</aop:aspect>
</aop:config>
5. 配置Advice
5.1 五中通知类型
After Advice 是无论目标方法成功还是异常,方法运行后都会执行
返回后通知:参数有2个,JointPoint以及另一个Object来接受目标方法的返回值。
public void doAfterReturning(JoinPoint joinPoint,Object ret){
System.out.println("<----返回后通知:" + ret);
配置文件:
<aop:aspect ref="methodAspect">
<aop:after-returning method="doAfterReturning" returning="ret" pointcut-ref="pointcut"/>
</aop:aspect>
返回后通知:参数有2个,JointPoint以及另一个Object来接受异常信息。
public void doAfterThrowing(JoinPoint joinPoint,Throwable th){
System.out.println("<----异常通知:" + th.getMessage());
}
配置文件:
<aop:aspect ref="methodAspect">
<aop:after-throwing method="doAfterThrowing" throwing="th" pointcut-ref="pointcut"/>
</aop:aspect>
返回后通知、后置通知、异常通知的顺序由配置文件中定义的顺序而定。
环绕通知:参数只有一个,ProceedingJointPoint 是JointPoint的升级版,在原有功能外,还可以控制目标方法的是否执行。
public class MethodChecker {
ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = pjp.proceed();//执行目标方法
long endTime = new Date().getTime();
long duration = endTime - startTime; //执行时长
if(duration >= 1000){
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
System.out.println("=======" + now + ":" + className + "." + methodName + "(" + duration + "ms)======");
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception message:" + throwable.getMessage());
throw throwable;
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns="http://www.springframework.org/schema/beans"
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">
<bean id="userDao" class="com.imooc.spring.aop.dao.UserDao"/>
<bean id="employeeDao" class="com.imooc.spring.aop.dao.EmployeeDao"/>
<bean id="userService" class="com.imooc.spring.aop.service.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="employeeService" class="com.imooc.spring.aop.service.EmployeeService">
<property name="employeeDao" ref="employeeDao"/>
</bean>
<bean id="methodChecker" class="com.imooc.spring.aop.aspect.MethodChecker"></bean>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* com.imooc..*.*(..))"></aop:pointcut>
<aop:aspect ref="methodChecker">
<!--环绕通知-->
<aop:around method="check" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
5.2 引介增强
引介增强(IntroductionInterceptor)是对类的增强,而非方法,上面5中增强是对方法的增强,而引介增强是允许在运行时为目标类增加新属性或方法,这是一种高级引用,程序运行时,这些类被加载到jvm中,引介增强再对内存中的类动态增强。引介增强允许在运行时改变类的行为,让类随运行环境动态变更。
6、使用注解开发Spring AOP
1. 在applicationContext.xml中增加IOC组件扫描和启用AOP注解模式
<?xml version="1.0" encoding="UTF-8"?>
<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"
xmlns="http://www.springframework.org/schema/beans"
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">
<!--初始化IoC容器-->
<context:component-scan base-package="com.imooc"/>
<!--启用Spring AOP注解模式-->
<aop:aspectj-autoproxy/>
</beans>
2.实体类以及三层类
Dao类
/**
* 员工表Dao
*/
@Repository
public class EmployeeDao {
public void insert(){
System.out.println("新增员工数据");
}
}
/**
* 用户表Dao
*/
@Repository
public class UserDao {
public void insert(){
System.out.println("新增用户数据");
}
}
Service类:
/**
* 用户服务
*/
@Service
public class UserService {
@Resource
private UserDao userDao;
public void createUser(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行员工入职业务逻辑");
userDao.insert();
}
public String generateRandomPassword(String type , Integer length){
System.out.println("按" + type + "方式生成"+ length + "位随机密码");
return "Zxcquei1";
}
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
/**
* 员工服务
*/
@Service
public class EmployeeService {
@Resource
private EmployeeDao employeeDao;
public void entry(){
System.out.println("执行员工入职业务逻辑");
employeeDao.insert();
}
public EmployeeDao getEmployeeDao() {
return employeeDao;
}
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
}
aspect类:
1、在类上加@Aspect注解。
2、在方法上添加@Around注解说明是环绕通知
@Component //标记当前类为组件
@Aspect //说明当前类是切面类
public class MethodChecker {
//环绕通知,参数为PointCut切点表达式
@Around("execution(* com.imooc..*Service.*(..))")
//ProceedingJoinPoint是JoinPoint的升级版,在原有功能外,还可以控制目标方法是否执行
public Object check(ProceedingJoinPoint pjp) throws Throwable {
try {
long startTime = new Date().getTime();
Object ret = pjp.proceed();//执行目标方法
long endTime = new Date().getTime();
long duration = endTime - startTime; //执行时长
if(duration >= 1000){
String className = pjp.getTarget().getClass().getName();
String methodName = pjp.getSignature().getName();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String now = sdf.format(new Date());
System.out.println("=======" + now + ":" + className + "." + methodName + "(" + duration + "ms)======");
}
return ret;
} catch (Throwable throwable) {
System.out.println("Exception message:" + throwable.getMessage());
throw throwable;
}
}
}
四、Spring AOP实现原理
Spring基于2种代理模式实现功能动态扩展:
1、目标类拥有接口,通过JDK动态代理实现功能扩展
2、目标类没有接口,通过CGlib组件实现功能扩展
目标类有接口优先使用JDK动态代理,没有接口使用CGLib动态代理。
1、代理模式
代理模式通过代理对象对原对象的实现功能扩展
静态代理,是指必须手动创建代理类的代理模式,也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
动态代理,代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
1.1 静态代理
需要手动去new代理类,如下 Application类中:
public interface UserService {
public void createUser();
}
public class UserServiceImpl implements UserService{
public void createUser() {
System.out.println("执行创建用户业务逻辑");
}
}
//静态代理是指必须手动创建代理类的代理模式使用方式
public class UserServiceProxy implements UserService{
//持有委托类的对象
private UserService userService ;
public UserServiceProxy(UserService userService){
this.userService = userService;
}
//===========前置功能===========
public void createUser() {
System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=========");
userService.createUser();
}
}
public class UserServiceProxy1 implements UserService{
private UserService userService ;
public UserServiceProxy1(UserService userService){
this.userService = userService;
}
public void createUser() {
userService.createUser();
System.out.println("========后置扩展功能======");
}
}
public class Application {
public static void main(String[] args) {
UserService userService = new UserServiceProxy1(new UserServiceProxy(new UserServiceImpl()));
userService.createUser();
}
}
1.2 动态代理
JDK动态代理:(要求:目标类必须有接口)
public interface UserService {
public void createUser();
}
public class UserServiceImpl implements UserService{
public void createUser() {
System.out.println("执行创建用户业务逻辑");
}
}
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;//目标对象
private ProxyInvocationHandler(Object target){
this.target = target;
}
/**
* 在invoke()方法对目标方法进行增强
* @param proxy 代理类对象
* @param method 目标方法对象
* @param args 目标方法实参
* @return 目标方法运行后返回值
* @throws Throwable 目标方法抛出的异常
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//前置功能
System.out.println("=====" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()) +"=========");
Object ret = method.invoke(target, args);//调用目标方法,相当于环绕通知的ProceedingJoinPoint.proceed()
return ret;
}
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyInvocationHandler invocationHandler = new ProxyInvocationHandler(userService);
//动态创建代理类,传入三个参数:类加载器、实现的接口对象、invocationHandler
UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(),
userService.getClass().getInterfaces(),
invocationHandler);
userServiceProxy.createUser();
}
}
JDK动态代理解析:
当执行Proxy.newProxyInstance(。。)语句后:本地硬盘会生成一个$Proxy().class文件
1、确定生成的代理类的包在 :com.sun.proxy下 。
2、确定代理类的类名为(不出现重复的情况下):com.sun.proxy.$Proxy0
3、确定代理类的类名:$Proxy()生成代理类的代码ProxyGenerator.generateProxyClass
语句执行会发生会执行相当于以下的伪代码:
public class $Proxy0 implements UserService(
private UserService targetObject;
public void createUser(){
System.out.println("........");//前置通知等方法以及下面的接口中的方法都会被搬入这个同名方法中
targetObject.createUser();
}
)
4、执行 defineClass0 方法,这些字节码文件会通过被代理类的类加载器载入到JVM的方法区中(heap ),保存的是字节码解析以后的这些与类的描述的定义信息。
5、类被加载以后会执行new $Proxy0()方法创建对象,对象被保存到jvm的堆中。
然后执行代理对象userServiceProxy.createUser()方法,也就是包括前置通知的方法。
CGLib实现代理类: (不需要接口)
CGLib是运行时字节码增强技术,Spring AOP扩展无接口类使用CGLib,AOP会运行时生成目标继承类字节码的方式进行行为扩展。