Spring框架理解(三)

 Spring AOP

(一)Spring AOP简介

(1)什么是 AOP

        AOP 的全称是 Aspect-Oriented Programming ,即面向切面编程(也称面向方面编程)。 它是面向对象编程 (OOP) 的一种补充。AOP 采取横向抽取机制。将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方 。

        目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。AspectJ 是一个基于 Java 语言的 AOP 框架。

(2)AOP术语

        包括 Aspect 、Joinpoint 、Pointcut 、Advice 、Target Object 、Proxy 和 Weaving ,对于这些专业术语的解释,具体如下

  • Aspect (切面):    在实际应用中,切面通常是指封装的用于横向插入系统功能(如事务、曰志等)的类。 该类要被 Spring 容器 识别为切面,需要在配置文件中通过<bean>元素指定 。
  • Joinpoint (连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个操作,例如方法的调用或异常的抛出 。 在 Spring AOP 中,连接点就是指方法的调用 。
  • Pointcut (切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点。 通常在程序中,切入点指的是类或者方法名,如某个通知要应用到所有以 add 开头 的方法中,那么所有满足这一规则的方法都是切入点 。
  • Advice( 通知/增强处理): AOP 框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码 。 可以将其理解为切面类中的方法,它是切面的具体实现 。
  • Target Object (目标对象):是指所有被通知的对象,也称为被增强对象 。 如果 AOP 框架采用的是动态的 AOP 实现,那么该对象就是一个被代理对象 。
  • Proxy (代理):将通知应用到目标对象之后,被动态创建的对象 。
  • Weaving (织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

(二)动态代理

AOP 中的代理就是由 AOP 框架动态生成的一个对象,该对象可以作为目标对象使用 。 Spring 中的 AOP 代理,可以是 JDK 动态代理,也可以是 CGLIB代理。Spring中的 AOP 代理默认就是使用 JDK 动态代理的方式来实现的。
 

(1)JDK 动态代理

JDK 动态代理是通过 java.lang. reflect. Proxy 类来实现的,我们可以调用 Proxy 类的newProxylnstanceO方法来创建代理对象 。

创建接口 UserDao

public interface UserDao {
	//新增
	public void addUser();
	//删除
	public void deleteUser();

}

创建UserDaoImpl类

public class UserDaolmpl implements UserDao {
	@Override
	public void addUser() {
		System.err.println("新增成功");
	}
	@Override
	public void deleteUser() {
		System.err.println("删除成功");
	}
}

创建切面类 MyAspect
 

/**
 * 切面类 可以存在多个Advice
 * 
 * @author allen
 *
 */
public class MyAspect {

	public void check_Permissions() {
		System.out.println("模拟检查权限. . . ");
	}

	public void log() {
		System.out.println(" 模拟记录日志. . . ");
	}
}

创建代理类 JdkProxy,实现接口InvocationHandler,重载invoke
 

public class JdkProxy implements InvocationHandler{
	
	private UserDao userDao;
	
	//创建代理方法
	public Object createProxy(UserDao userDao) {
		this.userDao = userDao ;
		// 1.类加载器
		 ClassLoader classLoader = JdkProxy.class.getClassLoader() ;
		 // 2.被代理对象实现的所有接口
		 Class[] clazz = userDao.getClass().getInterfaces();
		// 3. 使用代理类,进行增强 ,返回的是代理后的对象
		 return Proxy.newProxyInstance(classLoader ,clazz , this);
	}
	

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//声明切面
		MyAspect aspect = new MyAspect();
		//前增强
		aspect.check_Permissions();
		//在目标类上调用方法,并传入参数
		Object obj = method.invoke(userDao , args);
		//后增强
		aspect.log();
		return obj ;
	}

}

测试类

public class JdkTest {
	public static void main(String[] args) {
		// 创建代理对象
		JdkProxy jdkproxy = new JdkProxy();
		// 创建目标对象
		UserDao userDao = new UserDaolmpl();
		// 从代理对象中获取增强后的日标对象
		UserDao userDao1 = (UserDao) jdkproxy.createProxy(userDao);
		// 执行方法
		userDao1.addUser();
	}
}

JDK动态代理的缺陷: 使用动态代理的对象必须实现一个或多个接口,也就是说createProxy返回的必定是接口,而不是类。
 

(2)CGLIB代理

如果要对没有实现接口的类进行代理,那么可以使用 CGLIB代理。

创建UserDao类

public class UserDao {
	public void addUser() {
		System.err.println("新增成功");
	}

	public void deleteUser() {
		System.err.println("删除成功");
	}
}

创建代理类

public class CglibProxy implements MethodInterceptor {

	// 代理方法
	public Object createProxy(Object target) {
		// 创建一个动态类对象
		Enhancer enhancer = new Enhancer();
		// 确定需要增强的类,设置其父类
		enhancer.setSuperclass(target.getClass());
		// 添加回调函数
		enhancer.setCallback(this);
		// 返回创建的代理类
		return enhancer.create();
	}

	/**
	 * proxy CGlib 根据指定父类生成的代理对象 
	 * method 拦截的方法 
	 * args 拦截方法的参数数组
	 * methodProxy方法的代理对象,用于执行父类的方法
	 */
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

		// 创建切面类对象
		MyAspect myAspect = new MyAspect();
		// 前增强
		myAspect.check_Permissions();
		// 目标方法执行
		Object obj = methodProxy.invokeSuper(proxy, args);
		// 后增强
		myAspect.log();
		return obj;
	}

}

测试类

public class CgLibTest {
	public static void main(String[] args) {
		// 创建代理对象
		CglibProxy cglibProxy = new CglibProxy();
		// 创建目标对象
		UserDao userDao = new UserDao();
		// 从代理对象中获取增强后的日标对象
		UserDao userDao1 = (UserDao) cglibProxy.createProxy(userDao);
		// 执行方法
		userDao1.addUser();
	
	}
}

(三) Spring  AOP 实现

(1)Spring 的通知类型

Spring 中的通知按照在目标类方法的连接点位置,可以分为以下 5 种类型 。

  • org.aopalliance.intercept. MethodInterceptor (环绕通知)  在目标方法执行前后实施增强,可以应用于曰志、事务管理等功能
  • org.springframework.aop.MethodBeforeAdvice (前置通知)   在目标方法执行前实施增强,可以应用于权限管理等功能 。
  • org.springframework.aop.AfterReturningAdvice (后置通知)   在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能 。
  • org .springframework.aop.ThrowsAdvice (异常通知)   在方法抛出异常后实施增强,可以应用于处理异常记录曰志等功能 。
  • org .springframework.aop.introductionInterceptor (引介通知)   在目标类中添加一些新的方法和属性,可以应用于修改老版本程序(增强类)

(2)ProxyFactoryBean

         ProxyFactoryBean 是 FactoryBean 接口的实现类, FactoryBean 负责实例化一个 Bean ,而ProxyFactoryBean 负责为 Bean 创建代理实例 。

接下来通过一个典型的环绕通知案例,来演示Spring 使用 ProxyFactoryBean 创建 AOP 代理的过程.
(1)导包

(2)创建UserDao

(3)创建MyAspect

注意:MethodInterceptor 接口导包aopalliance下的接口。

(4)创建配置文件 appl icationContext.xm l


(5)测试类

(四)Aspect 开发

使用 AspectJ 实现 AOP 有两种方式:一种是基于 XML 的声明式 AspectJ ,另一种是基于注解的声明式 AspectJ 。

(1)基于 XML 的声明或 AspectJ

           基于 XML 的声明式 AspectJ 是指通过 XML 文件来定义切面、切入点及通知,所有的切面、切入点和通知都必须定义在 <aop:config>元素内 。
 

这些常用元素的配置代码如下所示 。

备注:

execution(* com. itheima.jdk. * *(..))就是定义的切入点表达式,该切入点表达式的意思是匹配 com.itheima.jdk 包中任意类的任意方法的执行。 其中 execution()是表达式的主体,第 1 个*表示的是返回类型,使用*代表所有类型; com.itheima.jdk 表示的是需要拦截的包名,后面第 2 个*表示的是类名,使用*代表所有的类;第 3 个*表示的是方法名,使用*表示所有方法;后面(.. )表示方法的参数,其中的".. "表示任意参数 。 需要注意的是,第 1个*与包名之间有一个空格 。
 

接下来通过案例来演示如何在 Spring中使用基于 XML 的声明式 AspectJ ,具体实现步骤如下:

①导包

②创建MyAspect
 

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class MyAspect {

	// 前置通知
	public void myBefore(JoinPoint joinPoint) {
		System.out.print(" 前置通知:模拟执行权限检查. . . , ");
		System.out.print(" 目标类是: " + joinPoint.getTarget());
		System.out.println(" ,被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
	}

	// 后置通知

	public void myAfterReturning(JoinPoint joinPoint) {
		System.out.print("后置通知:模拟记录日志. . . ,");
		System.out.println("被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
	}

	/**
	 * 环绕通知 ProceedingJoinPoint 是 JoinPoint 子接口,表示可以执行目标方法
	 * 
	 * 1.必须是 Object 类型的返回值 
	 * 2. 必须接收一个参数, 类型为 ProceedingJoinPoint 
	 * 3.必须 throws Throwable
	 */
	public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		// 开始
		System.out.println(" 环绕开始:执行目标方法之前,模拟开启事务. . . ");
		// 执行当前目标方法
		Object obj = proceedingJoinPoint.proceed();
		// 结束
		System.out.println("环绕结束 : 执行门标方法之后 , 模拟关闭事务 . . . ");
		return obj;
	}

	// 异常通知
	public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
		System.out.println("异常通知:" + "出错了 " + e.getMessage());
	} //

	// 最终通知
	public void myAfter() {
		System.out.println(" 最终通知 : 模拟方法结束后的释放资源 . . . ");
	}
}

③创建配置文件 app l icationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:aop="http://www.springframework.org/schema/aop"
	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-4.3.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
	<!-- 目标类 -->
	<bean id="userDao" class="com.itheima.jdk.UserDaoImpl" />
	<!-- 切面类 -->
	<bean id="myAspect" class="com.itheima.aspect.xml.MyAspect" />
	
	<aop:config>
		<aop:aspect id="myAspect" ref="myAspect">
			<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))" id="myPointCut"/>
			
			<aop:before method="myBefore" pointcut-ref="myPointCut"/>
			
			<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
			
			<aop:around method="myAround" pointcut-ref="myPointCut"/>
			
			<aop:after-throwing method="myAfterThrowing" throwing="e" pointcut-ref="myPointCut"/>
			
			<aop:after method="myAfter" pointcut-ref="myPointCut"/>
		</aop:aspect>
	</aop:config>
</beans>

④测试类

public class TestXmlAspectj {

	public static void main(String[] args) {
		String application = "com/itheima/aspect/xml/applicationContext.xml";
		ApplicationContext app = new ClassPathXmlApplicationContext(application);
		UserDao userDao = (UserDao) app.getBean("userDao");
		userDao.addUser();
	}
}

 结果显示:

(2)基于注解的声明式 AspectJ


示例如下:

①创建MyAspect
 

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class MyAspect {

	// 定义切入点表达式
	@Pointcut("execution(* com.itheima.jdk.*.*(..))")
	private void myPointCut() {
	}
	

	// 前置通知
	@Before(value = "myPointCut()")
	public void myBefore(JoinPoint joinPoint) {
		System.out.print(" 前置通知:模拟执行权限检查. 111. . , ");
		System.out.print(" 目标类是: " + joinPoint.getTarget());
		System.out.println(" ,被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
	}

	// 后置通知
	@AfterReturning(value = "myPointCut()", returning = "returnVal")
	public void myAfterReturning(JoinPoint joinPoint) {
		System.out.print("后置通知:模拟记录日志. . . ,");
		System.out.println("被植入增强处理的目标方法为: " + joinPoint.getSignature().getName());
	}

	/**
	 * 环绕通知 ProceedingJoinPoint 是 JoinPoint 子接口,表示可以执行目标方法
	 * 
	 * 1.必须是 Object 类型的返回值 2. 必须接收一个参数, 类型为 ProceedingJoinPoint 3.必须 throws
	 * Throwable
	 */
	@Around(value = "myPointCut()")
	public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
		// 开始
		System.out.println(" 环绕开始:执行目标方法之前,模拟开启事务. . . ");
		// 执行当前目标方法
		Object obj = proceedingJoinPoint.proceed();
		// 结束
		System.out.println("环绕结束 : 执行门标方法之后 , 模拟关闭事务 . . . ");
		return obj;
	}

	// 异常通知
	@AfterThrowing(value = "myPointCut()", throwing = "e")
	public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
		System.out.println("异常通知:" + "出错了 " + e.getMessage());
	} //

	// 最终通知
	@After(value = "myPointCut()")
	public void myAfter() {
		System.out.println(" 最终通知 : 模拟方法结束后的释放资源 . . . ");
	}
}

首先使用 @Aspect 注解定义了 切面类 ,由于该类在 Sprin g 中是作为组件
使用的,所以还需要添加@Component 注解才能生效 。
 

②创建userDao

@Repository("userDao") 
public class UserDaoImpl implements UserDao{

	@Override
	public void addUser() {
		System.err.println("新增用户");
	}

	@Override
	public void deleteUser() {
		System.err.println("删除用户");	
	}
	
}

③创建配置文件 app l icationContext.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" xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.3.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-4.3.xsd
        ">

	<context:component-scan base-package="com.itheima" />
	<aop:aspectj-autoproxy />
</beans>

使用 <aop:aspectj-autoproxy />来启动 Spring对基于注解的声 明式 AspectJ 的 支持。
 

④创建测试类 TestAnnotation

结果显示:
 

备注:

基于注解的方式与基于 XML 的方式的执行结果相同,只是在目标方法前后通知的执行顺序发生了变化 。
 


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值