Spring AOP

目录

一、AOP的概念和作用

二、AOP术语

 三、动态代理

3.1  JDK动态代理(通过Proxy类的newProxyInstance方法来实现)

3.2 CGLIB代理

四、AspectJ开发

4.1 基于XML的声明式Aspectj

 4.2 基于注解的声明式AspectJ


一、AOP的概念和作用

AOP:面向切面编程(也称面向方面编程,是面向对象编程(OOP)的一种补充)。

AOP作用:aop采用横向抽取机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或者运行时,再将这些提取出来的代码应用到需要执行的地方。AOP是OOP的延伸和补充,不是OOP的替代品。AOP的使用,使开发人员在编写业务逻辑时可以专心于核心业务,而不用过多的关注于其他业务逻辑的实现,这不但提高了开发效率,而且增强了代码的可维护性。

AOP思想中,类和切面的关系: 

二、AOP术语

  • Aspect(切面):切面通常是指封装的用于横向插入系统功能(如事务、日志等)的类
  • JoinPoint(连接点):在程序执行过程中的某个阶段点。AOP中指方法的调用。
  • PointCut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点。
  • Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,即在定义好的切入点处所要执行的程序代码。可以将其理解为切面类中的方法。
  • Target Object(目标对象):被增强的对象。如果AOP采用的是动态的AOP实现,那么该对象就是一个被代理对象。
  • Proxy(代理):将通知应用到目标对象之后,被动态创建的对象。
  • Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。

 三、动态代理

3.1  JDK动态代理(通过Proxy类的newProxyInstance方法来实现)

JDK动态代理是通过java.lang.reflect.Proxy类来实现的,通过调用Proxy类的newProxyInstance()方法来创建代理对象。对于使用业务接口的类,Spring默认会使用JDK动态代理来实现AOP。——使用动态代理的对象必须实现一个或多个接口。

(一)创建UserDao接口及其实现类UserDaoImpl

本案例会将实现类UserDaoImpl作为目标类,对其中的方法进行增强处理。

//创建UserDao接口,并添加相应方法
package com.haust.dao;

public interface UserDao {
	public void add();
	
	public void delete(int id);
	
	public void update(int id);
	
	public void query();
}
//创建UserDao接口的实现类UserDaoImpl,并实现相应方法
package com.haust.dao.impl;

import org.springframework.stereotype.Repository;

import com.haust.dao.UserDao;

public class UserDaoImpl implements UserDao {

	@Override
	public void add() {
		// TODO Auto-generated method stub
		System.out.println("添加用户...");
	}

	@Override
	public void delete(int id) {
		// TODO Auto-generated method stub
		System.out.println("删除用户..."+id);
	}

	@Override
	public void update(int id) {
		// TODO Auto-generated method stub
		System.out.println("修改用户..."+id );
	}

	@Override
	public void query() {
		// TODO Auto-generated method stub
		System.out.println("查询用户...");
	}

}

(二)创建切面类

//创建切面类,在该类中定义一个模拟权限检查的方法和一个模拟日志记录的方法,这两个方法就表示切面中的通知
package com.haust.advice;
//切面类,可以存在多个通知Advice(即增强的方法)
public class MyAdvise {
	public void check_permission(){
		System.out.println("模拟权限控制...");
	}
	
	public void log(){
		System.out.println("模拟日志记录...");		
	}
}

(三)创建代理类

package com.haust.utils;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;

import com.haust.advice.MyAdvise;
import com.haust.dao.UserDao;

public class JdkProxy implements InvocationHandler {
	//声明目标类接口
	private UserDao userDao ; 
	//创建代理方法
	public Object createProxy(UserDao userDao){
		this.userDao = userDao;
		//第一个参数是类加载器,通过字节码对象(userDao.getClass())获得类加载器
		ClassLoader loader = userDao.getClass().getClassLoader();
		//第二个参数是目标类的接口中的所有方法,通过目标类的字节码对象(userDao.getClass())获得所有方法的接口
		Class[] interfaces =  userDao.getClass().getInterfaces();
		return Proxy.newProxyInstance(loader, interfaces,this);
		
	}
	/*
	 * 所有动态代理类的方法调用,都会由invoke()方法去处理
	 * Object proxy:表示代理对象
	 *	Method method:目标对象的方法对象
	 *	Object[] args:目标对象方法的参数 
	 */
	@Override
	public Object invoke(Object proxy, Method methods, Object[] args)throws Throwable {
		//声明切面类
		MyAdvise myadvise = new MyAdvise();
		//前增强
		myadvise.check_permission();
		//在目标类上调用方法,并传入参数
		Object retValue =  methods.invoke(userDao, args);
		//后增强
		myadvise.log();
		return retValue;
	}
	
}

(四)测试类

package com.haust.test;

import org.junit.Test;

import com.haust.advice.MyAdvise;
import com.haust.dao.UserDao;
import com.haust.dao.impl.UserDaoImpl;
import com.haust.utils.JdkProxy;

public class TestReflect {
	@Test
	public void jdkProxyTest(){
		//创建代理对象
		JdkProxy jdkProxy = new JdkProxy();
		//创建目标对象
		UserDao userDao = new UserDaoImpl();
		//从代理对象中获取增强后的目标对象
		UserDao userProxy = (UserDao) jdkProxy.createProxy(userDao);
		//执行方法
		userProxy.add();
		userProxy.delete(10);
	}
}

(五)结果显示

3.2 CGLIB代理

 CGLIB是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类
生成一个子类,并对子类进行增强。如果想代理没有实现接口的类,就可以使用CGLIB代理。(在spring的核心包中已经集成了CGLIB所需要的包,所以开发中不需要另外导入jar包)

通过一个案例来实现Spring中的CGLIB动态代理。 

(一)创建一个目标类UserDao,不需要实现任何接口

package com.haust.dao.impl;
//目标类
public class UserDao {

	public void add() {
		System.out.println("添加用户...");
	}

	public void delete(int id) {
		System.out.println("删除用户..."+id);
	}
}

(二)创建代理类CglibProxy

package com.haust.utils;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import com.haust.advice.MyAdvise;

public class CglibProxy implements MethodInterceptor {
	//代理方法
	public Object createProxy(Object terget){
		//创建一个动态代理类
		Enhancer enhancer = new Enhancer();
		//确定需要增强的目标类,设置其父类
		enhancer.setSuperclass(terget.getClass());
		//添加回调函数
		enhancer.setCallback(this);
		//返回创建的代理类
		return enhancer.create();
		
	}
	/*
	*Objcet proxy :根据指定父类生成的代理对象
	 *Method method : 拦截的方法
	 *Object[] args : 拦截方法的参数数组
	 *methodProxy : 方法的代理对象,用于执行父类的方法
	 */
	@Override
	public Object intercept(Object proxy, Method methods, Object[] args,
			MethodProxy methodproxy ) throws Throwable {
		//创建切面类对象
		MyAdvise myadvise = new MyAdvise();
		//前增强
		myadvise.check_permission();
		//目标方法执行
		Object obj =  methodproxy.invokeSuper(proxy, args);
		//后增强
		myadvise.log();
		return obj;
	}
}

(三)测试类

import com.haust.utils.CglibProxy;
import com.haust.dao.UserDao;
import org.junit.Test;

public class TestReflect {
	@Test
	public void CjlibProxyTest(){
        //创建代理对象
		CglibProxy cglibproxy = new CglibProxy();
        //创建目标对象
		UserDao userDao =new UserDao();
        //获取增强后目标对象
		UserDao userProxy = (UserDao) cglibproxy.createProxy(userDao);
		//执行方法
        userProxy.add();
        userproxy.delete(10);
	}
}

(四)结果显示

 

四、AspectJ开发 

AspectJ是基于Java语言的AOP框架,新版本的Spring框架,建议使用AspectJ来开发AOP

所需要的jar包:

 

4.1 基于XML的声明式Aspectj

基于XML的声明式AspectJ是指通过XML文件来定义切面,切入点,通知。

常用的配置代码如下:

<!-- 定义切面Bean -->
	<bean id="myAspect" class="com.haust.aspect.MyAspect"></bean>
	<aop:config>
		<!-- 1.配置切面 -->
		<aop:aspect id="aspect" ref="myAspect">
			<!--2. 配置切入点 -->
			<aop:pointcut expression="execution(* com.haust.dao.*.*(..))" id="pointCut"/>
			<!--3. 配置通知-->
			<!-- 前置通知 -->
			<aop:before method="myBefore" pointcut-ref="pointCut"/>
			<!-- 后置通知 -->
			<aop:after-returning method="myAfterReturning" pointcut-ref="pointCut" returning="returnVal"/>
			<!-- 环绕通知 -->
			<aop:around method="myAround" pointcut-ref="pointCut"/>
			<!-- 异常通知 -->
			<aop:after-throwing method="myAfterThrowing" pointcut-ref="pointCut" throwing="e"/>
			<!-- 最终通知 -->
			<aop:after method="myAfter" pointcut-ref="pointCut"/>
		</aop:aspect>
	</aop:config>

<!--2. 配置切入点 -->
<aop:pointcut expression="execution(* com.haust.dao.*.*(..))" id="pointCut"/>该切入点表达式的意思是匹配com.haust.dao包中任意类的任意方法的执行。其中execution()是表达式的主体,第一个*表示的是返回类型,代表返回所有类型;com.haust.dao表示的是需要拦截的包名,后面第二个*表示的是类名,代表所有的类;第三个*代表的是方法名,使用*代表所有的方法;(...)表示方法中的参数,“...”代表任意参数。需要注意的是,第一个*和包名之间有一个空格。

通过一个案例来实现基于XML的AspectJ开发

(一)编写一个切面类MyAspect.java

package com.haust.aspect.xml;

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

/*
 * 切面类,在此类中编写通知
 */
public class MyAspect {
	//前置通知
	public void myBefore(JoinPoint joinPoint){
		System.out.println("前置通知,模拟执行权限检查....");
		System.out.println("目标类是"+joinPoint.getTarget());	//joinPoint.getTarget 获取被代理对象
		System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
		
	}
	//后置通知
	public void myAfterReturning(JoinPoint joinPoint){
		System.out.println("后置通知,模拟记录日志....");
		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("最终通知:模拟方法结束后的释放资源....");
	}

}
/*JoinPoint对象
 * JoinPoint对象封装了SpringAop中切面方法的信息,在切面方法中添加JoinPoint参数,就可以
 * 	获取到封装了该方法信息的JoinPoint对象
 * 方法名:
 * Signature getSignature():获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的class等信息
 * Object[] getArgs():获取传入目标方法的参数对象
 * Object[] getTarget():获取被代理的对象
 * Object getThis():获取代理对象
 * 
 * ProceedingJoinPoint对象:
 * 	ProcecedingJoinPoint对象是JoinPoint对象的子接口,该对象只用在@Around的切面方法中
 * 	该子接口添加了两个方法 :
 * 		Object proceed() throws Throwable  //执行目标方法
 * 		Object proceed(Object[] varl) throws Throwable//传入的新的参数去执行目标方法
 */

(二)编写配置文件applicationContext.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:context="http://www.springframework.org/schema/context"
    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.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">
    <!-- 目标类 -->
 	<bean id="userDao" class="com.haust.dao.impl.UserDaoImpl"></bean>
 	<!-- 切面 -->
 	<bean id="myAspect" class="com.haust.aspect.xml.MyAspect"></bean>
 	<!-- aop编程 -->
 	<aop:config>
 		<!-- 配置切面 -->
 		<aop:aspect ref="myAspect">
 			<!-- 配置切入点 -->
 			<aop:pointcut expression="execution(* com.haust.dao.*.*(..))" 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" pointcut-ref="myPointCut" throwing="e"/>
 			<aop:after method="myAfter" pointcut-ref="myPointCut"/>
 		</aop:aspect>
 	</aop:config>
</beans>

(三)编写一个测试TestXmlAspectj.java

package com.haust.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.haust.dao.UserDao;

public class TestXmlAspectj {
	public static void main(String[] args) {
		ApplicationContext applicationcontext = new ClassPathXmlApplicationContext("applicationContext.xml");
		//1.从Spring容器中获得内容
		UserDao userdao = (UserDao) applicationcontext.getBean("userDao");
		//2.执行方法
		userdao.delete(10);
	}
}

(四)结果显示

 4.2 基于注解的声明式AspectJ

基于代理类的AOP实现相比,基于XML的声明式AspectJ要简单的多,但是基于XML的方式在Spring文件中要配置大量的代码信息,为了解决这一问题,就有了基于注解的声明式AspectJ。
AspectJ的注解及其描述:

 用一个案例来实现基于注解的声明式AspectJ:

(一)编写切面类/MyAspectAnnocation.java

package com.haust.aspect.annocation;

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 MyAspectAnnocation {
	
		//定义切入点表达式
		@Pointcut("execution(* com.haust.dao.*.*(..))")
		//使用一个返回值为void,方法体为空的方法来命名切入点
		private void myPointCut(){}
		
		@Before("myPointCut()")
		//前置通知
		public void myBefore(JoinPoint joinPoint){
			System.out.println("前置通知,模拟执行权限检查....");
			System.out.println("目标类是"+joinPoint.getTarget());
			System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
			
		}
		
		@AfterReturning("myPointCut()")
		//后置通知
		public void myAfterReturning(JoinPoint joinPoint){
			System.out.println("后置通知,模拟记录日志....");
			System.out.println("被织入增强处理的目标方法为:"+joinPoint.getSignature().getName());
		}
		
		/*
		 * 环绕通知
		 * ProceedingJoinPoint是JoinPoint子接口,表示可以执行目标方法
		 * 1.必须是Object类型的返回值
		 * 2.必须接受一个参数,类型为ProceedingJoinPoint
		 * 3.必须throws Throwable
		 */
		@Around("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("myPointCut()")
		//最终通知
		public void myAfter(){
			System.out.println("最终通知:模拟方法结束后的释放资源....");
		}
}

(二)编写配置文件aspectAnnocation.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:context="http://www.springframework.org/schema/context"
    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.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">
        <!-- xmlns:aop="http://www.springframework.org/schema/aop"
        	使用aop命名空间,在配置文件中打开相应的注解处理器 -->
        
        
        <!-- 指定需要扫描的包,使注解生效 -->
        <context:component-scan base-package="com.haust"></context:component-scan>
        <!-- 启动基于注解声明式AspectJ支持 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

(三)编写测试类TestAnnocationAspectj.java

package com.haust.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.haust.dao.UserDao;

public class TestAnnocationAspectj {

	public static void main(String[] args) {
		ApplicationContext applicationcontext = new ClassPathXmlApplicationContext("aspectAnnocation.xml");
		UserDao userdao = (UserDao) applicationcontext.getBean("userDao");
		userdao.add();
		
	}

}

(四)结果显示

 

注意:如果在同一个连接点有多个通知需要执行,那么在同一切面中,目标方法之前的前置通知和环绕通知额执行顺序是未知的,目标方法之后的后置通知和环绕通知的执行顺序也是未知的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值