[Spring框架]Spring AOP基础入门总结一.

时间:2022-02-06 02:20:44

前言:
前面已经有两篇文章讲了Spring IOC/DI 以及 使用xml和注解两种方法开发的案例, 下面就来梳理一下Spring的另一核心AOP.

一, 什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码.

Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码.

二,AOP开发中的专业术语

  1. Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点.
  2. Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义.
  3. Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
  4. Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field.
  5. Target(目标对象):代理的目标对象
  6. Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程.
     spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入.
  7. Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类Aspect(切面): 是切入点和通知(引介)的结合

三, 动态代理
spring AOP核心技术就是使用了Java 的动态代理技术, 这里简单的总结下JDK和CGLIB两种动态代理机制.

概念:
当一个对象(客户端)不能或者不想直接引用另一个对象(目标对象),这时可以应用代理模式在这两者之间构建一个桥梁--代理对象。按照代理对象的创建时期不同,可以分为两种:
静态代理:程序员事先写好代理对象类,在程序发布前就已经存在了;
动态代理:应用程序发布后,通过动态创建代理对象。
其中动态代理又可分为:JDK/Cglib 动态代理.

3.1 JDK动态代理

此时代理对象和目标对象实现了相同的接口,目标对象作为代理对象的一个属性,具体接口实现中,可以在调用目标对象相应方法前后加上其他业务处理逻辑。

代理模式在实际使用时需要指定具体的目标对象,如果为每个类都添加一个代理类的话,会导致类很多,同时如果不知道具体类的话,怎样实现代理模式呢?这就引出动态代理。

JDK动态代理只能针对实现了接口的类生成代理。
代码实例:
UserService.java:

public interface UserService {
public void save(); public void update(); public void delete(); public void find();
}

UserServiceImpl.java:

 public class UserServiceImpl implements UserService {

     @Override
public void save() {
System.out.println("保存用户...");
} @Override
public void update() {
System.out.println("修改用户...");
} @Override
public void delete() {
System.out.println("删除用户...");
} @Override
public void find() {
System.out.println("查询用户...");
} }

MyJdbProxy.java:

 /**
* 使用JDK的动态代理实现代理机制
*
*/
public class MyJdbProxy implements InvocationHandler{ private UserService userService; public MyJdbProxy(UserService userService){
this.userService = userService;
} public UserService createProxy(){
// 生成UserSErvice的代理:
UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
userService.getClass().getClassLoader(), userService.getClass()
.getInterfaces(), this);
return userServiceProxy;
} @Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 判断是否是save方法:
if("save".equals(method.getName())){
// 增强:
System.out.println("权限校验===========");
return method.invoke(userService, args);
}
return method.invoke(userService, args);
} }

SpringDemo.java 测试类:

 public class SpringDemo1 {

     @Test
// 没有代理的时候的调用方式
public void demo1() {
// 创建目标对象
UserService userService = new UserServiceImpl(); userService.save();
userService.update();
userService.delete();
userService.find();
} @Test
// 使用代理
public void demo2() {
// 创建目标对象
UserService userService = new UserServiceImpl();
UserService proxy = new MyJdbProxy(userService).createProxy(); proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}


3.2 CGLIB 动态代理

CGLIB(CODE GENERLIZE LIBRARY)代理是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的。

如果目标对象没有实现接口,则默认会采用CGLIB代理;

如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库,并在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>
代码实例:
CustomerService.java:

 public class CustomerService {
public void save(){
System.out.println("保存客户...");
}
public void update(){
System.out.println("修改客户...");
}
public void delete(){
System.out.println("删除客户...");
}
public void find(){
System.out.println("查询客户...");
}
}

MyCglibProxy.java:

 /**
* 使用Cglib产生代理
*
*/
public class MyCglibProxy implements MethodInterceptor{ private CustomerService customerService; public MyCglibProxy(CustomerService customerService){
this.customerService = customerService;
} public CustomerService createProxy(){
// 创建核心类:
Enhancer enhancer = new Enhancer();
// 设置父类:
enhancer.setSuperclass(customerService.getClass());
// 设置回调:
enhancer.setCallback(this);
// 创建代理:
CustomerService customerServiceProxy = (CustomerService) enhancer.create();
return customerServiceProxy;
} @Override
public Object intercept(Object proxy, Method method, Object[] arg,
MethodProxy methodProxy) throws Throwable {
if("delete".equals(method.getName())){
Object obj = methodProxy.invokeSuper(proxy, arg);
System.out.println("日志记录==========");
return obj;
}
return methodProxy.invokeSuper(proxy, arg);
}
}

SpringDemo.java 测试类:

 public class SpringDemo2 {

     @Test
public void demo1(){
CustomerService customerService = new CustomerService();
customerService.save();
customerService.update();
customerService.delete();
customerService.find();
} @Test
public void demo2(){
CustomerService customerService = new CustomerService();
// 产生代理:
CustomerService proxy = new MyCglibProxy(customerService).createProxy();
proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}

AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。

四, Spring的传统的AOP:基于ProxyFactoryBean的方式的代理

4.1 Spring的通知类型

前置通知 org.springframework.aop.MethodBeforeAdvice

  在目标方法执行前实施增强

后置通知 org.springframework.aop.AfterReturningAdvice

  在目标方法执行后实施增强

环绕通知 org.aopalliance.intercept.MethodInterceptor

  在目标方法执行前后实施增强

异常抛出通知 org.springframework.aop.ThrowsAdvice

  在方法抛出异常后实施增强

 

4.2 Spring的切面类型

Advisor : 代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截.(不带有切入点的切面,默认增强类中所有方法)

PointcutAdvisor : 代表具有切点的切面,可以指定拦截目标类哪些方法.(带有切入点的切面)

4.3 Spring传统AOP的快速入门

 4.3.1  不带有切入点的切面开发

  AOP开发需要引入的相关jar包:
  [Spring框架]Spring AOP基础入门总结一.

ProductService.java:

 /**
* 商品业务层接口
*
*
*/
public interface ProductService {
public void save(); public void update(); public void delete(); public void find();
}

ProductServiceImpl.java:

 /**
* 商品业务层实现类
*
*/
public class ProductServiceImpl implements ProductService { @Override
public void save() {
System.out.println("保存商品...");
} @Override
public void update() {
System.out.println("修改商品...");
} @Override
public void delete() {
System.out.println("删除商品...");
} @Override
public void find() {
System.out.println("查询商品...");
} }

MyBeforeAdvice.java:

 /**
* 自定义的前置增强:
*
*/
public class MyBeforeAdvice implements MethodBeforeAdvice { @Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("前置增强=============");
} }

SpringDemo.java 测试类:

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
@Resource(name="productServiceProxy")
private ProductService productService; @Test
/**
* 传统方式:
*/
public void demo1(){
productService.save();
productService.update();
productService.delete();
productService.find();
}
}

  4.3.1  带有切入点的切面开发

OrderService.java:

 public class OrderService {
public void save(){
System.out.println("添加订单...");
}
public void update(){
System.out.println("修改订单...");
}
public void delete(){
System.out.println("删除订单...");
}
public void find(){
System.out.println("查询订单...");
}
}

MyAroundAdvice.java:

 /**
* 定义的环绕增强:
*
*/
public class MyAroundAdvice implements MethodInterceptor{ @Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前增强===============");
// 执行目标方法:
Object obj = methodInvocation.proceed();
System.out.println("环绕后增强===============");
return obj;
} }

SpringDemo.java 测试类:

 @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo4 { @Resource(name="orderServiceProxy")
private OrderService orderService; @Test
public void demo1(){
orderService.save();
orderService.update();
orderService.delete();
orderService.find();
}
}

applicationContext.xml 配置文件:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置目标对象 -->
<bean id="productService" class="cn.augmentum.aop.demo3.ProductServiceImpl"></bean> <!-- 配置前置增强 -->
<bean id="myBeforeAdvice" class="cn.augmentum.aop.demo3.MyBeforeAdvice"/> <!-- 配置生成代理 -->
<bean id="productServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- ProxyFactoryBean常用可配置属性
target : 代理的目标对象
proxyInterfaces : 代理要实现的接口
如果多个接口可以使用以下格式赋值
<list>
<value></value>
....
</list>
proxyTargetClass : 是否对类代理而不是接口,设置为true时,使用CGLib代理
interceptorNames : 需要织入目标的Advice
singleton : 返回代理是否为单实例,默认为单例
optimize : 当设置为true时,强制使用CGLib
-->
<property name="target" ref="productService"/>
<property name="proxyInterfaces" value="cn.augmentum.aop.demo3.ProductService"/>
<property name="interceptorNames" value="myBeforeAdvice"/> </bean> <bean id="orderService" class="cn.augmentum.aop.demo4.OrderService"/> <bean id="myAroundAdvice" class="cn.augmentum.aop.demo4.MyAroundAdvice"/> <bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 正则表达式: . :代表任意字符 *:任意次数 -->
<!-- <property name="pattern" value=".*"/> -->
<property name="pattern" value="cn\.augmentum\.aop\.demo4\.OrderService\.save"/>
<property name="advice" ref="myAroundAdvice"/>
</bean> <bean id="orderServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="orderService"/>
<property name="proxyTargetClass" value="true"/>
<property name="interceptorNames" value="advisor"/>
</bean> </beans>

基于Spring FactoryBean代理的总结:

Spring会根据类是否实现接口采用不同的代理方式:

* 实现接口:JDK动态代理.

* 没有实现接口:CGLIB生成代理.

基于ProxyFactoryBean的方式生成代理的过程中不是特别理想:

* 配置繁琐,不利为维护.

* 需要为每个需要增强的类都配置一个ProxyFactoryBean.

 

五, Spring的传统的AOP:基于自动代理

5.1 自动代理和基于ProxyFactoryBean代理方式比较:

自动代理基于BeanPostProcessor(前一篇文章讲过的工厂钩子)完成的代理.

* 在类的生成过程中就已经是代理对象.

基于ProxyFactoryBean方式代理:

* 先有目标对象,根据目标对象产生代理.

5.2自动代理的方式:
BeanNameAutoProxyCreator:基于Bean名称的自动代理

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置目标对象 -->
<bean id="productService" class="cn.augmentum.aop.demo3.ProductServiceImpl"></bean>
<bean id="orderService" class="cn.augmentum.aop.demo4.OrderService"/> <!-- 配置前置增强 -->
<bean id="myBeforeAdvice" class="cn.augmentum.aop.demo3.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="cn.augmentum.aop.demo4.MyAroundAdvice"/> <!-- 基于Bean名称的自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Service"/>
<property name="interceptorNames" value="myBeforeAdvice"/>
</bean>
</beans>

 

DefaultAdvisorAutoProxyCreator:基于切面信息的自动代理

 <beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 配置目标对象 -->
<bean id="productService" class="cn.augmentum.aop.demo3.ProductServiceImpl"></bean>
<bean id="orderService" class="cn.augmentum.aop.demo4.OrderService"/> <!-- 配置前置增强 -->
<bean id="myBeforeAdvice" class="cn.augmentum.aop.demo3.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="cn.augmentum.aop.demo4.MyAroundAdvice"/> <!-- 配置切面信息 -->
<bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="myAroundAdvice"/>
<!-- <property name="pattern" value="cn\.augmentum\.aop\.demo4\.OrderService\.save"/> -->
<property name="patterns" value="cn\.itcast\.aop\.demo4\.OrderService\.save,cn\.augmentum\.aop\.demo3\.ProductService\.update"/>
</bean> <!-- 配置基于切面信息自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
</beans>

Spring AOP最基础的知识大概就是这些了, 由于篇幅的限制及阅读的观感, 所以打算再写个Spring AOP基础入门二 来总结下 Spring基于AspectJ的AOP的开发, 这个地方才是重点, 当然这一篇博文的知识也是有利于大家对Spring AOP有了一个整体的认识了. 文章内容皆是自己学习工作整理所得, 如若有问题 欢迎指正.