一、为什么使用AOP
Aop是面向对象的补充,当我们需要为多个对象引入一个公共行为,比如日志,操作记录等,就需要在每个对象中引用公共行为,这样程序就产生了大量的重复代码,使用AOP可以完美解决这个问题。
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
Aop的作用:在程序运行期间,不修改源码对已有方法进行增强。
Aop优势:减少重复代码、提高开发效率、维护方便
AOP的实现方式:使用动态代理技术
二、创建AOP过程
2.1新建Maven项目
2.2添加pom文件依赖
<dependencies>
<!--spring坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
<!--解析切入点表达式-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.9.RELEASE</version>
</dependency>
</dependencies>
刷新pom文件
2.3创建SpringConfig文件
package com.test.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
//用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解
@ComponentScan({"com"})//用于指定 spring 在初始化容器时要扫描的包
@EnableAspectJAutoProxy//开启动态代理
public class SpringConfig {
}
2.4创建UserServer
package com.test.service;
import org.springframework.stereotype.Service;
@Service
public class UserServer {
public void saveUser(){
System.out.println("保存用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
public void updateUser(){
System.out.println("更新用户");
}
2.5创建切面类
package com.test.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component//交给ioc容器
@Aspect//定义为切面类
public class LoggerAspect {
@Pointcut("execution(* com.test.*.*.*(..))")
public void pointcut() {
}
@Before("pointcut()")
public void doBefore(){
System.out.println("**********运行前面记录日志**********");
}
@ @AfterReturning("pointcut()")
public void doAfter(){
System.out.println("**********运行后面记录日志**********");
}
}
@Aspect注解会将此类当做是切面类
定义为切面类后,在其中创建一个切面方法,其中@Pointcut("execution(* com.test.*.*.*(..))")
是标注我的切面在哪里的,固定格式,excution中的com.test.*.*.*(..)表示我切入点的表达式。无参有参构造方法都囊括其中
@Befor是代表我程序运行之前我需要的一些操作
@AfterRunning代表我程序运行结束后的操作
2.4定义测试类
package com.test.contrall;
import com.test.configuration.SpringConfig;
import com.test.service.UserServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserServer us = (UserServer) context.getBean("userServer");
us.saveUser();
us.updateUser();
us.deleteUser();
}
}
2.5运行结果展示
三、相关知识点补充
3.1切入点表达式的写法-execution函数
关键字:execution(表达式)
表达式:访问修饰符 返回值 包名.包名.包名...类名.方法名
规则 | 表达式 | 示例 |
匹配所有类的public方法 | execution(public **(..)) | |
匹配指定包下所有类的方法(不包含子包) | execution(* 包名.*(..)) | execution(* test.aop.t1.*(..)) |
匹配指定包下所有类的方法(包含子包) | execution(* 包名.**(..)) | execution(* test.aop.t1..*(..)) |
匹配指定类下的所有方法 | execution(* 包名.类名.*(..)) | execution(* test.aop.t1.UserDao.*(..)) |
匹配指定类下的指定方法 | execution(* 包名.类名.方法名(..)) | execution(* test.aop.t1.UserDao.add(..)) |
匹配实现指定接口的所有类的方法 | execution(* 包名.接口名+*(..)) | execution(* test.aop.t1.UserDao+*(..)) |
匹配所有名称以save开头的方法 | execution(* save*(..)) |
3.2 Spring中的通知
Spring中一共有5种通知
前置通知(before)、后置通知(returning)、异常通知(throwing)、最终通知(after)、环绕通知(around)
通知类型 | 作用 | 应用场景 |
前置通知 | 在目标方法执行前增强代码逻辑 | 权限控制(权限不足抛出异常)、记录方法调用信息日志 |
后置通知 | 在目标方法运行后返回值后再增强代码逻辑,在出现异常时不执行 | 与业务相关的,如银行在存取款结束后的发送短信消息 */ |
异常通知 | 目标代码出现异常,通知执行。记录异常日志、通知管理员(短信、邮件) | 处理异常(一般不可预知),记录日志 |
最终通知 | 不管目标方法是否发生异常,最终通知都会执行(类似于finally代码功能) | 释放资源 (关闭文件、 关闭数据库连接、 网络连接、 释放内存对象 ) |
环绕通知 | 目标方法执行前后,都进行增强(控制目标方法执行) | 日志、缓存、权限、性能监控、事务管理 |
四、练习
计算一千亿累加数字,并使用环绕通知输出计算时间
comfiguration类
package com.demo02.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com"})
public class configuration {
}
切面类
package com.test.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component//交给ioc容器
@Aspect//定义为切面类
public class LoggerAspect {
@Pointcut("execution(* com.test.*.*.*(..))")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point){
Object result = null;
//执行前通知
long beginTime = new Date().getTime();
//执行要增强的函数
//获取目标函数中的参数
Object[] args = point.getArgs();
try {
result = point.proceed(args);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//执行后通知
long endTime = new Date().getTime();
System.out.println("**********执行时间:"+(endTime-beginTime)+"毫秒**********");
return result;
}
}
创建server层
package com.test.service;
import org.springframework.stereotype.Service;
@Service
public class UserServer {
public void doSum(){
long sum=0;
long xx=1000000000+1;
sum = xx * (1000000000 / 2);
System.out.println(sum);
}
}
创建测试类
package com.test.contrall;
import com.test.configuration.SpringConfig;
import com.test.service.UserServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserServer us = (UserServer) context.getBean("userServer");
us.doSum();
}
}
运行结果