Spring AOP 基础(一)

一、AOP是什么

Aop,Aspect Orientied Programing,面向切面编程,大部分情况下若代码中出现大量重复的代码,可以通过定义一个父类抽象类将此类代码提取出来,但是类似日志、事务管理等流程代码依附在业务代码之中,无法通过定义父类去提取出来。这类代码就像树木的年轮一样,将业务代码包裹在里面,只有通过横切编程的方式将其抽取出来独立成一个模块,使得业务代码清晰。

二、AOP中的基本术语:

1.连接点(Joinpoint)

程序执行的某个特定位置,一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就被称为“连接点”。Spring仅支持方法的连接点,即只能在方法调用前、调用后、抛出异常时以及方法调用前后 这些程序执行点织入增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位(前后等)。

2.切点(Pointcut):

每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点。连接点相当于数据库中的记录,而切点相当于查询条件,切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。在Spring中,切点通过Pointcut接口进行描述,使用类和方法作为连接点的查询条件,Spring AOP的规则 解析引擎负责解析切点所设定的查询条件 找到对应的连接点,确切的说是执行点而非连接点。因为连接点是方法执行前、执行后等包括方位信息的具体程序执行点,而切点只定位到某个方法上,所以如果希望定位到具体连接点上,还需要提供方位信息。

3.增强(Advice)

 增强是织入到目标类连接点上的一段程序代码。Spring提供的增强接口都是带方位名的:BeforeAdvice、AfterReturningAdvice等。所以只有结合切点和增强两者一起上阵才能确定特定的连接点并实施增强逻辑。

4.目标对象(Target)

增强逻辑的织入目标类或者目标对象。

5.介(Introduction)

引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态的为该业务类实现逻辑,让业务类成为这个接口的实现类。

6.织入(Weaving)

织入是将增强添加对目标类具体连接点上的过程,AOP像一台织布机,将目标类、增强或者引介通过AOP织到一起。AOP有三种织入的方式:编译期织入,这要求使用特殊的Java编译器;类装载期织入,这要求使用特殊的类装载器;动态代理织入,在运行期为目标类添加增强生成子类的方式;

Spring采用的是动态代理织入,而Aspectj采用的是编译期织入和类装载织入。

7.代理(Proxy)

一个类被AOP织入增强后,就会产生一个结果类,它是融合了原类和增强逻辑的代理类。根据不同的代理方式,代理类即可能是和原类具有相同接口的类,也可能是原类的子类,所以我们可以采用调用原类相同的方式去调用代理类。

8.切面(Aspect)

切面由切点和增强(引介)组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。

很显然,AOP的工作重心在于如何将增强应用于目标对象的连接点上,这里包含两个工作:第一,如何通过切点和增强定位到连接点上;第二,如何在增强中编写切面的代码。

三、动态代理

SpringAOP使用纯Java实现,不需要专门的编译过程,不需要特殊的类装载器,它在运行期通过代理的方式向目标类织入增强代码。Spring并不尝试提供最完整的AOP实现,相反,它侧重于提供一种和Spring IoC容器整合的AOP实现,用以解决企业级开发的问题。在Spring中,可以将SpringAOP、Ioc和Aspect整合到一起。SpringAOP使用的动态代理的技术,主要使用了两种代理机制:一种是基于JDK的动态代理,一种是基于CGLib的动态代理。

这里举一个简单的例子,吃饭是一个简单的业务,包括吃什么、怎么吃,而饭前洗手、饭后漱口是和吃饭并无关系的两种逻辑流程,我们可以定义一个接口Eat以及一个简单的实现MyEat,我们需要将这两种增强织入到吃饭这个具体的业务中去。

 

public interface Eat {
	public void eat(String food);
	public void drink(String water);
}
public class myEat implements Eat {
	public void eat(String food) {
		System.out.println("今天吃" + food);
	}
	public void drink(String water) {
		System.out.println("今天喝" + water);
	}
}

 

 

 

1.JDK动态代理实现

jdk动态代理是JDK自带的代理实现,其核心是java.lang.reflect包中的两个类:Proxy和InvocationHandler ,其中InvocationHandler 定义横切逻辑,通过反射机制调用目标类代码,动态将横切逻辑和业务逻辑编织到一起,Proxy通过newProxyInstance()方法动态去创建符合接口的实例。

定义一个handler代理

 

public class MyJdkProxy implements InvocationHandler {

	private Object target;

	public MyJdkProxy(Object target) {
		this.target = target;
	}

	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		System.out.println("饭前先洗手");
		System.out.println("代理的类:" + target.getClass().getName() + " 方法:"+ method.getName() + " 参数:" + args[0].toString());
		Object result = method.invoke(target, args);
		System.out.println("饭后要漱口");
		return result;
	}
}

调用代理去执行:

 

 

public class JdkProxyTest{
	public static void main(String[] args) {
		//第一步,创建目标类
		myEat my = new myEat();
		//第二步,目标类跟横切代码织到一起
		InvocationHandler handler = new MyJdkProxy(my);
		//第三步,创建代理
		Eat eat = (Eat) Proxy.newProxyInstance(handler.getClass().getClassLoader(), my.getClass().getInterfaces(), handler);
		//第四步,执行
		eat.eat("西红柿炒蛋");
		System.out.println();
		eat.drink("橙汁");
	}
}

输出结果:

 

 

饭前先洗手
代理的类:aopTest.myEat 方法:eat 参数:西红柿炒蛋
今天吃西红柿炒蛋
饭后要漱口

饭前先洗手
代理的类:aopTest.myEat 方法:drink 参数:橙汁
今天喝橙汁
饭后要漱口

 

 

2.CGLib动态代理实现

CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。
创建代理:

 

public class CglibProxy implements MethodInterceptor{
	Enhancer hancer = new Enhancer();
	public Object getProxy(Class clazz){
		hancer.setSuperclass(clazz);//设置要创建的子类
		hancer.setCallback(this);
		return hancer.create();//通过字节码技术创建子类
	}
	//obj 要代理的类 proxy生成的代理 intercept拦截父类方法调用
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("饭前先洗手");
		System.out.println("代理的类:" + obj.getClass().getName() + " 方法:" + method.getName() + " 参数:" + args[0].toString());
		Object result = proxy.invokeSuper(obj, args);
		System.out.println("饭后要漱口");
		return result;
	}
}

执行:

 

 

public class CglibProxyTest {
	public static void main(String[] args) {
		CglibProxy proxy = new CglibProxy();
		Eat eat = (Eat) proxy.getProxy(myEat.class);
		eat.eat("蛋炒西红柿");
		System.out.println();
		eat.drink("牛奶");
	}
}


输出结果:

 

饭前先洗手
代理的类:aopTest.myEat$$EnhancerByCGLIB$$640edb7e 方法:eat 参数:蛋炒西红柿
今天吃蛋炒西红柿
饭后要漱口

饭前先洗手
代理的类:aopTest.myEat$$EnhancerByCGLIB$$640edb7e 方法:drink 参数:牛奶
今天喝牛奶
饭后要漱口


3.小结

 

CGLib创建代理的性能比JDK动态代理差很多,但是创建后的代理对象的性能比JDK高很多,所以对于singleton单例的代理对象或者具有实例池的代理,使用CGLib动态代理技术,反之使用JDK技术;另外,cglib采用动态创建子类的方式生成代理对象,所以不能代理final类以及方法

AopProxy接口提供了两个final类型的实现类:CGlib2AopProxy和JdkDynamicAopProxy ,如果通过ProxyFactory的setInterface接口指定针对接口的代理,则使用了jdk代理;如果是针对类的代理就使用了cglib代理

JDK动态代理和CGLib代理三个改进:
1.目标类的所有方法都添加了横切逻辑,然而更多时候只是需要对某些特定方法添加横切逻辑(对应切点Pointcut)
2.我们通过硬编码的方式指定了横切逻辑的织入点(对应增强Advice具体织入点)
3.手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写响应的创建代码(Spring通过切面Advisor将以上两点组装起来)

而这些改进,正是SpringAOP要做的事情

 

参考文章:《Spring 3.x 企业应用开发实战

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值