aop学习笔记之Pointcut Express和Advice:http://blog.csdn.net/zyb2017/article/details/79444476
本篇代码下载地址:https://download.csdn.net/download/zyb2017/10277492
一 环境
开始解决之前先来搭建一个测试的环境,MAVEN+Spring-Boot,版本为1.5.10,因为要使用AOP所以在pom里添加aop的依赖
<!--spring aop 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
刷新Maven后看下External Libraries中是否有我们要的包
二 普通写法
测试程序为管理员对产品进行删除或添加操作,操作之前要验证管理员身份,不是admin则抛出异常
2.1 产品类
package com.march.aop.domain;
/**
* Product class
*
* @author TransientBa
* @date 2018/03/01
*/
public class Product {
private Long id;
private String name;
public Long getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public void setId(Long id) { this.id = id; }
}
2.2 持有校验
package com.march.aop.security;
/**
* CurrentUserHolder class
*
* @author TransientBa
* @date 2018/03/01
*/
public class CurrentUserHolder {
//维护多线程间局部变量
private static final ThreadLocal<String> holder = new ThreadLocal<>();
public static String get(){
//holder.get() 返回局部变量的当前线程值
return holder.get() == null ? "unknown" : holder.get();
}
public static void set(String user){
holder.set(user);
}
}
2.3 身份验证Service
package com.march.aop.service;
import com.march.aop.security.CurrentUserHolder;
import org.springframework.stereotype.Component;
/**
* AuthService class
*
* @author TransientBa
* @date 2018/03/01
*/
@Component
public class AuthService {
public void checkAccess(){
String user = CurrentUserHolder.get();
String role = "admin";
//若当前用户不是admin则抛出异常不允许操作
if(!role.equals(user)){
throw new RuntimeException("operation not allow");
}
}
}
2.4 产品Service
package com.march.aop.service;
import com.march.aop.domain.Product;
import com.march.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ProductService class
*
* @author TransientBa
* @date 2018/03/01
*/
@Service
public class ProductService {
@Autowired
AuthService authService;
public void insert(Product product){
//插入之前进行身份校验
authService.checkAccess();
System.out.println("insert product");
}
public void delete(Long id){
authService.checkAccess();
System.out.println("delete product");
}
}
2.5 测试启动类
package com.march.aop;
import com.march.aop.security.CurrentUserHolder;
import com.march.aop.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
ProductService productService;
@Test(expected = Exception.class)
public void annoInsertTrst() {
CurrentUserHolder.set("TransientBa");
productService.delete(1L);
}
@Test
public void adminInsert(){
CurrentUserHolder.set("admin");
productService.delete(1L);
}
}
第一个annoInsertTrst方法因为插入的管理员叫TransientBa而不是admin,理论上会跑抛异常。我们加上expected = Exception.class,运行测试:
我们看到测试是通过了的。
第二个adminInsert插入了admin,理论上不会抛异常,直接运行测试:
通过
三 aop织入
以aop范式的规则织入,来替代产品Service中的调用
3.1 管理员注解
我们用注解来控制织入的位置,创建管理员注解
package com.march.aop.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* class
*
* @author TransientBa
* @date 2018/3/1
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {
}
自定义注解的方法可查看我的另一片文章:
@interface 自定义注解:http://blog.csdn.net/zyb2017/article/details/78827525
3.2 @Aspect
package com.march.aop.security;
import com.march.aop.service.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* SecurityAspect class
*
* @author TransientBa
* @date 2018/3/1
*/
@Aspect
@Component
public class SecurityAspect {
@Autowired
AuthService authService;
/**切入点为带有AdminOnly注解的方法**/
@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){
}
/**在adminOnly方法执行前 做checkAccess的校验**/
@Before("adminOnly()")
public void check(){
authService.checkAccess();
}
}
同时通过@Component注解将SecurityAspect 类交给Spring进行托管
3.3 修改产品Service
取消原本的用户校验,改为应用@AdminOnly注解来标注接入点
package com.march.aop.service;
import com.march.aop.domain.Product;
import com.march.aop.security.AdminOnly;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* ProductService class
*
* @author TransientBa
* @date 2018/03/01
*/
@Service
public class ProductService {
@Autowired
AuthService authService;
@AdminOnly
public void insert(Product product){
System.out.println("insert product");
}
@AdminOnly
public void delete(Long id){
System.out.println("delete product");
}
}
3.4 测试
回到DemoApplicationTest,进行测试
运行结果如下
annoInsertTrst:
adminInsert:
四 总结
以aop的方式解决一些非功能性的需求,例如日志、事务、异常等。可以很好的将非业务性的需求拿出来,减少代码的侵入性,当我们修改时做到不影响原本的业务逻辑。