背景:

  当需要为多个不具有继承关系的对象引入一个公共行为,例如日志、权限验证、事务等功能时。如果使用OOP,需要为每个Bean引入这些公共

行为。会产生大量重复代码,并且不利用维护,AOP就是为了解决这个问题。

AOP:

  就是上面的解释,可以理解一种思想,不是Java独有的,作用是对方法进行拦截处理或增强处理。而在Java中我们使用Spring AOP和AspectJ。

Spring AOP:

  基于动态代理实现,如果目标对象有实现接口,使用jdk proxy,如果目标对象没有实现接口,使用cglib。然后从容器获取代理后的对象,

在运行期植入“切面”类的方法。Spring AOP需要依赖于IOC容器来管理,只能作用于Spring容器中的Bean。

  Spring AOP可以使用注解或者XML配置的方式,而类似@Aspect、@Pointcut等都是AspectJ的注解,但是通过Spring去实现,只是沿用AspectJ

的概念。理论上,Spring AOP足够日常开发,一般使用不到AspectJ。Spring AOP在运行时生成代理对象来织入的,还可以在编译期、类加载期织入

,比如AspectJ。

AspectJ:

  AspectJ在实际代码运行前完成了织入,所以它生成的类是没有额外运行时开销的。而Spring AOP基于动态代理,生成一个代理类,这样栈深度

更深,效率理论上要差于AspectJ。AspectJ功能更强大,是AOP的完整解决方案。

  AspectJ除了注解,个人不太了解,这里就不细讲了。

  动态代理请参考:https://www.cnblogs.com/huigelaile/p/10980045.html

Spring AOP应用:

  1、Controller层的参数校验:参考Spring AOP拦截Controller做参数校验

  2、使用Spring AOP实现MySQL数据库读写分离案例分析

  3、在执行方法前,判断是否具有权限

  4、对部分函数的调用进行日志记录:监控部分重要函数,若抛出指定的异常,可以以短信或邮件方式通知相关人员

  5、信息过滤,页面转发等等功能

Spring AOP术语:

  在一个或多个连接点上,可以把切面的功能(通知)织入到程序的执行过程中

1、增强Advice:

  方法层面的增强。对某个方法进行增强的方法,分为:Before、After、After-returning、After-throwing、Around。

try {
//@Before
result = method.invoke(target, args);
//@After
return result;
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
//@AfterThrowing
throw targetException;
} finally {
//@AfterReturning
}

2、连接点Join Point:

  可以被拦截到的点。也就是可以被增强的方法都是连接点。

2、切点Pointcut:

  joint point的组合,通常使用类和方法名称或者正则表达式匹配来指定切点。

3、切面Aspect:

  切面是Advice和Pointcut的结合,他们共同定义了在何时和何处完成其功能。

4、引入Introduction:

  向现有的类添加新方法或属性。

5、织入Weaving:

  把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命里有多个点可以进行织入:

编译器、类加载期、运行期。

6、目标对象Target:

  织入 Advice 的目标对象.。

Spring对AOP的支持:由于基于动态代理实现,所以只支持方法级别的连接点

  1、基于代理的经典Spring AOP:虽然带有经典二字,但是现在看来这种方式已经有点捞。。。

  2、纯POJO切面

  3、@AspectJ注解驱动的切面

  4、注入式AspectJ切面(适用于Spring各版本)

Spring AOP实现:

1、添加Maven依赖:

  1).aspectjweaver

  2).如果使用Spring Boot:spring-boot-starter-aop

2、首先开启Spring AOP支持

XML方式:<aop:aspectj-autoproxy/>

注解方式:@EnableAspectJAutoProxy,所有被@aspect配置的Bean,都是Aspect

3.1、基于XML(schema-based)

Spring AOP要使用AspectJ的切点表达式定义切点:

@execution:上面使用了execution来正则匹配方法,是最常用的。也可以使用其他的指示器。

execution表达式以*开始,表示不关心方法返回值的类型,两个点号(..)表名切点要选择任意的perform()方法,无论方法的参数是什么。

@within:指定所在类或所在包下面的方法

例如:@Pointcut("within(com.it.aop.AService..*)")

@annotation:方法上具有特定的注解,如@Subscribe用于订阅特定的事件。

例如:@Pointcut("execution(* .(..)) && @annotation(com.javadoop.annotation.Subscribe)")

@bean:匹配bean的名字

例如:@Pointcut("bean(*Service)")

PS:通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。

举个栗子:

public class AService {

    public void add() {
System.out.println("add");
}
}
public class LogRecord {

    public void log() {
System.out.println("record log");
} public void transaction() {
System.out.println("transaction");
} public void permission() {
System.out.println("permission");
}
}
<aop:config>    <!--顶层的AOP配置元素。大多数aop元素都在这内部 -->
<!--声明一个切面 -->
<aop:aspect ref="logRecord">
<!--前置通知 -->
<aop:before pointcut="execution(** com.it.aop.AService.add(..))" method="log" />
<aop:before pointcut="execution(** com.it.aop.AService.add(..))" method="permission" />
<!--返回通知 -->
<aop:after-returning pointcut="execution(** com.it.aop.AService.add(..))" method="log" />
<!--异常通知 -->
<aop:after-throwing pointcut="execution(** com.it.aop.AService.add(..))" method="transaction" />
</aop:aspect>
</aop:config>

上述代码中,Pointcut都是相同的我们就可以声明<aop:pointcut>,如果把<aop:pointcut>作为<aop:config>的直接子元素,将作为全局Pointcut

<aop:config>    <!--顶层的AOP配置元素。大多数aop元素都在这内部 -->
<!--声明一个切面 -->
<aop:aspect ref="logRecord">
<aop:pointcut id="add" expression="execution(** com.it.aop.AService.add(..))" />
<aop:before pointcut-ref="add" method="log" />
</aop:aspect>
</aop:config>

环绕通知

public void around(MethodInvocationProceedingJoinPoint point) {
try {
System.out.println("record log");
System.out.println("permission");
point.proceed();
System.out.println("record log");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
<aop:config>    <!--顶层的AOP配置元素。大多数aop元素都在这内部 -->
<!--声明一个切面 -->
<aop:aspect ref="logRecord">
<aop:pointcut id="add" expression="execution(** com.it.aop.AService.add(..))" />
<!--环绕通知 -->
<aop:around pointcut-ref="add" method="around" />
</aop:aspect>
</aop:config>

3.2、基于注解(@AspectJ)实现:

  @AspectJ和AspectJ没多大关系,仅仅是使用了AspectJ中的概念,注解来自于AspectJ的包,但是实现还是Spring AOP来的。

@Aspect
public class LogRecord { @Pointcut("execution(** com.it.aop.AService.add(..))")
public void add() {} @Before("add()")
public void log() {
System.out.println("record log");
}
@AfterThrowing("add()")
public void transaction() {
System.out.println("transaction");
}
@Before("add()")
public void permission() {
System.out.println("permission");
}
@Around("add()")
public void around(MethodInvocationProceedingJoinPoint point) {
try {
System.out.println("record log");
System.out.println("permission");
point.proceed();
System.out.println("record log");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}

PS:

  Spring通常建议创建一个SystemArchitecture类,里面定义Pointcut,然后在需要的地方去引用。例如,在@Aspect的Bean中使用@Before

("com.it.SystemArchitecture.A")

Spring AOP通过@annotation实现权限控制

PS:这里只是校验部分API的登录状态和用户权限,如果系统要求登录过后才能请求,肯定就选择拦截器了。

1、首先定义两个注解

//@CheckLogin通过Cookie是否包含X-Token验证用户是否登录
public @interface CheckLogin { } //@CheckAuthorization("**")校验用户是否登录,权限**是否满足
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckAuthorization {
String value();
}

2、注解的AOP处理

/* *
* Description: 通过校验jwt中token实现登录和用户权限校验
**/
@Aspect //定义为一个切面
@Component //必须声明为Bean
@RequiredArgsConstructor(onConstructor = @__(@Autowired)) //lombok实现IOC,相比直接通过@Autowired更有优势
public class AuthAspect { private final JwtOperator jwtOperator; //jwt操作类 //@CheckLogin注解操作
@Around("@annotation(com.diamondshine.auth.CheckLogin)")
public Object checkLogin(ProceedingJoinPoint point) throws Throwable {
checkToken();
return point.proceed();
} private void checkToken() {
try {
// 1. 从header里面获取token
HttpServletRequest request = getHttpServletRequest(); Cookie[] cookies = request.getCookies();
String token = "";
for (Cookie cookie : cookies) {
if (StringUtils.equals("X-Token", cookie.getName())) {
token = cookie.getValue();
break;
}
} // 2. 校验token是否合法&是否过期;如果不合法或已过期直接抛异常;如果合法放行
Boolean isValid = jwtOperator.validateToken(token);
if (!isValid) {
throw new SecurityException("Token不合法!");
} // 3. 如果校验成功,那么就将用户的信息设置到request的attribute里面
Claims claims = jwtOperator.getClaimsFromToken(token);
request.setAttribute("id", Long.valueOf(claims.get("id").toString()));
request.setAttribute("role", claims.get("role"));
} catch (Throwable throwable) {
throw new SecurityException("Token不合法!");
}
} private HttpServletRequest getHttpServletRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes attributes = (ServletRequestAttributes) requestAttributes;
return attributes.getRequest();
} //@CheckAuthorization注解操作
@Around("@annotation(com.diamondshine.auth.CheckAuthorization)")
public Object checkAuthorization(ProceedingJoinPoint point) throws Throwable {
try {
// 1. 验证token是否合法;
this.checkToken();
// 2. 验证用户角色是否匹配
HttpServletRequest request = getHttpServletRequest();
List<String> list = (List<String>) request.getAttribute("role"); MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//获取@CheckAuthorization注解值
CheckAuthorization annotation = method.getAnnotation(CheckAuthorization.class); String value = annotation.value(); //ROLE_ADMIN,用户权限必须为admin。如果注解为ROLE_ADMIN_USER,用户权限为admin/user都可以
list.forEach(role -> {
if (!(("ROLE_ADMIN".equals(value) && "ROLE_ADMIN".contains(role)) || ("ROLE_ADMIN_USER".equals(value) && "ROLE_ADMIN_USER".contains(role)))) {
throw new SecurityException("用户无权访问!");
}
}); } catch (Throwable throwable) {
if (StringUtils.isNotBlank(throwable.getMessage())) {
throw new SecurityException(throwable.getMessage(), throwable);
} else {
throw new SecurityException("用户无权访问!", throwable);
}
}
return point.proceed();
}
}

PS:内部使用jwt获取token中的信息,这段不重要,根据要求去实现代码,参考这种AOP实现方式

3、简单使用

/* *
* Description: 用户必须登录,权限为ROLE_ADMIN_USER
**/
@CheckAuthorization("ROLE_ADMIN_USER")
@PostMapping(value = "/hahaha")
@ResponseBody
public CustomizeResponse subscribeHouse(@RequestParam(value = "house_id") Long houseId) {
//***
} /* *
* Description: 只需要用户登录状态
**/
@CheckLogin
@GetMapping("rent/house/show/{id}")
public String showHouseDetail(@PathVariable(value = "id") Long houseId, Model model) {
//***
}

4、SecurityException异常处理,返回json数据

@Slf4j
@RestControllerAdvice
public class GlobalExceptionErrorHandler {
@ExceptionHandler(SecurityException.class)
public ResponseEntity<ErrorBody> error(SecurityException e) {
log.warn("发生SecurityException异常", e);
return new ResponseEntity<>(
ErrorBody.builder()
.body(e.getMessage())
.status(HttpStatus.UNAUTHORIZED.value())
.build(),
HttpStatus.UNAUTHORIZED
);
}
} @Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ErrorBody {
private String body;
private int status;
}

如果权限不合法,页面返回

权限验证思路来自:面向未来微服务:Spring Cloud Alibaba从入门到进阶  第11章内容

参考:https://mp.weixin.qq.com/s/KOV_lWOTPYMi8-2kFZCS9Q

Spring框架系列(五)--面向切面AOP的更多相关文章

  1. Spring基础(二)_面向切面(AOP)

    面向切面编程 面向切面编程[AOP,Aspect Oriented Programming]:通过预编译方式和运行期间动态代理实现程序功能的统一维护的技术.AOP 是 Spring 框架中的一个重要内 ...

  2. Spring框架系列(4) - 深入浅出Spring核心之面向切面编程(AOP)

    在Spring基础 - Spring简单例子引入Spring的核心中向你展示了AOP的基础含义,同时以此发散了一些AOP相关知识点; 本节将在此基础上进一步解读AOP的含义以及AOP的使用方式.@pd ...

  3. Spring框架系列(9) - Spring AOP实现原理详解之AOP切面的实现

    前文,我们分析了Spring IOC的初始化过程和Bean的生命周期等,而Spring AOP也是基于IOC的Bean加载来实现的.本文主要介绍Spring AOP原理解析的切面实现过程(将切面类的所 ...

  4. Spring框架系列之AOP思想

    微信公众号:compassblog 欢迎关注.转发,互相学习,共同进步! 有任何问题,请后台留言联系! 1.AOP概述 (1).什么是 AOP AOP 为 Aspect Oriented Progra ...

  5. Spring框架系列(10) - Spring AOP实现原理详解之AOP代理的创建

    上文我们介绍了Spring AOP原理解析的切面实现过程(将切面类的所有切面方法根据使用的注解生成对应Advice,并将Advice连同切入点匹配器和切面类等信息一并封装到Advisor).本文在此基 ...

  6. Spring框架系列(11) - Spring AOP实现原理详解之Cglib代理实现

    我们在前文中已经介绍了SpringAOP的切面实现和创建动态代理的过程,那么动态代理是如何工作的呢?本文主要介绍Cglib动态代理的案例和SpringAOP实现的原理.@pdai Spring框架系列 ...

  7. Spring框架系列(12) - Spring AOP实现原理详解之JDK代理实现

    上文我们学习了SpringAOP Cglib动态代理的实现,本文主要是SpringAOP JDK动态代理的案例和实现部分.@pdai Spring框架系列(12) - Spring AOP实现原理详解 ...

  8. Spring框架的核心功能之AOP技术

     技术分析之Spring框架的核心功能之AOP技术 AOP的概述        1. 什么是AOP的技术?        * 在软件业,AOP为Aspect Oriented Programming的 ...

  9. Spring框架系列(2) - Spring简单例子引入Spring要点

    上文中我们简单介绍了Spring和Spring Framework的组件,那么这些Spring Framework组件是如何配合工作的呢?本文主要承接上文,向你展示Spring Framework组件 ...

随机推荐

  1. HDU 1269 迷宫城堡 最大强连通图题解

    寻找一个迷宫是否是仅仅有一个最大强连通图. 使用Tarjan算法去求解,经典算法.必需要学习好,要自己创造出来是十分困难的了. 參考资料:https://www.byvoid.com/blog/scc ...

  2. Genymotion模拟器连接不上开发服务器解决办法

    问题截图: 问题原因:虚拟机没有联网.可以打开虚拟机的浏览器随便打开一个网站试一下能不能正常上网.如果不能正常上网. 第一步: 打开VirtualBox 点击确定.重启Genymotion.

  3. HDU 5832A water problem

    大数 判断整除 /* *********************************************** Author :guanjun Created Time :2016/8/14 1 ...

  4. javascript学习---BOM

    1.top是顶级的框架,也就是浏览器窗口. 2.window.close()只能关闭window.open()打开的窗口. 3.firefox不支持修改状态栏,firefox3后强制始终在弹出窗口中显 ...

  5. LD_LIBRARY_PATH设置问题

    今天突然遇到设置LD_LIBRARY_PATH的问题,,发现在.bashrc和/etc/profile中添加 exportLD_LIBRARY_PATH = path_name:$LD_LIBRARY ...

  6. Netty,Thrifty

    小白科普:Netty有什么用? https://mp.weixin.qq.com/s/PTKnRQ_hLf8BBPYnywLenA Thrifty 是基于.net Attribute 实现了标准 Th ...

  7. UVaLive 6833 Miscalculation (表达式计算)

    题意:给定一个表达式,只有+*,然后问你按照法则运算和从左到右计算结果有什么不同. 析:没什么可说的,直接算两次就好. 代码如下: #pragma comment(linker, "/STA ...

  8. 在Linux环境下使用OpenSSL对消息和文件进行加密(转载)

    转自:http://netsecurity.51cto.com/art/201301/378513.htm 1.简介 OpenSSL是一款功能强大的加密工具包.我们当中许多人已经在使用OpenSSL, ...

  9. bzoj 1734: [Usaco2005 feb]Aggressive cows 愤怒的牛【二分+贪心】

    二分答案,贪心判定 #include<iostream> #include<cstdio> #include<algorithm> using namespace ...

  10. [Swift通天遁地]一、超级工具-(8)地图视图MKMapView的常用代理方法

    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs. ...