e.g. 在接口执行前输出当前系统时间
导入关键坐标
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
制作连接点方法(原始操作,Dao接口与实现类)
@Repository
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("save book");
};
@Override
public void update() {
System.out.println("update book");
};
};
制作共性功能(通知类与通知)
public void before() {
System.out.println(System.currentTimeMillis());
};
定义切入点
@Pointcut("execution(void com.cikian.dao.BookDao.update())")
private void pt() {
};
说明:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑
绑定切入点与通知关系(切面)
@Compoent
@Aspect
@Before("pt()")
public void before() {
System.out.println(System.currentTimeMillis());
};
使用
@Compoent
注解定义通知类受Spring容器管理,使用@Aspect
注解告知Spring将此类以AOP方式处理 在Spring配置类中: @Configuration @ComponentScan("com.cikian") c public class SpringConfig { }; 使用@EnableAspectJAutoProxy
注解开启Spring对AOP注解驱动支持
AOP核心概念
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
切入点表达式标准格式: 动作关键字 (访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
,如:
execution(User com.cikian.dao.BookDao.update(int))
其中:
可以使用通配符描述切入点,快速描述
execution (public * com.cikian.*.BookDao.find*(*))
匹配com.cikian包下的任意包中的BookDao类或接口中所有find开头的带有一个参数的方法
execution (public User com..UserService.findById (..)
匹配com包下的任意包中的UserService类或接口中所有名称为findByld的方法
execution(* *..*Service+.*(..))
所有代码按照标准规范开发,否则以下技巧全部失效
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
AOP通知共分为5种类型:
前置通知
名称:@Before
类型:方法注解
位置:通知方法定义上方
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
示例:
@Before("pt()")
public void before() {
System.out.println("before advice");
};
相关属性:value(默认):切入点方法名,格式为类名.方法名( )
后置通知
名称:@After
类型:方法注解
位置:通知方法定义上方
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
示例:
@After("pt()")
public void after() {
System.out.println("after advice");
};
相关属性:value(默认):切入点方法名,格式为类名.方法名( )
环绕通知(重点)
名称:@Around
类型:方法注解
位置:通知方法定义上方
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
示例:
@Around("pt()")
public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("around before advice");
Object o = pjp.proceed();
System.out.println("around after advice");
return o;
};
注意事项
返回后通知(了解)
名称:@AfterReturning
类型:方法注解
位置:通知方法定义上方
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
示例:
@AfterReturning("pt()")
public void afterReturning() {
System.out.println("afterReturning advice");
};
相关属性:value(默认):切入点方法名,格式为类名.方法名( )
抛出异常后通知(了解)
名称:@AfterThrowing
类型:方法注解
位置:通知方法定义上方
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
示例:
@AfterThrowing("pt()")
public void afterThrowing() {
System.out.println("afterThrowing advice");
};
相关属性:value(默认):切入点方法名,格式为类名.方法名( )
获取切入点方法的参数
示例:
@Pointcut("execution(* com.cikian.dao.*Dao.find(..))")
public void pt() {
};
@Before("pt()")
public void before(JoinPoint jp) {
// 获取参数
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
};
@After("pt()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
};
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0] = 2;
return pjp.proceed(args);
};
环绕通知获取参数后,可对参数进行修改后在调用proceed( )方法时将修改后的参数数组作为参数传入
获取切入点方法返回值
返回后通知
@AfterReturning(value = "pt()", returning = "ret")
public void afterReturning(JoinPoint jp, Object ret) {
System.out.println("原始返回值:" + ret);
};
在
@AfterReturning(value = "pt()", returning = "ret")
注解中,需要另外一个参数returning,值与afterReturning方法的形参名相同。 若在afterReturning方法中有多个参数,JoinPoint参数必须放在第一位
环绕通知
proceed( )方法返回值即为原始方法返回值
获取切入点方法运行异常信息
抛出异常后通知
@AfterThrowing(value = "pt()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e) {
System.out.println("异常信息:"+e);
};
在
@AfterThrowing(value = "pt()", throwing = "e")
注解中,需要另外一个参数throwing,值与afterThrowing方法的形参名相同。 若在afterThrowing方法中有多个参数,JoinPoint参数必须放在第一位
环绕通知
将proceed( )方法使用try…catch…环绕即可对异常信息进行捕获、处理
AccountService接口:
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findById(Integer id);
};
AccountService接口实现类:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void save(Account account) {
accountDao.save(account);
};
@Override
public void delete(Integer id) {
accountDao.delete(id);
};
@Override
public void update(Account account) {
accountDao.update(account);
};
@Override
public List<Account> findAll() {
return accountDao.findAll();
};
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
};
};
AccountDao:
public interface AccountDao {
@Insert("insert into tb_account(name,money) values(#{name};,#{money};)")
void save(Account account);
@Delete("delete from tb_account where id=#{id};")
void delete(Integer id);
@Update("update tb_account set name=#{name};,money=#{money}; where id=#{id};")
void update(Account account);
@Select("select * from tb_account")
List<Account> findAll();
@Select("select * from tb_account where id=#{id};")
Account findById(Integer id);
};
Account:
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
};
public void setId(Integer id) {
this.id = id;
};
public String getName() {
return name;
};
public void setName(String name) {
this.name = name;
};
public Float getMoney() {
return money;
};
public void setMoney(Float money) {
this.money = money;
};
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name=" + name +
", money=" + money +
'};';
};
};
通知类Advice:
@Component
@Aspect
public class Advice {
@Pointcut("execution(* com.cikian.service.*Service.*(..))")
public void pointcut() {
};
@Around("pointcut()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String metName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
pjp.proceed();
};
long end = System.currentTimeMillis();
System.out.println(className + "接口的" + metName + "方法 " + "运行万次时间为:" + (end - start) + "毫秒");
};
};
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById() {
accountService.findById(1);
};
@Test
public void testFindAll() {
accountService.findAll();
};
};
需求:对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理
分析:
实现:
ResourcesService接口:
public interface ResourcesService {
boolean openURL(String url,String password);
};
ResourcesService接口实现类:
@Service
public class ResourcesServiceImpl implements ResourcesService {
@Autowired
private ResourcesDao resourcesDao;
@Override
public boolean openURL(String url, String password) {
return resourcesDao.readResources(url, password);
};
};
ResourcesDao接口:
public interface ResourcesDao {
boolean readResources(String url, String password);
};
ResourcesDao接口实现类:
public class ResourcesDaoImpl implements ResourcesDao {
@Override
public boolean readResources(String url, String password) {
//模拟校验
return password.equals("abcd");
};
};
通知类:
@Component
@Aspect
public class DataAdvice {
@Pointcut("execution(* com.cikian.service.*Service.*(..))")
private void servicePT() {
};
@Around("servicePT()")
public Object trimString(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
for (int i = 0; i < args.length; i++) {
if (args[i].getClass().equals(String.class)) {
args[i] = args[i].toString().trim();
};
};
Object re = pjp.proceed(args);
return re;
};
};