了解SpringAOP
目录
一、AOP概述
1.什么是AOP
2.什么是Spring AOP
二、Spring AOP入门
编写AOP程序
三、Spring AOP详解
1.Spring AOP核心概念
1.1 切点(Pointcut)
1.2 连接点(Join Point)
1.3 通知(Advice)
1.4 切面(Aspect)
2. 通知类型
3. @PointCut
4. 切面优先级@Order
5. 切点表达式
5.1 execution表达式
切点表达式示例
5.2 @annotation
自定义注解@MyAspect
切面类
给方法添加自定义注解
四、Spring AOP原理
1.代理模式
2.代理模式的主要角色
2.1 静态代理
2.2 动态代理
JDK动态代理
CGLIB动态代理
五、总结
一、AOP概述
AOP是Spring框架的第二大核心(第一大核心是Ioc)。
1.什么是AOP
Aspect Oriented Programming (面向切面编程)
面向切面编程:就是指某一类特定问题,所以AOP可以理解为面向特定方法编程。
而面向特定方法编程指的是一类特定的问题,比如登录校验的拦截器,就是对“登录校验”这类问题的统一处理,所以拦截器也是AOP的一种应用。
AOP是一种思想,拦截器是AOP思想的一种实现。Spring框架实现了这种思想,提供了拦截器技术的相关接口。
同样的,统一数据返回格式和统一异常处理,也是AOP思想的一种实现。
简单来说:AOP是一种思想,是对某一类事情的集中处理。
2.什么是Spring AOP
AOP是一种思想,它的实现方法有很多,有Spring AOP,也有AspectJ,CGLIB等。
Spring AOP是其中的一种实现方法。
二、Spring AOP入门
我们先通过下面的程序体验下AOP的开发,并掌握Spring中AOP的开发步骤。
需求:统计接口各个方法的执行时间。
在pom.xml文件中添加配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
编写AOP程序
记录Controller类中每个方法的执行时间。
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
@Slf4j
public class TimeAspect {
/*
记录方法耗时
*/
//注意下面为controller的包路径
@Around("execution(* org.example.springblog.controller.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录方法执行开始时间
long begin = System.currentTimeMillis();
//执行原始方法
Object result = pjp.proceed();
//记录方法执行结束时间
long end = System.currentTimeMillis();
//记录方法执行耗时
log.info(pjp.getSignature()+"执行耗时:{}ms",end - begin);
return result;
}
}
执行结果:
对程序进行简单的讲解:
- @Aspect:标识这是一个切面类。
- @Around:环境通知,在目标方法的前后都会被执行。后面的表达式表示对哪些方法进行增强。
- ProceedingJoinPoint.proceed()让原始方法执行。
这整个代码划分为三部分:
我们通过AOP入门程序完成了业务接口执行耗时的统计。
通过上面的程序,我们也可以感受到AOP面向切面编程的一些优势:
- 代码无侵入:不修改原始的业务方法就可以对原始业务方法进行了功能的增强或者是功能的改变。
- 减少了重复代码。
- 提高开发效率。
- 维护方便
三、Spring AOP详解
下面详细的学习AOP,主要是以下部分:
- Spring AOP中涉及的核心概念
- Spring AOP通知类型
- 多个AOP程序的执行顺序
1.Spring AOP核心概念
AOP的核心概念主要有以下几种:
- 切点(一组规则,通过表达式来描述)
- 连接点(切面要作用的方法,即目标方法,切点描述的方法)
- 通知(具体的逻辑,要做什么处理)
- 切面(切点+通知)
1.1 切点(Pointcut)
切点(Pointcut),也称为“切入点”。
Pointcut的作用就是提供一组规则,告诉程序对哪些方法来进行功能增强。
上面的表达式 execution(* org.example.springblog.controller.*.*(..)) 就是切点表达式。
1.2 连接点(Join Point)
满足切点表达式规则的方法,就是连接点,也就是可以被AOP控制的方法。
入门程序举例,所有 org.example.springblog.controller 路径下的方法,都是连接点。
package org.example.springblog.controller;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Result login(String userName,String password){
//..
}
@RequestMapping("/getUserInfo")
public UserInfo getUserInfo(HttpServletRequest request){
//..
}
@RequestMapping("/getAuthorInfo")
public UserInfo getAuthorInfo(Integer blogId){
//..
}
}
上述UserController中的方法都是连接点。
切点和连接点的关系:连接点是满足切点表达式的元素,切点可以看做是保存了众多连接点的一个集合。
举个形象的例子:
切点表达式:全体教师
连接点:张三,李四等各个老师。
1.3 通知(Advice)
通知就是具体要做的工作,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)。
比如上述程序中记录业务方法的耗时时间,就是通知。
在AOP面向切面编程当中,把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容。
1.4 切面(Aspect)
切面(Aspect)=切点(Pointcut)+通知(Advice)
通过切面就能够描述当前AOP程序需要针对于哪些方法,在什么时候执行什么样的操作。
切面既包含了通知逻辑的定义,也包括了连接点的定义。
切面所在的类,一般称为切面类(被@Aspect注解标识的类)。
2. 通知类型
上面讲了什么事通知,下面学习通知的类型。@Around就是其中一种通知类型,表示环绕通知。
Spring中AOP的通知类型有以下几种:
- @Around:环绕通知,此注解标注的通知方法在目标方法前,后都被执行。
- @Before:前置通知,此注解标注的通知方法在目标方法前被执行。
- @After:后置通知,此注解标注的通知在目标方法后被执行,无论是否有异常都会执行。
- @AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行。
- @AfterThrowing:异常后通知,此注解标注的通知方法发生异常后执行。
接下来通过代码来加深对这几个通知的理解:
@Component
@Aspect
@Slf4j
public class TimeAspect {
/*
记录方法耗时
*/
//注意下面为controller的包路径
@Around("execution(* org.example.aspect.demos.web.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
log.info("Around,目标方法执行前");
Object o = pjp.proceed();
log.info("Around,目标方法执行后");
return o;
}
//前置通知
@Before("execution(* org.example.aspect.demos.web.*.*(..))")
public void doBefore(){
log.info("执行Before方法");
}
//后置通知
@After("execution(* org.example.aspect.demos.web.*.*(..))")
public void doAfter(){
log.info("执行After方法");
}
//返回后通知
@AfterReturning("execution(* org.example.aspect.demos.web.*.*(..))")
public void AfterReturning(){
log.info("执行AfterReturning方法");
}
@AfterThrowing("execution(* org.example.aspect.demos.web.*.*(..))")
public void AfterThrowing(){
log.info("执行AfterThrowing方法");
}
}
写一些测试程序:
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@RequestMapping("/t2")
public boolean t2() {
int a = 10 / 0;
return true;
}
}
- 访问t1时,无异常的情况:
http://127.0.0.1:8080/test/t1
可以看到,程序正常运行的情况下,@AfterThrowing 标识的通知方法不会执行。
从日志中可以看出,@Around标识的通知方法包含两部分,一个“前置逻辑”,一个“后置逻辑”。其中,“前置逻辑”会先于@Before标识的通知方法执行,“后置逻辑”会晚于@After标识的通知方法执行。
如下图:
- 访问t2时,出现异常的情况:
http://127.0.0.1:8080/test/t2
程序发生异常的情况下:
- @AfterReturning标识的通知方法不会执行,@AfterThrowing标识的通知方法执行了。
- @Around 环境通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在在执行了(因为原始方法调用出异常了)。
注意事项:
- @Around 环绕通知需要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行。
- @Around 环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。
- 一个切面类可以有多个切点。
3. @PointCut
上面代码存在一个问题,就是存在大量重复的切点表达式:
execution(* org.example.springblog.controller.*.*(..))
Spring提供了 @PointCut注解,把公共表达式提取出来,需要用到时引入该切点表达式即可。
- 上述代码就可以修改为:
@Component
@Aspect
@Slf4j
public class TimeAspect {
//定义切点(公共的切点表达式)
@Pointcut("execution(* org.example.springblog.controller.*.*(..))")
private void pt(){}
/*
记录方法耗时
*/
//注意下面为controller的包路径
@Around("pt()")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
}
//前置通知
@Before("pt()")
public void doBefore(){
}
//后置通知
@After("pt()")
public void doAfter(){
}
//返回后通知
@AfterReturning("pt()")
public void AfterReturning(){
}
@AfterThrowing("pt()")
public void AfterThrowing(){
}
}
当切点定义使用private修饰时,仅能在当前切面类使用,当其他切面类也要使用当前切点定义时,就需要把private改为public,引用方式为:全限定类名.方法名()。
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
//前置通知
@Before("com.example.demo.aspect.AspectDemo.pt()")
public void doBefore() {
log.info("执⾏ AspectDemo2 -> Before ⽅法");
}
}
4. 切面优先级@Order
当在一个项目中,定义了多个切面类时,并且这些切面类的多个切入点都匹配到了同一个目标方法。
当目标方法运行的时候,这些切面类中的通知方法都会执行,那么这几个通知方法的执行顺序是什么样的呢?
- 定义多个切面类:
为了简单化只写@Before和@After 这两个通知。
@Component
public class AspectDemo2 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执⾏ AspectDemo2 -> Before ⽅法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执⾏ AspectDemo2 -> After ⽅法");
}
}
@Component
public class AspectDemo3 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执⾏ AspectDemo3 -> Before ⽅法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执⾏ AspectDemo3 -> After ⽅法");
}
}
@Component
public class AspectDemo4 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("执⾏ AspectDemo4 -> Before ⽅法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("执⾏ AspectDemo4 -> After ⽅法");
}
}
运行程序观察日志:
通过上述程序的运行结果可以看出:
存在多个切面类时,默认按照切面类的类名字母排序:
- @Before通知:字母排名靠前的先执行。
- @After通知:字母排名靠前的后执行。
Spring 给我们提供了一个新的注解来控制这些切面通知的执行顺序:@Order。
使用方式如下,添加在类上:
@Aspect
@Component
@Order(2)
public class AspectDemo2 {
//...代码省略
}
@Aspect
@Component
@Order(1)
public class AspectDemo3 {
//...代码省略
}
@Aspect
@Component
@Order(3)
public class AspectDemo4 {
//...代码省略
}
运行程序,观察日志:
通过上述程序的运行结果可以得出结论:
@Order注解标识的切面类,执行顺序如下:
- @Before通知:字母越小先执行。
- @After通知:数字越大先执行。
5. 切点表达式
上面的代码中,我们一直在使用切点表达式来描述切点,下面介绍一下切点表达式的语法。
切点表达式常见有两种表达方式:
- execution(...):根据方法的签名来匹配
- @annotation(...):根据注解匹配
5.1 execution表达式
execution()是最常用的切点表达式,用来匹配方法,语法为:
execution(<访问修饰符> <返回类型> <包名.类名.⽅法(⽅法参数)> <异常>)
其中:访问修饰符和异常可以省略:
切点表达式支持通配符表达:
1. * :匹配任意字符,只匹配一个元素(返回类型,包,类名,方法或者方法参数)
- 包名使用 * 表示任意包(一层包使用一个 *)
- 类名使用 * 表示任意类
- 返回值使用 * 表示任意返回值类型
- 方法名使用 * 表示任意方法
- 参数使用 * 表示一个 任意类型的参数
2. .. :匹配多个连接的任意符号,可以通配任意层级的包,或任意类型,任意个数的参数
- 使用 .. 配置包名,标识此包下的所有子包
- 可以使用 .. 配置参数,任意类型的参数
切点表达式示例
TestController 下的public修饰,返回类型为String,方法名为t1,无参方法:
execution(public String com.example.demo.controller.TestController.t1())
省略访问修饰符:
execution( String com.example.demo.controller.TestController.t1())
匹配所有返回类型:
execution( * com.example.demo.controller.TestController.t1())
匹配TestController 下的所有无参方法:
execution(* com.example.demo.controller.TestController.*())
匹配TestController下的所有方法:
execution(* com.example.demo.controller.TestController.*(..))
匹配controller包下所有的类的所有方法:
execution(* com.example.demo.controller.*.*(..))
匹配所有包下面的TestController:
execution(* com..TestController.*(..))
匹配com.example.demo包下, ⼦孙包下的所有类的所有⽅法:
execution(* com.example.demo..*(..))
5.2 @annotation
execution 表达式更适用有规则的,如果我们要匹配多个无规则的方法呢,比如TestController中的t1()和UserController中的u1()这两个方法。
这个时候使用execution这种切点表达式来描述就不是很方便了。
可以借助自定义注解的方式以及另一种切点表达式 @annotation 来描述这一类的切点。
语法:
@annotation(自定义注解的全限定类名)
自定义注解@MyAspect
创建一个注解类(创建Class文件一样的流程,选择Annotation就可以了)。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
切面类
使用@annotation切点表达式定义切点,只对有注解@Myspec的方法t生效。
切面类代码如下:
@Slf4j
@Component
@Aspect
public class MyAspectDemo {
//前置通知
@Before("@annotation(com.example.demo.aspect.MyAspect)")
public void before(){
log.info("MyAspect -> before ...");
}
//后置通知
@After("@annotation(com.example.demo.aspect.MyAspect)")
public void after(){
log.info("MyAspect -> after ...");
}
}
给方法添加自定义注解
在TestController中的t1()和UserController中的u1()这两个方法上添加自定义注解 @MyAspect,
其他方法不添加。
@MyAspect
@RequestMapping("/t1")
public String t1() {
return "t1";
}
@MyAspect
@RequestMapping("/u1")
public String u1(){
return "u1";
}
观察日志:
可以看到,切面通知被执行了。
四、Spring AOP原理
上面的内容主要讲了Spring AOP是如何实现的。
Spring AOP 是基于动态代理来实现AOP的。
1.代理模式
代理模式,也叫委托模式。
定义:为其他对象提供一种代理以控制对这个对象的访问,它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接诶对目标方法进行调用,而是通过代理类间接调用。
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象呢之间起到中介的作用。
- 使用代理前:
- 使用代理后:
生活中的代理:房屋中介。房屋进行租赁时,卖方会把房屋授权给中介,由中介来代理看房,房屋咨询等服务。
2.代理模式的主要角色
- Subject:业务接口类。可以提供是抽象类或者接口(事件)。
- RealSubject:业务实现类。具体的业务执行,也就是被代理对象(事件的实现内容)。
- Proxy:代理类。RealSubject的代理(代理事件的实现)。
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。
根据代理的创建时期,代理模式分为静态代理和动态代理:
- 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的.class文件就已经存在了。
- 动态代理:在程序运行时,运用反射机制动态创建而成。
2.1 静态代理
静态代理:在程序运行前,代理类的.class文件就已经存在了(在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了)。
- 通过代码来加深理解,以房租租赁为例。
1.定义接口(定义为房东要做的事情,也是中介需要做的事情)
public interface HouseSubject {
void rentHouse();
}
2.实现接口(房东出租房子)
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
}
3.代理(中介帮房东出租房子)
public class HouseProxy implements HouseSubject {
//将被代理对象声明为成员变量
private HouseSubject subject;
public HouseProxy(HouseSubject subject) {
this.subject = subject;
}
@Override
public void rentHouse() {
//开始代理
System.out.println("我是中介,开始代理");
//代理房东出租房子
subject.rentHouse();
//代理结束
System.out.println("我是中介,代理结束");
}
}
4.使用
public class Main {
public static void main(String[] args) {
HouseSubject subject = new RealHouseSubject();
//创建代理类
HouseProxy proxy = new HouseProxy(subject);
//通过代理类访问目标方法
proxy.rentHouse();
}
}
运行结果:
上面这个代理实现方式就是静态代理(仿佛啥也没干)。
从上述程序可以看出,虽然静态代理也完成了对目标对象的代理,但是由于代码都写死了,对目标对象的每个方法的增强都是手动完成,非常不灵活,所以日常开发几乎看不到静态代理的场景。
- 接下来新增需求:中介又新增了其他业务:代理房屋出售
需要对上述代码进行修改
1.接口定义修改
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
2.接口实现修改
public class RealHouseSubject implements HouseSubject {
@Override
public void rentHouse() {
System.out.println("我是房东,我出租房子");
}
@Override
public void saleHouse() {
System.out.println("我是房东,我要出售房子");
}
}
3.代理类修改
public class HouseProxy implements HouseSubject {
//将被代理对象声明为成员变量
private HouseSubject subject;
public HouseProxy(HouseSubject subject) {
this.subject = subject;
}
@Override
public void rentHouse() {
//开始代理
System.out.println("我是中介,开始代理");
//代理房东出租房子
subject.rentHouse();
//代理结束
System.out.println("我是中介,代理结束");
}
@Override
public void saleHouse() {
//开始代理
System.out.println("我是中介,开始代理");
//代理房东出租房子
subject.saleHouse();
//代理结束
System.out.println("我是中介,代理结束");
}
}
4.使用
public class Main {
public static void main(String[] args) {
HouseSubject subject = new RealHouseSubject();
//创建代理类
HouseProxy proxy = new HouseProxy(subject);
// //通过代理类访问目标方法
// proxy.rentHouse();
proxy.saleHouse();
}
}
运行结果:
上述代码可以看出,我们修改接口(HouseSubject) 和业务实现(RealHouseSubject)时,还需要修改代理类(Proxy)。
同样的,如果又新增的接口(Subject)和业务实现类(RealSubject),也就需要对每一个业务实现类新增代理类(Proxy)。
2.2 动态代理
相比于静态代理来说,动态代理更加灵活。
我们不需要针对每个目标对象都单独创建一个代理对象,而是把这个创建代理对象的工作推迟到程序运行时由JVM来实现。也就是说动态代理在程序运行时,根据需要动态创建生成。
比如房屋中介,我不需要提前预测有哪些业务,而是业务来了再根据情况创建。
java对动态代理进行了实现,并给我们提供了一些API,常见的实现方式有两种:
- JDK动态代理
- CGLIB动态代理
动态代理在日常开发中使用的相对较少,但是在框架中几乎是必用的一门技术,学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。
JDK动态代理
JDK动态代理实现步骤:
1.定义一个接口及其实现类(上面静态代理中的 HouseSubject 和 RealHouseSubject)。
2.自定义 InvocaionHandler 并重写 invoke 方法,在invoke 方法中我们会调用目标方法(被代理类的方法)并自定义一些处理逻辑。
3.Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces.InvocationHandler h) 方法创建代理对象。
定义JDK动态代理类:
实现 InvocationHandler 接口
public class JDKInvocationHandler implements InvocationHandler {
//目标对象即就是被代理对象
private Object target;
public JDKInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理增强内容
System.out.println("我是中介,开始代理");
//通过反射调用被代理的方法
Object retVal = method.invoke(target, args);
//代理增强内容
System.out.println("我是中介,结束代理");
return retVal;
}
}
创建一个代理对象并使用
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
//创建一个代理类:通过被代理类、被代理实现的接口,方法调用处理器来创建
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new JDKInvocationHandler(target)
);
proxy.rentHouse();
}
}
代码简单讲解
主要是学习API的使用,按照Java API的规范来使用即可。
1.InvocationHandler
InvocationHandler 接口是java动态代理的关键接口之一,它定义了一个单一方法 invoke(),用于处理被代理对象的方法调用。
public interface InvocationHandler{
/**
*参数说明
*proxy:代理对象
*method:代理对象需要实现的方法,即其中重写的方法
*args:method所对应方法的参数
*/
public Object invoke(Object proxy,Method method,Object[] args){
throw Throwable;
}
}
通过实现 InvocationHandler 接口,可以被代理对象的方法进行功能增强。
2.Proxy
proxy 类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?> interfaces,
InvocationHandler h)throws IllegalArgumentException
{
}
这个方法一共有三个参数:
- Loader:类加载器,用于加载代理对象。
- interfaces:被代理类实现的一些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的一些类)。
- h:实现了InvocationHandler 接口的对象。
CGLIB动态代理
JDK动态代理有一个最致命的问题是其只能代理实现了接口的类。
有些场景下,我们的业务代码是直接实现的,并没有接口定义。为了解决这个问题,我们可以用CGLIB动态代理机制来解决。
CGLIB核心机制: 通过拦截器针对目标方法生成代理对象。
CGLIB动态代理实现步骤
- 定义一个类(被代理类)。
- 自定义MethodInterceptor 并重写intercept方法,intercept 用于增强目标方法,和JDK动态代理中的invoke 方法类似。
- 通过Enhancer 类的create()创建代理类。
接下来看下实现:
添加依赖
和JDK动态代理不同,CGLIB(Code Genration Library)实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
自定义MethodInterceptor(方法拦截器)
实现 MethodInterceptor 接口
public class CGLIBInterceptor implements MethodInterceptor {
//目标对象,即被代理对象
private Object target;
public CGLIBInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
//代理增强内容
System.out.println("我是中介,开始代理");
//通过反射调用被代理类的方法
Object retVal = methodProxy.invoke(target, objects);
//代理增强内容
System.out.println("我是中介,结束代理");
return retVal;
}
}
创建代理类,并使用
public class DynamicMain {
public static void main(String[] args) {
HouseSubject target = new RealHouseSubject();
HouseSubject proxy = (HouseSubject)
Enhancer.create(target.getClass(),new CGLIBInterceptor(target));
proxy.rentHouse();
}
}
·运行结果和上面的一样。
代码简单讲解
1.MethodInterceptor
MethodInterceptor和 JDK 动态代理中的 InvocationHandler 类似,它只定义了一个方法 intercept(),用于增强目标方法。
public interface MethodInterceptor extends Callback{
/**
*参数说明:
*o:被代理的对象
*method :目标方法(被拦截的方法,也就是需要增强的方法)
*objects:方法入参
*methodProxy:用于调用原始方法
*/
Object intercept(Object o,Method method,Object[] objects,
MethodProxy methodProxy)throws Throwable;
}
2.Enhancer.create()
Enhancer.create()用来生成一个代理对象。
public static Object create(Class type, Callback callback){
}
参数说明:
type:被代理类的类型(类或接口)。
callback:自定义方法拦截器MethodInterceptor。
五、总结
- AOP是什么?
AOP是一种思想,是对某一类事情的集中处理,Spring 框架实现了AOP,称之为SpringSOP。
- Spring AOP常见的实现方式?
- 基于注解@Aspect来实现
- 基于自定义注解(@annotation)来实现
- Spring AOP是基于动态代理实现的,有哪两种实现方式?
- 基本JDK动态代理实现
- 基于CGLIB动态代理实现
- 什么时候用JDK,什么时候用CGLIB?
代理普通类,只能用CGLIB;代理接口,JDK和CGLIB都可以使用。
- 动态代理与AOP的关系: