继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

慕课网《探秘Spring AOP》学习总结

妙空
关注TA
已关注
手记 23
粉丝 138
获赞 532

慕课网《探秘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的使用局限
打开App,阅读手记
3人推荐
发表评论
随时随地看视频慕课网APP

热门评论

还需要再Spring的配置文件中开启Aspect的注解以及组件扫描

   <context:component-scan base-package="com.dimple"/>
   <aop:aspectj-autoproxy/>


查看全部评论