概念
原来的方法写法
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表示执行到指定切入点 访问
修饰符:public,private等,可以省略
异常名:方法定义中抛出指定异常,可以省略
下面两种都行:
execution(void com.xxz.dao.BookDao.update())
execution(void com.xxz.dao.impl.BookDaoImpl.update())
表达式使用通配符:
* :指代文件目录的一层,或一个元素,也可以用来匹配前后缀。
需要注意的是方法参数中要是写了*,那就是匹配的任意参数的,没参数不行
execution(public * com.xx.*.UserService.find*(*))
.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public 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对象地址。
}
}