spring系列 SpringAOP
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
概念
原来的方法写法
public void save(){
Long startTime = System.currentTimeMillis();
System.out.println(“book dao save ...”);
Long endTime = System.currentTimeMillis();
Long totalTime = endTime-startTime;
System.out.println("方法耗时:" + totalTime + "ms");
}
通知
把多个方法耦合的部分抽取出来就叫做通知设置通知的类就叫做通知类
public void method(){
Long startTime = System.currentTimeMillis();
//调用原始操作
Long endTime = System.currentTimeMillis();
Long totalTime = endTime-startTime;
System.out.println("方法耗时:" + totalTime + "ms");
}
@Component
@Aspect
public class MyAdvice {
@Pointcut("execution(void com.itheima.dao.BookDao.update())")
private void pt(){}
@Pointcut("execution(int com.itheima.dao.BookDao.select())")
private void pt2(){}//@Before前置通知在原始方法运行之前执行
// @Before("pt()")
public void before() {
System.out.println("before advice ...");
}//@After后置通知在原始方法运行之后执行原方法就算抛异常也会执行
// @After("pt2()")
public void after() {
System.out.println("after advice ...");
}//@Around环绕通知在原始方法运行的前后执行
//借助ProceedingJoinPoint pjp可以调用原来的方法从而达到对原来方法返回值的处理如果原来方法有返回值而这里没有虽然也会执行但是如果我们想通过代码获取返回值的话就会报错。
//这里如果ProceedingJoinPoint pjp没有那就会跳过原始方法执行相应通知可以用来做校验。
//这里的规范是用Object这样即使是个null也能接。
//调用原始方法的时候会提示我们要抛异常throws Throwable
// @Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {//获取原方法的参数实际值
//Object[] args = pjp.getArgs();
//Object ret = pjp.proceed(args);//在拿到原方法参数值后可以加一些校验逻辑不需要操作的话不用传参数也可以
//System.out.println(Arrays.toString(args));
//获取执行的签名对象
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName(); //类路径和类名
String methodName = signature.getName(); //方法名
System.out.println("around before advice ...");
//表示对原始操作的调用
Object ret = pjp.proceed();
System.out.println("around after advice ...");System.out.println("类方法"+ className+"."+methodName+"执行");
return ret;
}// @Around("pt2()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice ...");
//表示对原始操作的调用
Integer ret = (Integer) pjp.proceed();
System.out.println("around after advice ...");
return ret;
}//@AfterReturning返回后通知在原始方法执行完毕后运行且原始方法执行过程中没有抛异常现象才会执行执行时机在@after之前
//如果要操作原始方法的返回值。注解值要改成这样(value = "pt()",returning = "ret")然后下面方法写一个形参形参名要和上面的去接returning 值一样 具体类型可以自己根据情况选择。当ProceedingJoinPoint或JoinPoint和我们的返回参数一起被定义的为形参的时候JoinPoint必须在最前面不然会报illegalArgumentException。
// @AfterReturning("pt2()")
public void afterReturning() {
System.out.println("afterReturning advice ...");
}//@AfterThrowing抛出异常后通知在原始方法执行过程中如果出现异常后就会运行
//要获取异常对象的话(value = "pt()",throwing = "t")下面的参数改成Throwable t
@AfterThrowing("pt2()")
public void afterThrowing() {
System.out.println("afterThrowing advice ...");
}
}
连接点
这种实际的方法就是一个个的连接点
public void update(){
System.out.println("book dao update ...");
}
public void delete(){
System.out.println("book dao delete ...");
}
public void select(){
System.out.println("book dao select ...");
}
切入点
指定要具体操作连接点的就是切入点。
代码中指要进行增强的方法。
切入点表达式
切入点表达式标准格式
动作关键字访问修饰符 返回值 包名.类/接口名.方法名参数异常名
动作关键字描述切入点的行为动作例如execution表示执行到指定切入点 访问
修饰符publicprivate等可以省略
异常名方法定义中抛出指定异常可以省略
下面两种都行
execution(void com.xxz.dao.BookDao.update())
execution(void com.xxz.dao.impl.BookDaoImpl.update())
表达式使用通配符
* 指代文件目录的一层或一个元素也可以用来匹配前后缀。
需要注意的是方法参数中要是写了*那就是匹配的任意参数的没参数不行
executionpublic * com.xx.*.UserService.find**
.. 多个连续的任意符号可以独立出现常用于简化包名与参数的书写
executionpublic User com..UserService.findById..
+用来匹配子类类型
execution(* *..*Service+.*(..))
切入点通常描述接口而不描述实现类因为实现类更可能会随需求改变耦合度更小。
返回值类型对于增删改类使用精准类型匹配而查询方法的返回值往往有多种数据结构的对于查询类使用*通配快速描述。
包名书写使用..匹配时效率过低尽量不使用常用*做单个包描述匹配或精准匹配。
接口名/类名书写名称与模块相关的采用*匹配例如UserService书写成*Service绑定业务层接口名。
方法名书写以动词进行精准匹配具体名词可以采用*匹配例如getById书写成getBy*而selectAll可以直接书写成selectAll。
通常不使用异常作为匹配规则异常有相应的处理手段。。
切面
就是将通知和切入点联系起来的部分。
案例
导包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
配置类
@EnableAspectJAutoProxy开启注解开发AOP功能
@Configuration
@ComponentScan("com.xxz")
@EnableAspectJAutoProxy
public class SpringConfig {
}
要被代理的方法save和update
@Repository
public class BookDaoImpl implements BookDao {public void save() {
System.out.println("book dao save ...");
}public void update(){
System.out.println("book dao update ...");
}
}
通知类
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类
@Aspect
public class MyAdvice {
//设置切入点要求配置在方法上方这个方法要求无参无返回值无逻辑
@Pointcut("execution(void com.xxz.dao.BookDao.update())")
private void pt(){}//设置在切入点pt()的前面运行当前操作前置通知
@Before("pt()")
public void method(){
System.out.println(System.currentTimeMillis());
}
}
启动类
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
// bookDao.update();
System.out.println(bookDao);
System.out.println(bookDao.getClass());//当方法名不对应目标时比如void com.xxz.dao.BookDao.update()写成void com.xxz.dao.BookDao.updateD()对象还是自身。而经过代理的由于重写了toString方法会打出一个proxy对象地址。
}
}