慕课网_《探秘Spring AOP》学习总结
时间:2017年09月03日星期日
说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com
教学源码:https://github.com/zccodere/s...
学习源码:https://github.com/zccodere/s...
第一章:课程介绍
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如下
完成后的项目结构如下
代码编写
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类
package com.myimooc.myproxydemo.pattern; /** * @title 代理对象接口 * @describe * @author zc * @version 1.0 2017-09-13 */ public interface Subject { void request(); }
2.编写RealSubject类
package com.myimooc.myproxydemo.pattern; /** * @title 目标对象 * @describe 实现了Subject接口 * @author zc * @version 1.0 2017-09-13 */ public class RealSubject implements Subject{ @Override public void request() { System.out.println("real subject execute request"); } }
3.编写Proxy类
package com.myimooc.myproxydemo.pattern; /** * @title 代理对象 * @describe 同样也实现了Subject接口 * @author zc * @version 1.0 2017-09-13 */ public class Proxy implements Subject{ // 需要引用目标对象 private RealSubject realSubject; // 强制必须传入目标对象 public Proxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void request() { // 在目标对象方法执行之前做一些额外的事情 System.out.println("before"); try{ // 代理对象不会做真实的业务逻辑,还是委托给真实的目标对象执行 realSubject.request(); }catch (Exception e) { System.out.println("ex:"+e.getMessage()); throw e; }finally { // 在目标对象方法执行之后做一些额外的事情 System.out.println("after"); } } }
4.编写Client类
package com.myimooc.myproxydemo.pattern; /** * @title 客户端 * @describe 测试代理模式 * @author zc * @version 1.0 2017-09-13 */ public class Client { public static void main(String[] args) { Subject subject = new Proxy(new RealSubject()); subject.request(); } }
3-4 JDK代理
静态代理与动态代理
静态代理的缺点:每当需要代理的方法越多的时候,重复的逻辑就越多 动态代理的两类实现:基于接口代理与基于继承代理 两类实现的代表技术:JDK代理与Cglib代理
JDK实现要点
类:java.lang.reflect.Proxy 接口:InvocationHandler 只能基于接口进行动态代理
代码编写
1.编写JdkSubject类
package com.myimooc.myproxydemo.jdkimpl; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import com.myimooc.myproxydemo.pattern.RealSubject; /** * @title 动态代理类 * @describe 相当于AOP的aspect * @author zc * @version 1.0 2017-09-13 */ public class JdkSubject implements InvocationHandler{ // 同样需要引入目标对象 private RealSubject realSubject; public JdkSubject(RealSubject realSubject) { this.realSubject = realSubject; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在目标对象方法执行之前做一些额外的事情 System.out.println("before"); Object result = null; try{ // 代理对象不会做真实的业务逻辑,还是委托给真实的目标对象执行 result = method.invoke(realSubject, args); }catch (Exception e) { System.out.println("ex:"+e.getMessage()); throw e; }finally { // 在目标对象方法执行之后做一些额外的事情 System.out.println("after"); } return result; } }
2.编写Client类
package com.myimooc.myproxydemo.jdkimpl; import java.lang.reflect.Proxy; import com.myimooc.myproxydemo.pattern.RealSubject; import com.myimooc.myproxydemo.pattern.Subject; /** * @title 动态代理类 * @describe JDK实现动态代理测试类 * @author zc * @version 1.0 2017-09-13 */ public class Client { public static void main(String[] args) { Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(), new Class[]{Subject.class}, new JdkSubject(new RealSubject())); subject.request(); } }
3-5 JDK解析
JDK代理源码解析
Proxy.newProxyInstance(首先,调用该方法) getProxyClass0、ProxyClassFactory、ProxyGenerator(然后,分别调用方法,生成字节码) newInstance(最后,利用反射根据字节码生成实例)
3-6 Cglib代理
代码编写
1.编写DemoMethodInterceptor类
package com.myimooc.myproxydemo.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; /** * @title 需要植入的代码类 * @describe 需要实现MethodInterceptorj接口 * @author zc * @version 1.0 2017-09-13 */ public class DemoMethodInterceptor implements MethodInterceptor{ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before in cglib"); Object result = null; try{ // 代理类调用父类的方法 proxy.invokeSuper(obj, args); }catch (Exception e) { System.out.println("ex:"+e.getMessage()); throw e; }finally { // 在目标对象方法执行之后做一些额外的事情 System.out.println("after in cglib"); } return result; } }
2.编写Client类
package com.myimooc.myproxydemo.cglib; import com.myimooc.myproxydemo.pattern.RealSubject; import com.myimooc.myproxydemo.pattern.Subject; import net.sf.cglib.proxy.Enhancer; /** * @title 动态代理类 * @describe Cglib实现动态代理测试类 * @author zc * @version 1.0 2017-09-13 */ public class Client { public static void main(String[] args) { // 实例化Enhancer对象 Enhancer enhancer = new Enhancer(); // 设置需要代理的对象 enhancer.setSuperclass(RealSubject.class); // 设置需要植入的代码 enhancer.setCallback(new DemoMethodInterceptor()); // 生成代理类 Subject subject = (Subject)enhancer.create(); subject.request(); } }
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类
package com.myimooc.myproxydemo.chain; /** * @title 责任链模式 * @describe 抽象接口 * @author zc * @version 1.0 2017-09-13 */ public abstract class Handler { // 后继Handler,是否有类进行处理 private Handler sucessor; // 对外暴露 public void execute(){ handleProcess(); if(sucessor != null){ sucessor.execute(); } } // 由子类实现 protected abstract void handleProcess(); public Handler getSucessor() { return sucessor; } public void setSucessor(Handler sucessor) { this.sucessor = sucessor; } }
2.编写Client类
package com.myimooc.myproxydemo.chain; /** * @title 责任链模式 * @describe 测试类 * @author zc * @version 1.0 2017-09-13 */ public class Client { static class HandlerA extends Handler{ @Override protected void handleProcess() { System.out.println("handle by a"); } } static class HandlerB extends Handler{ @Override protected void handleProcess() { System.out.println("handle by b"); } } static class HandlerC extends Handler{ @Override protected void handleProcess() { System.out.println("handle by c"); } } public static void main(String[] args) { HandlerA handlerA = new HandlerA(); HandlerB HandlerB = new HandlerB(); HandlerC HandlerC = new HandlerC(); // 设置链接关系 handlerA.setSucessor(HandlerB); HandlerB.setSucessor(HandlerC); handlerA.execute(); } }
3.编写Chain类
package com.myimooc.myproxydemo.chain; import java.util.List; /** * @title 责任链模式 * @describe 封装链式关系 * @author zc * @version 1.0 2017-09-13 */ public class Chain { private List<ChainHandler> handlers; private int index = 0; public Chain(List<ChainHandler> handlers){ this.handlers = handlers; } public void proceed(){ if(index >= handlers.size()){ return; } handlers.get(index++).execute(this); } }
4.编写ChainHandler类
package com.myimooc.myproxydemo.chain; /** * @title 责任链模式 * @describe 对Handler进行封装 * @author zc * @version 1.0 2017-09-13 */ public abstract class ChainHandler { public void execute(Chain chain){ handleProcess(); chain.proceed(); } // 由子类实现 protected abstract void handleProcess(); }
5.编写ChainClient类
package com.myimooc.myproxydemo.chain; import java.util.Arrays; import java.util.List; /** * @title 责任链模式 * @describe 有顺序的链式调用测试类 * @author zc * @version 1.0 2017-09-13 */ public class ChainClient { static class ChainHandlerA extends ChainHandler{ @Override protected void handleProcess() { System.out.println("handle by a"); } } static class ChainHandlerB extends ChainHandler{ @Override protected void handleProcess() { System.out.println("handle by b"); } } static class ChainHandlerC extends ChainHandler{ @Override protected void handleProcess() { System.out.println("handle by c"); } } public static void main(String[] args) { // 声明链式调用顺序 List<ChainHandler> handlers = Arrays.asList( new ChainHandlerA(), new ChainHandlerB(), new ChainHandlerC() ); Chain chain = new Chain(handlers); chain.proceed(); } }
第四章:代码解读
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的使用局限