Spring框架基础(三)代理和AOP

         在上一篇Spring框架基础(二)IOC容器和DI 中我们介绍了IOC和DI,本篇介绍Spring另一个重要的概念--面向切面编程AOP。在介绍AOP之前,我们先介绍一下代理(Proxy)。代理是一种设计模式,提供了对目标对象的另一种访问方式,也就是说,我们可以通过代理对象访问到目标对象。这种做法的好处:可以在不改变目标对象的基础上,增加新的功能。常用的场景有:事务、日志、权限等等。

一、三种代理方式

1.1、静态代理

特点:代理对象要实现与目标对象一样的接口

举例:目标对象UserDao类,实现了IUserDao接口。那么代理对象UserDaoProxy 如下

public class UserDaoProxy implements IUserDao{

	// 接收保存目标对象
	private IUserDao target;
	public UserDaoProxy(IUserDao target) {
		this.target = target;
	}
	
	@Override
	public void save() {
		System.out.println("开始事务...");   // 额外的方法
		
		target.save(); 			// 执行目标对象的方法
		
		System.out.println("提交事务...");   //额外的方法
	}
	
}
public class App {

	public static void main(String[] args) {
		// 目标对象
		IUserDao target = new UserDao();
		
		// 代理
		IUserDao proxy = new UserDaoProxy(target);
		proxy.save();  // 执行的是,代理对象的save方法
	}
}

1.2、总结

1)可以在不修改目标对象功能前提下,扩展目标对象的功能。
2)缺点:
        因为代理对象需要实现与目标对象一样的接口,所以会存在很多代理类。同时接口一旦增加新的方法,目标对象与代理对象都要维护。

为了解决这个问题,我们引入动态代理

2.1、动态代理(也叫JDK代理、接口代理)

特点:目标对象需要实现接口,而代理对象不需要实现接口,是利用JDK的API动态的在内存中构建代理对象。
JDK中生成代理对象的API:

|-- Proxy
   static Object newProxyInstance(
          ClassLoader loader,        指定当前目标对象使用类加载器
          Class<?>[] interfaces,     目标对象实现的接口的类型
          InvocationHandler h        事件处理器
)  

举例:目标对象UserDao类,实现了IUserDao接口。那么代理对象UserDaoProxy 如下

public class ProxyFactory {

	// 维护一个目标对象
	private Object target;
	public ProxyFactory(Object target){
		this.target = target;
	}
	
	// 给目标对象,生成代理对象  
	public Object getProxyInstance() {
		return Proxy.newProxyInstance(
				target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(),
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args)
							throws Throwable {
						System.out.println("开启事务");
						
						// 执行目标对象方法
						Object returnValue = method.invoke(target, args);
						
						System.out.println("提交事务");
						return returnValue;
					}
				});
	}
}
public class App {

	public static void main(String[] args) {
		// 目标对象
		IUserDao target = new UserDao();
		
		// 给目标对象,创建代理对象
		IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
		
		// 执行方法   【代理对象】
		proxy.save();
	}
}

2.2、总结

1)代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能使用动态代理

2)缺点:目标对象必须实现接口

如果目标对象没有实现接口呢?这时就需要第三种代理,以子类的方式实现代理(也叫做cglib代理)

3.1、cglib代理(也叫子类代理)

特点:目标对象不需要实现接口,通过在内存中构建一个子类对象实现对目标对象功能的扩展。

举例:目标对象UserDao类,注意没有实现接口,代理对象UserDaoProxy 如下

public class ProxyFactory implements MethodInterceptor{
	
	// 维护目标对象
	private Object target;
	public ProxyFactory(Object target){
		this.target = target;
	}
	
	// 给目标对象创建代理对象
	public Object getProxyInstance(){
		//1. 工具类
		Enhancer en = new Enhancer();
		//2. 设置父类
		en.setSuperclass(target.getClass());
		//3. 设置回调函数
		en.setCallback(this);
		//4. 创建子类(代理对象)
		return en.create();
	}
	

	@Override
	public Object intercept(Object obj, Method method, Object[] args,
			MethodProxy proxy) throws Throwable {
		
		System.out.println("开始事务.....");
		
		// 执行目标对象的方法
		Object returnValue = method.invoke(target, args);
		
		System.out.println("提交事务.....");
		
		return returnValue;
	}

}
public class App {

	public static void main(String[] args) {
		// 目标对象
		UserDao target = new UserDao();
		
		// 代理对象
		UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
		
		// 执行代理对象的方法
		proxy.save();
	}
}

3.2、 总结

1)代理对象通过实现MethodInterceptor接口,构建子类对象。

2)缺点:由于这里是构建子类对象,这就要求我们的目标对象不能为final,否则会报错。如果我们目标对象的方法为final/static, 那么就不会被拦截,即不会执行目标对象额外的业务方法。

       为什么我们要介绍代理模式呢?因为在Spring的AOP编程中,底层就是通过代理来实现面向切面编程的。

       如果加入容器的目标对象有实现接口,用JDK代理;如果目标对象没有实现接口,用Cglib代理;


二、面向切面编程AOP

2.1、 常用概念

面向切面编程(aspect object programming,AOP)    
        让关注点代码(重复执行的代码)与业务代码分离
关注点
        重复执行的代码就叫做关注点
切面
        关注点形成的类,就叫切面(类)
切入点
        执行目标对象方法,动态植入切面代码。
        可以通过切入点表达式,指定拦截哪些类的哪些方法, 给指定的类在运行的时候植入切面类代码。

2.2、 实例

我们以保存学生信息为例,我们都知道,在进行数据库操作时,需要添加事务。通常我们操作如下

public class StrudentDao implements IStrudentDao{

	@Override
	public void save() {
		
		System.out.println("开始事务/异常");
		
		System.out.println("执行保存");
		
		System.out.println("提交事务/关闭");
	}
}

这里只是一个增加方法,对于修改,删除方法都需要添加事务

也就是说,System.out.println("开始事务/异常");  和System.out.println("提交事务/关闭");就是我们上面提到的关注点

真正的业务代码只有System.out.println("执行保存");这一行

如何通过面向切面编程的思想对我们的代码进行优化呢?

接下来我们介绍两种方式:XML配置方式和注解方式来实现面向切面编程

1)XML配置方式

首先,我们提取出关注点代码,形成一个切面类Aop.class,如下

// 切面类
public class Aop {
	//前置通知
	public void begin(){
		System.out.println("开始事务/异常");
	}
	//后置通知
	public void after(){
		System.out.println("提交事务/关闭");
	}
	//返回后通知
	public void afterReturning() {
		System.out.println("afterReturning()");
	}
	//异常通知
	public void afterThrowing(){
		System.out.println("afterThrowing()");
	}
	//环绕通知
	public void around(ProceedingJoinPoint pjp) throws Throwable{
		System.out.println("环绕前....");
		pjp.proceed();  // 执行目标方法
		System.out.println("环绕后....");
	}
	
}

然后在Spring的核心配置文件中进行配置

第一步就需要引入aop名称空间,第二步配置拦截方法,如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    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/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!-- dao 实例 -->
	<bean id="strudentDao" class="cn.zc.f_aop_xml.StrudentDao"></bean>
	
	<!-- 切面类 -->
	<bean id="aop" class="cn.zc.f_aop_xml.Aop"></bean>
	
	<!-- Aop配置 -->
	<aop:config>
		<!-- 定义一个切入点表达式: 拦截哪些方法 -->
		<aop:pointcut expression="execution(* cn.zc.f_aop_xml.*.*(..))" id="pt"/>

		<!-- 切面 -->
		<aop:aspect ref="aop">
			<!-- 环绕通知 -->
			<aop:around method="around" pointcut-ref="pt"/>
			<!-- 前置通知: 在目标方法调用前执行 -->
			<aop:before method="begin" pointcut-ref="pt"/>
			<!-- 后置通知: -->
			<aop:after method="after" pointcut-ref="pt"/>
			<!-- 返回后通知 -->
			<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
			<!-- 异常通知 -->
			<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
			
		</aop:aspect>
	</aop:config>
</beans>  

至此,优化完毕

2)注解方式

首先将dao类和Aop切面类加入容器,Aop切面类中的需要用到以下几个注解,如下

@Component
@Aspect  // 指定当前类为切面类
public class Aop {

	// 指定切入点表单式: 拦截哪些方法; 即为哪些类生成代理对象
	
	@Pointcut("execution(* cn.zc.e_aop_anno.*.*(..))")
	public void pointCut_(){
	}
	
	// 前置通知 : 在执行目标方法之前执行
	@Before("pointCut_()")
	public void begin(){
		System.out.println("开始事务/异常");
	}
	
	// 后置/最终通知:在执行目标方法之后执行  【无论是否出现异常最终都会执行】
	@After("pointCut_()")
	public void after(){
		System.out.println("提交事务/关闭");
	}
	
	// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
	@AfterReturning("pointCut_()")
	public void afterReturning() {
		System.out.println("afterReturning()");
	}
	
	// 异常通知: 当目标方法执行异常时候执行此关注点代码
	@AfterThrowing("pointCut_()")
	public void afterThrowing(){
		System.out.println("afterThrowing()");
	}
	
	// 环绕通知:环绕目标方式执行
	@Around("pointCut_()")
	public void around(ProceedingJoinPoint p) throws Throwable{
		System.out.println("环绕前通知....");
		p.proceed();  // 执行目标方法
		System.out.println("环绕后通知....");
	}
	
}

然后在Spring的核心配置文件中开启注解扫描以及AOP注解方式

	<!-- 开启注解扫描 -->
	<context:component-scan base-package="cn.zc.e_aop_anno"></context:component-scan>
	
	<!-- 开启aop注解方式 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

总结

本文首先介绍了何为代理,以及代理的三种方式

然后引入面向切面编程概念,以及通过XML和注解的方式进行实现

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值