JAVA EE 知识总结(三)Spring AOP

JAVA EE 知识总结(三)Spring AOP,第1张

目录

3.1  Spring AOP简介

3.1.1什么是AOP?

3.1.2 AOP术语

3.2  动态代理

3.2.1 JDK动态代理

3.2.2 CGLIB代理

3.3  基于代理类的AOP实现

3.3.1 Spring的通知类型

3.3.2 ProxyFactoryBean

3.4 AspectJ开发

3.4.1 基于XML的声明式AspectJ


3.1  Spring AOP简介 3.1.1什么是AOP?

AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程)。它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编程方式。

在传统的业务处理代码中,通常都会进行事务处理、日志记录等 *** 作。虽然使用OOP可以通过组合或者继承的方式来达到代码的重用,但如果要实现某个功能(如日志记录),同样的代码仍然会分散到各个方法中。这样,如果想要关闭某个功能,或者对其进行修改,就必须要修改所有的相关方法。这不但增加了开发人员的工作量,而且提高了代码的出错率。

为了解决这一问题,AOP思想随之产生。AOP采取横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。这种采用横向抽取机制的方式,采用传统的OOP思想显然是无法办到的,因为OOP只能实现父子关系的纵向的重用。虽然AOP是一种新的编程思想,但却不是OOP的替代品,它只是OOP的延伸和补充

类与切面的关系

 

AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性

目前最流行的AOP框架有两个:Spring AOP和AspectJ。

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

AspectJ:是一个基于Java语言的AOP框架,从spring2.0开始,Spring Aop引入了对AspectJ的支持,AspectJ扩展了Java语言,提供了一个专门的编译器,在编译时提供了横向代码的织入。

3.1.2 AOP术语

AOP的专业术语:

(1)Aspect(切面):封装的用于横向插入系统功能的类(如事务、日志等)。该类要被Spring容器识别为切面,需要在配置文件中通过元素指定。

(2)Joinpoint(连接点):在程序执行过程中的某个阶段点,它实际上是对象的一个 *** 作(例如方法的调用或异常的抛出)。在Spring AOP中,连接点就是指方法的调用

(3)Pointcut(切入点):切面与程序流程的交叉点,即那些需要处理的连接点。通常在程序中,切入点指的是类或者方法名。(如:某个通知要应用到所有以add开头的方法中,那么满足这一规则的方法都是切入点)。

(4)Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法。是切面的具体实现。

(5)Target Object(目标对象):指所有被通知的对象也被称为被增强对象。如果AOP框架采用的是动态的AOP实现,那么该对象就是一个被代理对象。

(6)Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。

(7)Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

3.2  动态代理

AOP中的代理就是由AOP框架动态生成的一个对象,该对象可以作为目标对象的使用。

3.2.1 JDK动态代理

JDK动态代理是通过java.lang.reflect.Proxy 类来实现的,我们可以调用Proxy类newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。JDK动态代理案例:

(1)创建web项目chapter03,导入所需jar包

(2)创建包com.jdk,在包中创建接口UserDao,并编写添加,删除方法。

public interface UserDao {
	public void addUser();
	public void deleteUser();
}

(3)创建UserDao接口的实现类UserDaoImpl

//目标类(需要对addUser()和deleteUser()方法进行增强)
public class UserDaoImpl implements UserDao{
	@Override
	public void addUser() {
		System.out.println("添加用户");
	}
	@Override
	public void deleteUser() {
		System.out.println("删除用户");
	}
}

(4)创建一个包com.aspect,并创建切面类MyAspect

/*
 * 切面类MyAspect:可以存在多个通知Advice
 * (定义一个模拟权限检查的方法 和 一个模拟记录日志的方法,这两个方法就表示切面中的通知)
 */
public class MyAspect {
	public void check_Permissions() {
		System.out.println("模拟检查权限...");
	}
	public void log() {
		System.out.println("模拟记录日志...");
	}
}

(5)在com.jdk中,创建代理类JdkProxy,该类需要实现接口InvocationHandler,并编写代理方法。

/**
 *代理类:需要实现InvocationHandler接口,并编写代理方法
 *		在代理方法中,需要通过Proxy类实现动态代理
 */
public class JdkProxy implements InvocationHandler{
	//声明目标类接口
	private UserDao userDao;
	//创建代理方法
	public Object createProxy(UserDao userDao) {
		//给已经有的userDao进行赋值
		this.userDao = userDao;
		//1.类加载器
		ClassLoader classLoader = JdkProxy.class.getClassLoader();
		//2.被代理对象实现的所有接口
		Class[] clazz = userDao.getClass().getInterfaces();
		/*
		 * 3.使用代理类,进行增强,返回的是代理后的对象
		 * 		classLoader:当前类的类加载器
		 * 		clazz:被代理对象实现的所有接口
		 * 		this:指代代理类JdkProxy本身
		 */
		return Proxy.newProxyInstance(classLoader, clazz, this);//newProxyInstance创建代理对象
	}
	/*
	 *所有动态代理类的方法调用,都会交由invoke()方法去处理
	 *proxy:被代理后的对象
	 *method:将要被执行的方法信息(反射)
	 *args:执行方法时需要的参数
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//声明切面
		MyAspect myAspect = new MyAspect();
		//前增强
		myAspect.check_Permissions();
		//在目标类上调用方法,并传入参数
		Object obj = method.invoke(userDao, args);
		//后增强
		myAspect.log();
		return obj;
	}
}

(6)创建测试类JdkTest

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

通过前面的学习可知,JDK的动态代理用起来非常简单,但它是有局限性的,使用动态代理的对象必须实现一个或多个接口。

那么,如何代理没有实现接口的类?——CGLIB代理

CGLIB(Code Generation Library)是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成一个子类,并对子类进行增强。

案例代码:

(1)创建目标类UserDao

//目标类
public class UserDao {
	public void addUser() {
		System.out.println("添加用户");
	}
	public void deleteUser() {
		System.out.println("删除用户");
	}
}

(2)创建代理类CglibProxy

//代理类
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;
	}
	
}

(3)创建测试类

public class CglibTest {
	public static void main(String[] args) {
		//创建代理对象
		CglibProxy cglibProxy = new CglibProxy();
		//创建目标对象
		UserDao userDao = new UserDao();
		//获取增强前的目标对象
		UserDao userDao2 = (UserDao) cglibProxy.createProxy(userDao);
		//执行方法
		userDao2.addUser();
		userDao2.deleteUser();
	}
}
3.3  基于代理类的AOP实现 3.3.1 Spring的通知类型

Spring按照通知在目标类方法的连接点位置,可以分为5种类型,具体如下:org.springframework.aop.MethodBeforeAdvice(前置通知)     在目标方法执行前实施增强,可以应用于权限管理等功能。org.springframework.aop.AfterReturningAdvice(后置通知)     在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除     临时文件等功能。org.aopalliance.intercept.MethodInterceptor(环绕通知)     在目标方法执行前后实施增强,可以应用于日志、事务管理等功能。org.springframework.aop.ThrowsAdvice(异常抛出通知)     在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能。org.springframework.aop.IntroductionInterceptor(引介通知)     在目标类中添加一些新的方法和属性,可以应用于修改老版本程序。

3.3.2 ProxyFactoryBean

ProxyFactoryBean是FactoryBean接口的实现类,FactoryBean负责实例化一个Bean,而ProxyFactoryBean负责为其他Bean创建代理实例。在Spring中,使用ProxyFactoryBean是创建AOP代理的基本方式。    

ProxyFactoryBean类中的常用可配置属性如下:

环绕通知案例:

(1)创建项目,导入jar包:spring-aop-4.3.6.RELEASE.jar 和 aopalliance-1.0.jar

(2)在com.itheima.factorybean包中创建切面类MyAspect

// 切面类
public class MyAspect implements MethodInterceptor {
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		check_Permissions();
		// 执行目标方法
		Object obj = mi.proceed();
		log();
		return obj;
	}
	public void check_Permissions(){
		System.out.println("模拟检查权限...");
	}
	public void log(){
		System.out.println("模拟记录日志...");
	}
}

 (3)在包com.itheima.factorybean中,创建配置文件applicableContext.xml,并指定代理对象


	
	
	
	
	
		
		
		
		
		
		
		
		
	

(4)在包com.itheima.factorybean中,创建测试类ProxyFactoryBeanTest

// 测试类
public class ProxyFactoryBeanTest {
	public static void main(String args[]) {
	   String xmlPath = "com/itheima/factorybean/applicationContext.xml";
	   ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
	   // 从Spring容器获得内容
	   UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
	   // 执行方法
	   userDao.addUser();
	   userDao.deleteUser();
	}
}
3.4 AspectJ开发

    AspectJ是一个基于Java语言的AOP框架,它提供了强大的AOP功能。Spring 2.0以后,Spring AOP引入了对AspectJ的支持,并允许直接使用AspectJ进行编程,而Spring自身的AOP API也尽量与AspectJ保持一致。新版本的Spring框架,也建议使用AspectJ来开发AOP。    使用AspectJ实现AOP有两种方式:一种是基于XML的声明式AspectJ,另一种是基于注解的声明式AspectJ

3.4.1 基于XML的声明式AspectJ

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

小提示:图中灰色部分标注的元素即为常用的配置元素

XML文件中常用元素的配置方式如下:




	
	
		
		
		
		
		

		

		

		

    

配置内容进行详细讲解:

1.配置切面

在Spring的配置文件中,配置切面使用的是元素,该元素会将一个已定义好的Spring Bean转换成切面Bean,所以要在配置文件中先定义一个普通的Spring Bean。

配置元素时,通常会指定id和ref两个属性。

2.配置切入点

当元素作为元素的子元素定义时,表示该切入点是全局切入点,它可被多个切面所共享;当元素作为元素的子元素时,表示该切入点只对当前切面有效

在定义元素时,通常会指定id和expression两个属性。

切入点表达式:

execution(* com.itheima.jdk.*.*(..)) 是定义的切入点表达式,该切入点表达式的意思是匹配com.jdk包中任意类的任意方法的执行。切入点表达式的基本格式:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
name-pattern(param-pattern) throws-pattern?)

3.配置通知

使用的子元素可以配置5种常用通知,这5个子元素不支持使用子元素,但在使用时可以指定一些属性,其常用属性及其描述如下:

 

案例代码

接下来,就通过一个案例来演示如何在Spring中使用基于XML的声明式AspectJ,具体实现步骤请参见教材3.4.1小节。

(1)导入AspectJ相关的jar包spring-aspects-4.3.6.RELEASE.jar和aspectjweaver-1.8.10.jar

(2)创建包:com.aspect.xml   创建切面类MyAspecy.java

/**
 *切面类:在此类中编写通知
 */
public class MyAspecy {
	//前置通知
	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("最终通知:模拟方法结束后的释放资源...");
	}
}

 (3)在包com.aspect.xml中,创建配置文件applicationContext.xml







	

		
			
			
			
			
			
			
			
			
			
			
			
			


(4)在包com.aspect.xml中,创建测试类TestXmlAspectj.java

public class TestXmlAspectj {
	public static void main(String[] args) {
		ApplicationContext ac = new ClassPathXmlApplicationContext("com/aspect/xml/applicationContext.xml");
		// 1 从spring容器获得内容
		UserDao userDao = (UserDao) ac.getBean("userDao");
		
		userDao.addUser();
		userDao.deleteUser();
	}
}

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/786505.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-05
下一篇2022-05-05

发表评论

登录后才能评论

评论列表(0条)