1.参考
官网:Declaring a pointcut
参考:SpringAOP切入点详解
Spring AOP四种实现方式Demo详解与相关知识探究,参考博文,点击这里
1.内容:SpringAOP中的Joinpoint的相关用法:
参考博文2
2.SpringAOP中的@Before和@After执行的顺序:
2.环绕通知
Spring-Boot+AOP+统计单次请求方法的执行次数和耗时情况
只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint
- 1) 目标方法的调用由环绕通知决定,即你可以决定是否调用目标方法,而前置和后置通知 是不能决定的,他们只是在方法的调用前后执行通知而已,即目标方法肯定是要执行的。
- ProceedingJoinPoint 执行proceed方法的作用是让目标方法执行,类似于代理类中的invoke()
- 2) 环绕通知可以控制返回对象,即你可以返回一个与目标对象完全不同的返回值,虽然这很危险,但是你却可以办到。而后置方法是无法办到的,因为他是在目标方法返回值后调用
当方法符合切点规则不符合环绕通知的规则时候,执行的顺序如下
@Before→@After→@AfterRunning(如果有异常→@AfterThrowing)
当方法符合切点规则并且符合环绕通知的规则时候,执行的顺序如下
@Around→@Before→@After→@Around执行 ProceedingJoinPoint.proceed() 之后的操作→@AfterRunning(如果有异常→@AfterThrowing)
3.声明切入点
声明一个切入点包含两部分:切入点表达式和切入点签名
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
1、execution - for matching method execution join points, this is the primary pointcut designator you will use when working with Spring AOP
常用的切入点表达式:@Pointcut("execution(xxx)")
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.liaoxiang.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略(不写表示所有,但是不能使用通配符)
void com.liaoxiang.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.liaoxiang.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.liaoxiang.service.impl.*.*(..)
| public * *(..)
|任何公共方法的执行 |
| execution(* set*(..))
|以set开头的任何方法 |
| execution(* com.xyz.service.AccountService.*(..))
|AccountService接口定义的所有方法的执行 |
| execution(* com.xyz.service.*.*(..))
|service包下定义的所有方法的执行 |
| execution(* com.xyz.service..*.*(..))
|service包及其子包下方法的执行 |
| * com.learn..IHelloService.*()
|learn包及所有子包下IHelloService接口中的任何无参方法 |
| * com.learn..IHelloService.*(*)
|learn包及所有子包下IHelloService接口的任何只有一个参数方法 |
| * com.learn..IHelloService+.*()
|learn包及所有子包下IHelloService接口及子类型的的任何无参方法 |
| * com.learn..IHelloService*.test*(java.util.Date)
|learn包及所有子包下IHelloService前缀类型的的以test开头的只有一个参数类型为java.util.Date的方法,注意该匹配是根据方法参数类型进行匹配的,而不是根据执行时传入的参数类型决定的,如定义方法:public void test(Object obj);即使执行时传入java.util.Date,也不会匹配的; |
简单的一个测试方法:
@Repository
public class UserDao {
public void save(){
System.out.println("保存成功");
}
public String findName(){
System.out.println("查询名字");
return "张三";
}
public void update(Integer id){
System.out.println("根据id更新");
}
}
@Configuration
@EnableAspectJAutoProxy
@ComponentScan({"com.wuhuafeng"})
public class AppConfig {
}
@Component
@Aspect
public class Aspectj {
@Pointcut("execution(* com.wuhuafeng.dao.*.*(..))")
public void pointCut(){
System.out.println("日志-正在操作");
}
@Before("execution(* com.wuhuafeng.dao.*.*(..))")
public void before(){
System.out.println("日志-before");
}
@After("execution(* com.wuhuafeng.dao.*.*(..))")
public void after(){
System.out.println("日志-After");
}
}
public class test {
public static void main(String[] args) {
AnnotationConfigApplicationContext AC = new AnnotationConfigApplicationContext(AppConfig.class);
UserDao dao = (UserDao)AC.getBean("userDao");
dao.findName();
dao.update(1);
dao.save();
System.out.println("----------");
Skill skill =(Skill)dao;
skill.getSkill("我很牛逼");
}
}
日志-before
查询名字
日志-After
日志-before
根据id更新
日志-After
日志-before
保存成功
日志-After
@DeclareParents 为对象添加方法,为已生成的对象添加新的 方法。
(1.)定义一个名为 Skill 的接口及它的实现类 SkillImpl。我们将要把 SkillImpl 的getSkill()方法添加到UserDao的实列dao上。
@Component
public interface Skill {
public void getSkill(String skill);
}
@Component
public class SkillImpl implements Skill {
public void getSkill(String skill) {
System.out.println(skill);
}
}
@Component
@Aspect
public class Aspectj {
@DeclareParents(value = "com.wuhuafeng.dao.*",defaultImpl = SkillImpl.class)
public Skill skill; //添加的方法是这个接口里面的
//这里的Value:表示对哪里的类的实列,添加一个新的方法。
defaultImpl:添加方法的默认实现在SkillImpl中。
....//和上面的一样。
public class test {
public static void main(String[] args) {
AnnotationConfigApplicationContext AC = new AnnotationConfigApplicationContext(AppConfig.class);
UserDao dao = (UserDao)AC.getBean("userDao");
dao.findName();
dao.update(1);
dao.save();
System.out.println("----------");
Skill skill =(Skill)dao;// 通过类型转换,dao对象就拥有了SkillImp 类的方法
skill.getSkill("我很牛逼");
}
}
日志-before
查询名字
日志-After
日志-before
根据id更新
日志-After
日志-before
保存成功
日志-After
----------
我很牛逼