慕课网《探秘Spring AOP》学习总结
时间:2017年09月03日星期日
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:https://github.com/zccodere/study-imooc
学习源码:https://github.com/zccodere/study-imooc
第一章:课程介绍
1-1 面向切面
课程章节
概览
AOP使用
AOP原理
AOP开源运用
课程实战
课程总结
面向切面编程是一种编程范式
编程范式概览
面向过程编程
面向对象编程
面向函数编程(函数式编程)
事件驱动编程(GUI开发中比较常见)
面向切面编程
AOP是什么
是一种编程范式,不是编程语言
解决特定问题,不能解决所有问题
是OOP的补充,不是替代
AOP的初衷
DRY:Don’t Repeat Yourself代码重复性问题
SOC:Separation of Concerns关注点分离
-水平分离:展示层->服务层->持久层
-垂直分离:模块划分(订单、库存等)
-切面分离:分离功能性需求与非功能性需求
使用AOP的好处
集中处理某一关注点/横切逻辑
可以很方便地添加/删除关注点
侵入性少,增强代码可读性及可维护性
AOP的应用场景
权限控制
缓存控制
事务控制
审计日志
性能监控
分布式追踪
异常处理
支持AOP的编程语言
Java
.NET
C/C++
Ruby
Python
PHP
…
1-2 简单案例
案例背景
产品管理的服务
产品添加、删除的操作只能管理员才能进行
普通实现VS AOP实现
创建一个名为springaopguide的maven项目pom如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myimooc</groupId>
<artifactId>springaopguide</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springaop</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
完成后的项目结构如下
代码编写
1.编写Product类
package com.myimooc.springaopguide.domain;
/**
* @title 产品领域模型
* @describe 产品实体对象
* @author zc
* @version 1.0 2017-09-03
*/
public class Product {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2.编写CurrentUserHolder类
package com.myimooc.springaopguide.security;
/**
* @title 获取用户信息
* @describe 模拟用户的切换,将用户信息存入当前线程
* @author zc
* @version 1.0 2017-09-03
*/
public class CurrentUserHolder {
private static final ThreadLocal<String> holder = new ThreadLocal<>();
public static String get(){
return holder.get() == null ? "unkown" : holder.get();
}
public static void set(String user){
holder.set(user);
}
}
3.编写AdminOnly类
package com.myimooc.springaopguide.security;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @title 管理员权限注解
* @describe 被该注解声明的方法需要管理员权限
* @author zc
* @version 1.0 2017-09-03
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly {
}
4.编写SecurityAspect类
package com.myimooc.springaopguide.security;
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;
import com.myimooc.springaopguide.service.AuthService;
/**
* @title 权限校验切面类
* @describe
* @author zc
* @version 1.0 2017-09-03
*/
// 声明为一个切面
@Aspect
@Component
public class SecurityAspect {
@Autowired
private AuthService authService;
// 使用要拦截标注有AdminOnly的注解进行操作
@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){
}
@Before("adminOnly()")
public void check(){
authService.checkAccess();
}
}
5.编写AuthService类
package com.myimooc.springaopguide.service;
import java.util.Objects;
import org.springframework.stereotype.Service;
import com.myimooc.springaopguide.security.CurrentUserHolder;
/**
* @title 权限校验类
* @describe 对用户权限进行校验
* @author zc
* @version 1.0 2017-09-03
*/
@Service
public class AuthService {
public void checkAccess(){
String user = CurrentUserHolder.get();
if(!Objects.equals("admin", user)){
throw new RuntimeException("operation not allow");
}
}
}
6.编写ProductService类
package com.myimooc.springaopguide.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.myimooc.springaopguide.domain.Product;
/**
* @title 产品服务类
* @describe 产品相关业务服务-传统方式实现权限校验
* @author zc
* @version 1.0 2017-09-03
*/
@Service
public class ProductService {
@Autowired
private 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");
}
}
7.编写ProductServiceAop类
package com.myimooc.springaopguide.service;
import org.springframework.stereotype.Service;
import com.myimooc.springaopguide.domain.Product;
import com.myimooc.springaopguide.security.AdminOnly;
/**
* @title 产品服务类
* @describe 产品相关业务服务-AOP方式实现权限校验
* @author zc
* @version 1.0 2017-09-03
*/
@Service
public class ProductServiceAop {
@AdminOnly
public void insert(Product product){
System.out.println("insert product");
}
@AdminOnly
public void delete(Long id){
System.out.println("delete product");
}
}
8.编写AopGuideApplicationTests类
package com.myimooc.springaopguide;
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;
import com.myimooc.springaopguide.security.CurrentUserHolder;
import com.myimooc.springaopguide.service.ProductService;
import com.myimooc.springaopguide.service.ProductServiceAop;
/**
* @title 单元测试类
* @describe 测试权限校验服务是否生效
* @author zc
* @version 1.0 2017-09-03
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class AopGuideApplicationTests {
@Autowired
private ProductService productService;
@Test(expected = Exception.class)
public void annoInsertTest(){
CurrentUserHolder.set("tom");
productService.delete(1L);
}
@Test
public void adminInsertTest(){
CurrentUserHolder.set("admin");
productService.delete(1L);
}
@Autowired
private ProductServiceAop productServiceAop;
@Test(expected = Exception.class)
public void annoInsertAopTest(){
CurrentUserHolder.set("tom");
productServiceAop.delete(1L);
}
@Test
public void adminInsertAopTest(){
CurrentUserHolder.set("admin");
productServiceAop.delete(1L);
}
}
第二章:使用详解
2-1 本节内容
Spring AOP使用方式
XML配置+Pointcut expression【不推荐使用方式】
注解方式+ Pointcut expression【推荐使用该方式】
Aspectj注解
@Aspect:用于声明当前类是一个切面
@Pointcut:用于描述在哪些类、哪些方法上执行切面的代码
Advice:描述想要在这些方法执行的什么时机进行拦截
本章内容
Pointcut express:切面表达式
5种Advice:建言的五种细分怎么使用
2-2 切面表达式
切面表达式
1.designators(指示器)
execution()
描述通过什么样的方式去匹配哪些类、哪些方法
2.wildcards(通配符)
* .. +
使用通配符进行描述
3.operators(运算符)
&& || !
使用运算符进行多条件的判断
Designators(指示器)
匹配方法 execution()
匹配注解 @target() @args() @within() @annotation()
匹配包/类型 @within()
匹配对象 this() bean() target()
匹配参数 args()
Wildcards(通配符)
* 匹配任意数量的字符
+ 匹配指定类及其子类
.. 一般用于匹配任意参数的子包或参数
Operators(运算符)
&& 与操作符
|| 或操作符
! 非操作符
2-3 匹配包类
// 匹配 ProductServiceAop 类里面的所有方法
@Pointcut("within(com.myimooc.springaopguide.service.ProductServiceAop)")
public void matchType(){}
// 匹配 com.myimooc.springaopguide.service 包及子包下所有类的方法
@Pointcut("within(com.myimooc.springaopguide.service..*)")
public void matchPackage(){}
2-4 匹配对象
// 匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
@Pointcut("this(com.myimooc.springaopguide.dao.DemoDao)")
public void testDemo(){}
// 匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法,这里即DemoDao的方法
@Pointcut("target(com.myimooc.springaopguide.dao.IDao)")
public void targetDemo(){}
// 匹配所有以Service结尾的bean里面的方法
@Pointcut("bean(*Service)")
public void beanDemo(){}
2-5 匹配参数
// 匹配任何以find开头而且只有一个Long参数的方法
@Pointcut("execution(* *..find*(Long))")
public void argsDemo1(){}
// 匹配任何只有一个Long参数的方法
@Pointcut("args(Long)")
public void argsDemo2(){}
// 匹配任何以find开头而且第一个参数为Long型的方法
@Pointcut("execution(* *..find*(Long,..))")
public void argsDemo3(){}
// 匹配第一个参数为Long型的方法
@Pointcut("args(Long,..))")
public void argsDemo4(){}
2-6 匹配注解
// 匹配方法标注有AdminOnly的注解的方法
@Pointcut("@annotation(com.myimooc.springaopguide.security.AdminOnly)")
public void annoDemo(){}
// 匹配标注有Beta的类底下的方法,要求的annotation的RetentionPolicy级别为CLASS
@Pointcut("@within(com.google.common.annotations.Beta)")
public void annoWithDemo(){}
// 匹配标注有Repository的类底下的方法,要求的RetentionPolicy级别为RUNTIME
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void annoTargetDemo(){}
// 匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(org.springframework.stereotype.Repository)")
public void annoArgsDemo(){}
2-7 匹配方法
execution()格式
execution(
modifier-pattern? // 修饰符匹配
ret-type-pattern // 返回值匹配
declaring-type-pattern? // 描述值包名
name-pattern(param-pattern) // 方法名匹配(参数匹配)
throws-pattern?// 抛出异常匹配
)
execution()实例
// 匹配 使用public修饰符 任意返回值 在com.myimooc.springaopguide.service包及子下
// 以Service结尾的类 任意方法(任意参数)
@Pointcut("execution(public * com.myimooc.springaopguide.service..*Service.*(..))")
public void matchCondition(){}
2-8 建言注解
5中Advice(建言)注解
@Before,前置通知
@After(finally),后置通知,方法执行完之后
@AfterReturning,返回通知,成功执行之后
@AfterThrowing,异常通知,抛出异常之后
@Around,环绕通知
5中Advice(建言)实例
// 定义切点,拦截使用NeedSecured注解修饰的方法
@Pointcut("@within(com.myimooc.demo.security.NeedSecured)")
public void annoTargetVsWithinDemo(){}
// 使用NeedSecured注解修饰 且 在com.myimooc包下的方法
@Before("annoTargetVsWithinDemo() && within(com.myimooc..*)")
public void beforeDemo(){
System.out.println("被拦截方法执行之前执行");
}
@After("annoTargetVsWithinDemo() && within(com.myimooc..*)")
public void afterDemo(){
System.out.println("被拦截方法执行之后执行");
}
@AfterReturning("annoTargetVsWithinDemo() && within(com.myimooc..*)")
public void afterReturning(){
System.out.println("代码成功之后执行");
}
@AfterThrowing("annoTargetVsWithinDemo() && within(com.myimooc..*)")
public void afterThrowing(){
System.out.println("代码执行抛出异常之后执行");
}
@Around("annoTargetVsWithinDemo() && within(com.myimooc..*)")
public Object aroundDemo(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("相当于@Before");
try{
Object result = pjp.proceed(pjp.getArgs());
System.out.println("相当于@AfterReturning");
return result;
}catch (Throwable throwable) {
System.out.println("相当于@AfterThrowing");
throw throwable;
}finally {
System.out.println("相当于@After");
}
}
Advice中的参数及结果绑定
@Before("annoTargetVsWithinDemo() && within(com.myimooc..*) && args(userId)")
public void beforeWithArgs(JoinPoint joinPoint,Long userId){
System.out.println("被拦截方法执行之前执行,args:"+userId);
}
@AfterReturning(value="annoTargetVsWithinDemo() && within(com.myimooc..*)",returning="returnValue")
public void getResult(Object returnValue){
if(returnValue != null){
System.out.println("代码成功之后执行,result:"+returnValue);
}
}
第三章:实现原理
3-1 本节内容
上节回顾
Pointcut expression的组成部分
各种designators的区别
5中advice及参数、结果绑定
实现原理
概述
设计:代理模式、责任链模式
实现:JDK实现、cglib实现
3-2 原理概述
原理概述:植入的时机
1.编译期(AspectJ)
2.类加载时(Aspectj 5+)
3.运行时(Spring AOP)【本节课讲解内容】
运行时植入
运行时植入是怎么实现的
从静态代理到动态代理
基于接口代理与基于继承代理
3-3 代理模式
代理AOP对象
Caller:调用方
Proxy:AOP代理对象
Target:目标对象
代理模式类图
客户端通过接口来引用目标对象
代理对象把真正的方法委托目标对象来执行,自己执行额外的逻辑
代码编写
1.编写Subject类
2.编写RealSubject类
3.编写Proxy类
4.编写Client类
受篇幅限制,源码请到我的github地址查看
3-4 JDK代理静态代理与动态代理
静态代理的缺点:每当需要代理的方法越多的时候,重复的逻辑就越多
动态代理的两类实现:基于接口代理与基于继承代理
两类实现的代表技术:JDK代理与Cglib代理
JDK实现要点
类:java.lang.reflect.Proxy
接口:InvocationHandler
只能基于接口进行动态代理
代码编写
1.编写JdkSubject类
2.编写Client类
受篇幅限制,源码请到我的github地址查看
3-5 JDK解析JDK代理源码解析
Proxy.newProxyInstance(首先,调用该方法)
getProxyClass0、ProxyClassFactory、ProxyGenerator(然后,分别调用方法,生成字节码)
newInstance(最后,利用反射根据字节码生成实例)
3-6 Cglib代理
代码编写
1.编写DemoMethodInterceptor类
2.编写Client类
受篇幅限制,源码请到我的github地址查看
JDK与Cglib代理对比
JDK只能针对有接口的类的接口方法进行动态代理
Cglib基于继承来实现代理,无法对static、final类进行代理
Cglib基于继承来实现代理,无法对private、static方法进行代理
3-7 Spring选择
Spring创建代理bean时序图
SpringAOP对两种实现的选择
如果目标对象实现了接口,则默认采用JDK动态代理
如果目标对象没有实现接口,则采用Cglib进行动态代理
如果目标对象实现了接口,但设置强制cglib代理,则使用cglib代理
在SpringBoot中,通过@EnableAspectJAutoProxy(proxyTargetClass=true)设置
3-8 链式调用
当多个AOP作用到同一个目标对象时,采用责任链模式
责任链模式类图
代码编写
1.编写Handler类
2.编写Client类
3.编写Chain类
4.编写ChainHandler类
5.编写ChainClient类
受篇幅限制,源码请到我的github地址查看
第四章:代码解读 4-1 本节内容上节回顾
静态代理与动态代理
JDK代理与Cglib代理区别及局限
代理模式与责任链模式
Spring AOP在开源项目里面的应用:三个例子
事务:@Transactional:Spring如何利用Transaction进行事务控制
安全:@PreAuthorize:Spring Security如何利用PreAuthorize进行安全控制
缓存:@Cacheable:Spring Cache如何利用Cacheable进行缓存控制
通过案例来讲解,源码可到我的github地址查看
第五章:实战案例 5-1 案例背景实战案例背景
商家产品管理系统
记录产品修改的操作记录
什么人在什么时间修改了哪些产品的哪些字段修改为什么值
实现思路
利用aspect去拦截增删改方法
利用反射获取对象的新旧值
利用@Around的advice去记录操作记录
5-2 案例实现
创建名为mydatalog的maven项目pom如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.myimooc</groupId>
<artifactId>mydatalog</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>mydatalog</name>
<url>http://maven.apache.org</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency> -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.36</version>
</dependency>
</dependencies>
</project>
完成后的项目结构图如下
受篇幅限制,源码请到我的github地址查看
第六章:课程总结 6-1 课程总结要点清单
AOP的适用范围及优劣势
AOP的概念及Spring切面表达式
AOP的实现原理及运用
使用SpringAOP的注意事项
不宜把重要的业务逻辑放到AOP中处理
无法拦截static、final、private方法
无法拦截内部方法调用
课程小结
合理利用面向切面编程提高代码质量
掌握SpringAOP概念及实现原理
了解AOP的优缺点及SpringAOP的使用局限
共同学习,写下你的评论
评论加载中...
作者其他优质文章