好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Spring AOP 与代理的概念与使用

今天带来Spring AOP 与代理的概念与使用教程详解

一、AOP 的基本概念

1.1 什么是 AOP

Aspect Oriented Programming,面向切面编程。

就跟我们说 OOP 是面向对象一样,AOP 是面向切面的。切面是分散在应用中的一个标准代码或功能。切面通常与实际的业务逻辑不同(例如,事务管理)。每个切面专注于一个特定的环切功能。

这里的切面呢,可以理解为横切。比如在所有的 DAO 层方法上加上一个同样的切面,功能是记录日志;又或者在某个接口上应用一个切面,作用是检查权限。

AOP 是基于代理来实现的。而代理又分为静态代理和动态代理。两者的区别在于代理类于何时生成。

下面我们讲讲代理是怎么回事?

1.2 代理与 Spring AOP

代理分为静态代理和动态代理:

静态代理:代理类在编译阶段生成,程序运行前就存在。包括:AspectJ 静态代理、JDK 静态代理 动态代理:代理类在程序运行时创建。包括:JDK 动态代理、CGLib 动态代理

Spring AOP 原理:

JDK Proxy:interface based CGLib Proxy: class based

Spring AOP 中默认使用 JDK 动态代理,通过反射获取被代理的类,这个类必须实现一个接口。如果目标类没有实现接口,就会默认使用 CGLIB Proxy 来动态生成代理目标类,后者是被代理类的子类。

可以通过获取代理对象并打印的方式来查看其类型(JDK Proxy 下是 com.sun.prxy, CGlib 下是子类.

AspectJ: 用特定的编译器和语法,在编译时增强,实现了静态代理技术。

1.3 Spring AOP 与 AspectJ 的区别

AspectJ 是一套完整的 AOP 解决方案,而 Spring AOP 并不是 ―― 它只是在 Spring 框架下满足其使用要求的一个解决方法,比如 Spring AOP 仅支持对方法使用切面。

二、静态代理

2.1 AspectJ 静态代理

基于特殊的编译器和语法。这里不多介绍了。

2.2 JDK 静态代理

实际上是利用实现一个具体的代理类来调用业务类。代理类持有了一个业务类的引用。

更概况地说,JDK 静态代理体现的是一种设计模式。

缺点很明显,代码冗余,难以维护。

这里以?借书?和?还书?这两个行为来作为一个示例:

编写一个 BookService 接口:

public?interface?BookService?{

????boolean?borrow(String?id,?String?userName);

????boolean?reBack(String?id,?String?userName);
}

然后实现这个接口:

public?class?BookServiceImpl?implements?BookService?{

????@Override
????public?boolean?borrow(String?id,?String?userName)?{
????????System.out.println(userName?+?"?借书:"?+?id);
????????return?true;
????}

????@Override
????public?boolean?reBack(String?id,?String?userName)?{
????????System.out.println(userName?+?"?还书:"?+?id);
????????return?true;
????}
}

下面我们来编写? BookService ?的代理类:

public?class?BookProxy?implements?BookService?{

????private?BookServiceImpl?bookService;

????public?BookProxy(BookServiceImpl?bookService)?{
????????this.bookService?=?bookService;
????}

????@Override
????public?boolean?borrow(String?id,?String?userName)?{
????????boolean?res?=?false;
????????if?(check())?{
????????????res?=?bookService.borrow(id,?userName);
????????}
????????addLog();
????????return?res;
????}

????@Override
????public?boolean?reBack(String?id,?String?userName)?{
????????boolean?res?=?false;
????????if?(check())?{
????????????res?=?bookService.reBack(id,?userName);
????????}
????????addLog();
????????return?res;
????}

????//
????private?boolean?check()?{
????????System.out.println("检查权限");
????????return?true;
????}

????private?void?addLog()?{
????????System.out.println("操作完成");
????}
}

编写一个测试类:

public?class?MainTest?{

????public?static?void?main(String[]?args)?{
????????BookProxy?proxy?=?new?BookProxy(new?BookServiceImpl());
????????proxy.borrow("123",?"eknown");
????????proxy.reBack("234",?"java");
????}
}

这里我们可以看到,JDK 静态代理就是说在原来的实现类上套一层?代理。它好像是体现了代理模式,但实际上并没有带来太多的好处。代码相当冗余,也不利于维护。

真正体现代理模式好处的还是动态代理,下面我们来看看动态代理的原理。

三、动态代理

动态代理是程序运行时,由 JVM 根据反射等机制动态生成代理类的。

也就是说,程序运行前,我们仅仅定义了代理的规则,而不知道代理类具体长什么样,这不像上面的静态代理里,我们完整地定义了代理对象。

3.1 JDK 动态代理

JDK 动态代理是基于接口的。

我们可以通过实现? InvocationHandler ?接口来手动创建一个 JDK 代理类。

首先需要定义一个接口,让业务类和代理类都实现这个接口。

然后编写一个 InvocationHandler 接口的实现类:

public?class?BookProxy?implements?InvocationHandler?{

????//?被该代理类处理的业务类
????private?BookService?bookService;

????public?BookProxy(BookService?bookService)?{
????????this.bookService?=?bookService;
????}

????@Override
????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
????????Object?res?=?null;
????????if?(check())?{
????????????//?调用实际的?method,参数是?接口?+?参数
????????????res?=?method.invoke(bookService,?args);
????????}
????????addLog();
????????return?res;
????}
????
????private?boolean?check()?{
????????System.out.println("检查权限");
????????return?true;
????}

????private?void?addLog()?{
????????System.out.println("操作完成");
????}
}

测试:

public?class?MainTest?{

????public?static?void?main(String[]?args)?{
????????//?创建被代理的实际业务类
????????BookServiceImpl?bookServiceImpl?=?new?BookServiceImpl();

????????ClassLoader?classLoader?=?bookServiceImpl.getClass().getClassLoader();
????????//?获取所有的接口方法
????????Class[]?interfaces?=?bookServiceImpl.getClass().getInterfaces();

????????//?构造?Handler
????????InvocationHandler?invocationHandler?=?new?BookProxy(bookServiceImpl);

????????//?创建代理
????????Object?obj?=?Proxy.newProxyInstance(classLoader,?interfaces,?invocationHandler);
????????BookService?bookService?=?(BookService)?obj;
????????bookService.borrow("abc",?"eknown");
????????bookService.reBack("c23",?"py");
????}
}

3.2 CGLIB 动态代理

CGLIB 代理的原理是:让代理类继承业务类(也就自动拥有了业务类的所有非 final 的 public 方法)

我们这里手动编写一个 CGLIB 的代理试试看。

首先我们有一个 BookServiceImpl 业务类,这个业务类可以实现接口,也可以就是单纯的一个业务类。

然后我们定义一个 BookCglibProxy 类:

public?class?BookCglibProxy?implements?MethodInterceptor?{

????@Override
????public?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{
????????check();
????????//?调用实际的?method
????????Object?obj?=?methodProxy.invokeSuper(o,?objects);
????????addLog();
????????return?obj;
????}

????private?boolean?check()?{
????????System.out.println("检查权限");
????????return?true;
????}

????private?void?addLog()?{
????????System.out.println("操作完成");
????}
}

测试类:

public?class?CglibTest?{
????public?static?void?main(String[]?args)?{
????????BookServiceImpl?bookServiceImpl?=?new?BookServiceImpl();

????????BookCglibProxy?proxy?=?new?BookCglibProxy();

????????//?cjlib?中的增强器,用于创建动态代理(被代理类的子类)
????????Enhancer?enhancer?=?new?Enhancer();
????????//?设置要被代理的类
????????enhancer.setSuperclass(bookServiceImpl.getClass());
????????//?设置回调
????????enhancer.setCallback(proxy);

????????//?强转成父类
????????BookServiceImpl?proxyResult?=?(BookServiceImpl)?enhancer.create();
????????proxyResult.borrow("12333",?"ye");
????????proxyResult.reBack("123",?"fe");
????}
}

在第一节我们提到过 Spring AOP 是基于 JDK 动态代理和 CGLIB 动态代理的。下面我们来 Spring AOP 的一些基本案例。

四、Spring AOP 实例

AOP 中一些概念词汇,通过这些词汇,我们可以对 AOP 有更高一层的抽象。

Aspect - 切面,分散在应用中的一个标准代码或功能。切面通常与实际的业务逻辑不同(例如,事务管理)。每个切面专注于一个特定的环切功能。 Joinpoint - 连接点,是程序执行过程中的特定点,比如方法执行、构造器调用、字段赋值 Advice - 通知,切面在某个连接点采取的操作。Advice 有 5 种类型。 Pointcut - 切入点,一个匹配连接点的正则表达式。每当连接点匹配了一个切入点时,一个特定的通知就会被执行。 Weaving - 织入,指的是将切面和目标对象连接起来以创建代理对象的过程。

Spring AOP 有两种实现方式:基于 XML 或基于注解。更流行、更方便的是后者。(阿 sir,不会还有人用 XML 来做 Bean 的配置文件吧?)

4.1 基于 XML 的实例

首先定义一下接口和实现类(没有注解的!)。再编写一个代理类:

这里的代理类方法以 JoinPoint 为参数即可:

public?class?BookAspect?{

????public?void?checkUser(JoinPoint?point)?{
????????System.out.println("-----before-----");
????????Object[]?args?=?point.getArgs();
????????for?(Object?arg?:?args)?{
????????????System.out.println(arg);
????????}
????????System.out.println("检查用户权限...");
????}

????public?void?saveLog(JoinPoint?point)?{
????????System.out.println("-----after-----");
????????Object[]?args?=?point.getArgs();
????????for?(Object?arg?:?args)?{
????????????System.out.println(arg);
????????}
????????System.out.println("请求完毕,记录日志...");
????}
}

然后编写 Spring 的配置文件:

 


???? 
????
????

???? 
???????? 
???????? 
???????????? 
???????????? 
???????????? 
???????????? 
???????????? 
???????? 
???? 
 

运行测试:

public?class?AopXMLTest?{

????public?static?void?main(String[]?args)?{
????????ApplicationContext?context?=?new?ClassPathXmlApplicationContext("SpringAop.xml");

????????BookService?bookService?=?context.getBean("bookService",?BookService.class);
????????bookService.borrow("123",?"eknown");
????????bookService.reback("123",?"eknown");
????}
}

基于 XML 配置的 Spring 现在已经很少使用了。下面我们来看看如何基于注解使用 Spring AOP

4.2 基于注解的实例

这里以一个使用 SpringBoot 框架的 Web 项目作为简单的实例。

首先创建一个 SpringBoot 项目,写好 Controller、Service、DAO 层的基本类。(示例源码中没有使用 Mybatis 等持久层框架,而是用 Map 来模拟数据的存取)

下面我们针对 UserService 接口类,添加切面。

@Aspect
@Component
public?class?UserAspect?{

????@Before(value?=?"execution(*?com.example.springaopdemo.boot.UserService.*(..))")
????public?void?checkUser(JoinPoint?point)?{
????????System.out.println("-----before-----");
????????Object[]?args?=?point.getArgs();
????????for?(Object?arg?:?args)?{
????????????System.out.println(arg);
????????}
????????System.out.println("检查..."?+?point);
????}

????@After(value?=?"execution(*?com.example.springaopdemo.boot.UserService.*(..))")
????public?void?saveLog(JoinPoint?point)?{
????????System.out.println("-----after-----");
????????Object[]?args?=?point.getArgs();
????????for?(Object?arg?:?args)?{
????????????System.out.println(arg);
????????}

????????//?这里可以使用?point.getTarget()?获取到切面对应的?bean
????????//Object?target?=?point.getTarget();
????????//UserService?userService?=?(UserService)?target;
????????//List ?userList?=?userService.findAll();

????????System.out.println("请求完毕,记录日志..."?+?point);
????}

????@Around(value?=?"execution(*?com.example.springaopdemo.boot.UserService.save(..))")
????public?Object?saveAround(ProceedingJoinPoint?point)?{
????????System.out.println("around-before");
????????Object?obj?=?null;
????????try?{
????????????obj?=?point.proceed();
????????}?catch?(Throwable?throwable)?{
????????????throwable.printStackTrace();
????????}
????????System.out.println("around-after");
????????return?obj;
????}

}

示例中使用了 @Before/@After/@Aroud 三个注解,value 中使用切点表达式,分别匹配了 UserService 接口的所有方法和单个 save 方法。

我们还可以通过切点表达式匹配自定义的注解,比如实现一个 UserMonitor 注解,然后定义其切点方法:

public?@interface?UserMonitor?{

????String?value()?default?"";

????int?roleLimit()?default?0;

}

切点:

@Around("@annotation(com.example.springaopdemo.boot.UserMonitor)")
????public?Object?userRolePointCut(ProceedingJoinPoint?point)?{
????????System.out.println("检查用户权限...");
????
???????//?获取参数
????????Object[]?args?=?point.getArgs();
????????Class []?argTypes?=?new?Class[point.getArgs().length];
????????for?(int?i?=?0;?i? 

以上就是Spring AOP 与代理的概念与使用的详细内容,更多关于Spring AOP 与代理的资料请关注其它相关文章!


以上就是关于Spring AOP 与代理的概念与使用全部内容,感谢大家支持。

查看更多关于Spring AOP 与代理的概念与使用的详细内容...

  阅读:53次