Spring详解(五)------AOP

  这章我们接着讲 Spring 的核心概念---AOP,这也是 Spring 框架中最为核心的一个概念。

  PS:本篇博客源码下载链接:http://pan.baidu.com/s/1skZjg7r 密码:dn42

1、AOP 什么?

  AOP(Aspect Oriented Programming),通常称为面向切面编程。它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

  什么是切面,什么是公共模块,那么我们概念少说,直接通过一个实例来看看 AOP 到底是什么。

2、需求

  现在有一张表 User,然后我们要在程序中实现对 User 表的增加和删除操作。

  要求:增加和删除操作都必须要开启事务,操作完成之后要提交事务。

  User.java

package com.ys.aop.one;

public class User {
	private int uid;
	private String uname;
	public int getUid() {
		return uid;
	}
	public void setUid(int uid) {
		this.uid = uid;
	}
	public String getUname() {
		return uname;
	}
	public void setUname(String uname) {
		this.uname = uname;
	}

}

3、解决办法1:使用静态代理

  第一步:创建 UserService 接口

package com.ys.aop.one;

public interface UserService {
	//添加 user
	public void addUser(User user);
	//删除 user
	public void deleteUser(int uid);
}

第二步:创建 UserService的实现类

package com.ys.aop.one;

public class UserServiceImpl implements UserService{
	@Override
	public void addUser(User user) {
		System.out.println("增加 User");
	}
	@Override
	public void deleteUser(int uid) {
		System.out.println("删除 User");
	}
}

第三步:创建事务类 MyTransaction

package com.ys.aop.one;

public class MyTransaction {
	//开启事务
	public void before(){
		System.out.println("开启事务");
	}
	//提交事务
	public void after(){
		System.out.println("提交事务");
	}
}

第四步:创建代理类 ProxyUser.java

package com.ys.aop.one;

public class ProxyUser implements UserService{
	//真实类
	private UserService userService;
	//事务类
	private MyTransaction transaction;
	//使用构造函数实例化
	public ProxyUser(UserService userService,MyTransaction transaction){
		this.userService = userService;
		this.transaction = transaction;
	}
	@Override
	public void addUser(User user) {
		transaction.before();
		userService.addUser(user);
		transaction.after();
	}
	@Override
	public void deleteUser(int uid) {
		transaction.before();
		userService.deleteUser(uid);
		transaction.after();		
	}
}

测试:

@Test
	public void testOne(){
		MyTransaction transaction = new MyTransaction();
		UserService userService = new UserServiceImpl();
		//产生静态代理对象
		ProxyUser proxy = new ProxyUser(userService, transaction);
		proxy.addUser(null);
		proxy.deleteUser(0);
	}

结果:

  这是一个很基础的静态代理,业务类UserServiceImpl 只需要关注业务逻辑本身,保证了业务的重用性,这也是代理类的优点,没什么好说的。我们主要说说这样写的缺点:

  ①、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。

  ②、如果接口增加一个方法,比如 UserService 增加修改 updateUser()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

4、解决办法2:使用JDK动态代理 

   动态代理就不要自己手动生成代理类了,我们去掉 ProxyUser.java 类,增加一个 ObjectInterceptor.java 类

package com.ys.aop.two;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import com.ys.aop.one.MyTransaction;

public class ObjectInterceptor implements InvocationHandler{
	//目标类
	private Object target;
	//切面类(这里指事务类)
	private MyTransaction transaction;
	
	//通过构造器赋值
	public ObjectInterceptor(Object target,MyTransaction transaction){
		this.target = target;
		this.transaction = transaction;
	}
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		//开启事务
		this.transaction.before();
		//调用目标类方法
		method.invoke(this.target, args);
		//提交事务
		this.transaction.after();
		return null;
	}
	
}

  测试:

@Test
	public void testOne(){
		//目标类
		Object target = new UserServiceImpl();
		//事务类
		MyTransaction transaction = new MyTransaction();
		ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
		/**
		 * 三个参数的含义:
		 * 1、目标类的类加载器
		 * 2、目标类所有实现的接口
		 * 3、拦截器
		 */
		UserService userService = (UserService) Proxy.newProxyInstance(target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), proxyObject);
		userService.addUser(null);
	}

  结果:

  那么使用动态代理来完成这个需求就很好了,后期在 UserService 中增加业务方法,都不用更改代码就能自动给我们生成代理对象。而且将 UserService 换成别的类也是可以的。

  也就是做到了代理对象能够代理多个目标类,多个目标方法。

  注意:我们这里使用的是 JDK 动态代理,要求是必须要实现接口。与之对应的另外一种动态代理实现模式 Cglib,则不需要,我们这里就不讲解 cglib 的实现方式了。

不管是哪种方式实现动态代理。本章的主角:AOP 实现原理也是动态代理

 5、AOP 关键术语 

  1.target:目标类,需要被代理的类。例如:UserService

  2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法

  3.PointCut 切入点:已经被增强的连接点。例如:addUser()

  4.advice 通知/增强,增强代码。例如:after、before

  5. Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.

  6.proxy 代理类:通知+切入点

  7. Aspect(切面): 是切入点pointcut和通知advice的结合

  具体可以根据下面这张图来理解:

6、AOP 的通知类型  

Spring按照通知Advice在目标类方法的连接点位置,可以分为5类

  • 前置通知 org.springframework.aop.MethodBeforeAdvice
    • 在目标方法执行前实施增强,比如上面例子的 before()方法
  • 后置通知 org.springframework.aop.AfterReturningAdvice
    • 在目标方法执行后实施增强,比如上面例子的 after()方法
  • 环绕通知 org.aopalliance.intercept.MethodInterceptor
    • 在目标方法执行前后实施增强
  • 异常抛出通知 org.springframework.aop.ThrowsAdvice
    • 在方法抛出异常后实施增强
  • 引介通知 org.springframework.aop.IntroductionInterceptor

      在目标类中添加一些新的方法和属性

7、使用 Spring AOP 解决上面的需求

  我们只需要在Spring 的配置文件 applicationContext.xml 进行如下配置:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
       					   http://www.springframework.org/schema/beans/spring-beans.xsd
       					   http://www.springframework.org/schema/aop 
       					   http://www.springframework.org/schema/aop/spring-aop.xsd">
	<!--1、 创建目标类 -->
	<bean id="userService" class="com.ys.aop.UserServiceImpl"></bean>   
	<!--2、创建切面类(通知)  --> 
	<bean id="transaction" class="com.ys.aop.one.MyTransaction"></bean>
	
	<!--3、aop编程  
		3.1 导入命名空间
		3.2 使用 <aop:config>进行配置
				proxy-target-class="true" 声明时使用cglib代理
				如果不声明,Spring 会自动选择cglib代理还是JDK动态代理
			<aop:pointcut> 切入点 ,从目标对象获得具体方法
			<aop:advisor> 特殊的切面,只有一个通知 和 一个切入点
				advice-ref 通知引用
				pointcut-ref 切入点引用
		3.3 切入点表达式
			execution(* com.ys.aop.*.*(..))
			选择方法         返回值任意   包             类名任意   方法名任意   参数任意
	
	-->
	<aop:config>
		<!-- 切入点表达式 -->
		<aop:pointcut expression="execution(* com.ys.aop.*.*(..))" id="myPointCut"/>
		<aop:aspect ref="transaction">
			<!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
			<aop:before method="before" pointcut-ref="myPointCut"></aop:before>
            <aop:after-returning method="after" pointcut-ref="myPointCut"/>
		</aop:aspect>
	</aop:config>
</beans>

  测试:

@Test
	public void testAop(){
		ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
		UserService useService = (UserService) context.getBean("userService");
		useService.addUser(null);
	}

  结果:

  上面的配置我们在注释中写的很清楚了。这里我们重点讲解一下:

  ①、 切入点表达式,一个完整的方法表示如下:

execution(modifiers-pattern? ref-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
             类修饰符           返回值           方法所在的包                  方法名                     方法抛出的异常

  那么根据上面的对比,我们就很好理解:

execution(* com.ys.aop.*.*(..))
选择方法         返回值任意   包             类名任意   方法名任意   参数任意

  ②、springAOP 的具体加载步骤:

1、当 spring 容器启动的时候,加载了 spring 的配置文件

  2、为配置文件中的所有 bean 创建对象

  3、spring 容器会解析 aop:config 的配置

       1、解析切入点表达式,用切入点表达式和纳入 spring 容器中的 bean 做匹配

           如果匹配成功,则会为该 bean 创建代理对象,代理对象的方法=目标方法+通知

           如果匹配不成功,不会创建代理对象

  4、在客户端利用 context.getBean() 获取对象时,如果该对象有代理对象,则返回代理对象;如果没有,则返回目标对象

    说明:如果目标类没有实现接口,则 spring 容器会采用 cglib 的方式产生代理对象,如果实现了接口,则会采用 jdk 的方式

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java架构沉思录

Spring AOP失效之谜

AOP(Aspect Oriented Programming),即面向切面编程,其是OOP(Object Oriented Programming,面向对象编...

4374
来自专栏noteless

ServletRequest HttpServletRequest 请求方法 获取请求参数 请求转发 请求包含 请求转发与重定向区别 获取请求头字段

实际为   HttpServletRequest  或者  ServletRequest,   两者都为接口

995
来自专栏liulun

Nim教程【四】

这是国内第一个关于Nim的系列教程 先说废话 Screenshot (2).png 高雅的Nim Screenshot (3).png 方法 Screens...

20010
来自专栏木木玲

Netty 源码解析 ——— writeAndFlush流程分析

3334
来自专栏古时的风筝

Java Spring mvc 操作 Redis 及 Redis 集群

 本文原创,转载请注明:http://www.cnblogs.com/fengzheng/p/5941953.html 关于 Redis 集群搭建可以参考我的另...

22810
来自专栏Java架构沉思录

Spring AOP失效之谜

AOP(Aspect Oriented Programming),即面向切面编程,其是OOP(Object Oriented Programming,面向对象编...

762
来自专栏Java Edge

Java中的VO,PO等1.2.3.VO(value object) 值对象

32910
来自专栏PHP在线

了解PHP中Stream(流)的概念与用法

Stream是PHP开发里最容易被忽视的函数系列(SPL系列,Stream系列,pack函数,封装协议)之一,但其是个很有用也很重要的函数。Stream可以翻译...

3375
来自专栏一枝花算不算浪漫

[Java面试五]Spring总结以及在面试中的一些问题.

42619
来自专栏一枝花算不算浪漫

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

3219

扫码关注云+社区