AOP编程详细解析

1.什么是AOP

   AOP全称为Aspect Oriented Programming的缩写,也意为:面向切面编程,通过预编译手段和运行期动态代理实现程序功能的统一维护技术。采用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时也提高了开发效率。AOP采取横向抽取机制,取代了纵向继承体系重复的代码。

1.1AOP应用场景

   AOP典型的应用场景有:事务管理,性能监控、安全监测、日志等。

1.2AOP实现原理

   AOP底层采用代理的机制进行实现的。代理的方式有两种:jdk动态代理、cglib代理(字节码增强)

1.3AOP相关的术语

    1.target:目标类。需要被代理的类,也是需要通过代理增强的类。

    2.JoinPoint:连接点。连接点就是指需要增强类(target)可能拦截到的方法。如同target中。

    3.PointCut:切入点。已经被增强的连接点(在众多连接点中已经被增强的方法,就叫切入点。切入点是连接点的子集)。

    4.advice:通知(增强)。增强的代码(在切入点中增强所需的代码就是通知,例如:事务管理就是在切入点方法中前后开启事务和提交事务)。

    5.Weaving:织入。把增强代码(advice)应用到目标类(target)来创建新的代理对象(proxy)的过程就叫做织入。

    6.proxy:代理类。

    7.Aspect:切面。是切入点(PointCut)和通知(advice)的结合。

2.手动方式实现AOP编程

2.1采用JDK动态代理实现手动方式

   采用jdk动态代理需要的条件是:需要代理的类是采用接口+实现类的方式。(因为代理是采用目标类所实现的接口来生成目标类的代理)。

 第一步:准备目标类(target):

    接口:

public interface UserService {
	public void addUser();
	public void updateUser();
	public void deleteUser();
}

   实现类:

public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		System.out.println("addUser");
	}

	@Override
	public void updateUser() {
		System.out.println("updateUser");
	}

	@Override
	public void deleteUser() {
		System.out.println("deleteUser");
	}

}

  第二步:准备切面类(也是增强代码所在的类advice)

       切面类代码:

public class MyAspect {
	
	public void before() {
		System.out.println("前方法");
	}
	
	public void after() {
		System.out.println("后方法 ");
	};
}

  第三步:创建生成代理类工厂实现对目标类的代理

     创建代理类通过Proxy类中的静态方法newProxyInstance(类加载器,代理类实现的接口,InvacationHandler处理类)

public class MyBeanFactory {
	
	//应为这个类是生成代理类,所以就通过这个方法产生代理类 
	public static UserService createService() {
		//首先需要目标类
		UserService userService = new UserServiceImpl();
		//切面类
		MyAspect myAspect = new MyAspect();
		
		/* 3 代理类:将目标类(切入点)和 切面类(通知) 结合 --> 切面
		 * 	Proxy.newProxyInstance
		 * 		参数1:loader ,类加载器,动态代理类 运行时创建,任何类都需要类加载器将其加载到内存。
		 * 			一般情况:当前类.class.getClassLoader();
		 * 					目标类实例.getClass().get...
		 * 		参数2:Class[] interfaces 代理类需要实现的所有接口
		 * 			方式1:目标类实例.getClass().getInterfaces()  ;注意:只能获得自己接口,不能获得父元素接口
		 * 			方式2:new Class[]{UserService.class}   
		 * 			例如:jdbc 驱动  --> DriverManager  获得接口 Connection
		 * 		参数3:InvocationHandler  处理类,接口,必须进行实现类,一般采用匿名内部
		 * 			提供 invoke 方法,代理类的每一个方法执行时,都将调用一次invoke
		 * 				参数31:Object proxy :代理对象
		 * 				参数32:Method method : 代理对象当前执行的方法的描述对象(反射)
		 * 					执行方法名:method.getName()
		 * 					执行方法:method.invoke(对象,实际参数)
		 * 				参数33:Object[] args :方法实际参数
		 * 
		 */
		UserService obj = (UserService) Proxy.newProxyInstance(userService.getClass().getClassLoader()
				, userService.getClass().getInterfaces()
				, new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						//执行前
						myAspect.before();
						//执行目标类的方法
						Object obj = method.invoke(userService, args);
						//执行后
						myAspect.after();
						return obj;
					}
				} );
		return obj;
	}
}
2.2采用cglib(字节码增强)方式实现手动方式

   使用cglib字节码增强方式实现代理,需要导入相关的jar包(cglib.jar和asm.jar),但是一般Spring的核心包总已经提供了对cglib的支持。

   使用cglib字节码生成代理类不需要实现目标类的接口,而是通过目标类直接生成代理类(cglib生成代理类是采用继承的方式  实现代理)

   第一步:准备目标类(target)

     目标类代码:

public class UserServiceImpl{

	public void addUser() {
		System.out.println("addUser");
	}

	public void updateUser() {
		System.out.println("updateUser");
	}

	public void deleteUser() {
		System.out.println("deleteUser");
	}

}

    第二步:准备切面类(advice)

       切面类代码:

public class MyAspect {
	
	public void before() {
		System.out.println("前方法");
	}
	
	public void after() {
		System.out.println("后方法 ");
	};
}

   第三步:创建自定义工厂对目标类进行代理

      使用cglib创建代理采用的核心类是Enhancer,通过Enhancer类中create方法创建目标类的代理。但是在执行create方法之前需要确定代理目标类(也就是代理类的父类),确定父类是通过Enhancer类中的setSupperClass(父类的字节码)。也需要对Enhancer设置回调函数,也就是调用setCallback(Callback)。

  代码:

public class MyBeanFactory {
	
	//应为这个类是生成代理类,所以就通过这个方法产生代理类 
	public static UserServiceImpl createService() {
		//首先需要目标类
		UserServiceImpl userService = new UserServiceImpl();
		//切面类
		MyAspect myAspect = new MyAspect();
		//代理类: 采用cglib,底层创建目标类的子类
		//核心类
		Enhancer enhancer = new Enhancer();
		//确定父类
		enhancer.setSuperclass(userService.getClass());
		/**
		 * 设置回调函数:MethodInterceptor接口 等效于jdk中 InvacationHandler接口
		 * intercept()方法等效于jdk 的invoke()方法
		 * 参数1、参数2、参数3和invoke方法一样。
		 * 参数4调用代理类父类的目标方法
		 */
		enhancer.setCallback(new MethodInterceptor() {
			@Override
			public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
				//执行前方法
				myAspect.before();
				//执行目标方法
				//Object object = method.invoke(userService, args);
				//methodProxy 执行代理类父类的目标方法相当于执行userServiceImpl中的目标方法
				//Object object = methodProxy.invokeSuper(proxy, args);
				Object object = methodProxy.invoke(userService, args);
				//执行后方法
				myAspect.after();
				return object;
			}
		});
		//创建代理类
		UserServiceImpl userServiceProxy = (UserServiceImpl) enhancer.create();
		return userServiceProxy;
	}
}

3.通过AOP联盟定义的规范和Spring提供的支持实现半自动方式

 3.1.AOP联盟定义的通知类型

     1.AOP联盟定义的通知类型在org.aopalliance.aop.Advice中。

     2.Spring按照通知(advice)在目标方法的前后分为五类:

        前置通知org.springframework.aop.MethodBeforeAdvice:在目标方法执行前进行增强。

        后置通知org.springframework.aop.AfterReturningAdvice:在目标方法执行后进行增强。

        环绕通知org.aopalliance.intercept.MethodInterceptor:在目标方法执行前后进行增强,但是必须手动执行目标方法。

        异常抛出通知org.springframework.aop.ThrowsAdvice:在方法执行过程中抛出异常后进行增强。

        引介通知org.springframework.aop.IntroductionInterceptor:在目标类中添加一些新的属性和方法时进行增强。

 3.2.需要导入的jar包

   需要导入aop(org.aopalliance.jar)规范和Spring对规范的实现(spring-aop.jar)。

 3.3.进行半自动方式AOP编程

     第一步:准备目标类(target)

        目标类所实现的接口:

public interface UserService {
	public void addUser();
	public void updateUser();
	public void deleteUser();
}

          目标类:

public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		System.out.println("addUser");
	}

	@Override
	public void updateUser() {
		System.out.println("updateUser");
	}

	@Override
	public void deleteUser() {
		System.out.println("deleteUser");
	}

}
     第二步:准备切面类(advice)

       这里用我们经常用到的环绕通知进行演示。

       使用aop联盟方式进行aop编程,切面类需要按照aop联盟规范进行定义,使用环绕通知,advice类就需要实现接口MethodInterceptor。

     代码:

public class MyAspect implements MethodInterceptor{
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		
		//执行前方法
		System.out.println("前方法");
		
		//手动执行目标方法
		Object object = mi.proceed();
		
		//执行后方法
		System.out.println("后方法");
		return object;
	};
}
     第三步:在Spring配置文件中进行配置

      Spring配置文件配置,首先需要向配置文件中配置目标类和切面类bean,然后再通过ProxyFactoryBean来对目标类进行自动生成代理(也就是对目标类进行加强)。由于是通过Spring容器来自动对目标类进行代理,所以需要给ProxyFactoryBean注入目标类所实现的接口(通过接口可以通过jdk方式生成代理,如果没有配置接口,就会采用cglib方式进行代理。这种代理方式也是Spring的默认方式),也需要注入目标类(不管是jdk代理还是cglib代理都需要目标类),同时也需要注入切面类。如果不管是否有注入接口,都需要采用cglib方式进行代理,就给ProxyFactoryBean类中注入optimize,并且设置为true。

      配置文件如下:


		
	
	
	
	
	
	
		
		
		
		
		
		
	

      这种方式也是通过Spring提供的一个特殊的Bean来自动对目标类进行代理或者增强。所以我们使用的时候是直接使用的代理类,而不是使用目标类。

4.Spring AOP编程:全自动

  第一步:准备目标类(target)

     目标类接口:

public interface UserService {
	public void addUser();
	public String updateUser();
	public void deleteUser();
}

    目标类:

public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		System.out.println("addUser");
		//int a = 1/0;
	}

	@Override
	public String updateUser() {
		System.out.println("updateUser");
		return "超哥";
	}

	@Override
	public void deleteUser() {
		System.out.println("deleteUser");
	}

}

  第二步:准备切面类(advice)

  这里也是用环绕通知演示

public class MyAspect implements MethodInterceptor{
	
	@Override
	public Object invoke(MethodInvocation mi) throws Throwable {
		
		//执行前方法
		System.out.println("前方法");
		
		//手动执行目标方法
		Object object = mi.proceed();
		
		//执行后方法
		System.out.println("后方法");
		return object;
	};
}
   第三步:在Spring配置文件中进行配置

    在配置前需要给配置文件中加入aop的头信息。

    然后进行后面的配置:也是先配置目标类和切面类bean


		
	
	
	

   最后进行aop编程:


	
		
		
	

这样在使用过程中就直接使用目标类就可以了,因为已经对目标类进行了加强。(其实采用这种方式进行增强原理是:在执行目标方法前通过了后处理bean(BeanPostProcessor)调用来对目标类进行代理,并返回。这样在执行目标类中的方法,而不是执行的是目标类中的方法,而是执行的是代理类中的方法)。

5.使用AOP框架AspectJ来进行AOP编程

  5.1.AspectJ介绍

    AspectJ是一个基于Java语言的AOP框架。

   Spring在2.0的时候提供了对AspectJ的切入点(PointCut)表达式的支持(切入点表达式就是从target(或者是说JoinPoint)中去选出需要增强的方法)

  5.2.切入点表达式

      通过execution()来描述方法,也就是从target中(或者说是JoinPoint中)选出需要增强的方法。至于如何选出就是在execution()中写入选择的表达式。

execution()语法介绍:execution(需要筛选方法的修饰符 筛选方法的返回值  包.类.方法名(方法参数) throws 异常)。

    修饰符:一般可以省略。

           public   公共方法(private protect.....)

           *           任意修饰符

     返回值:返回值不能进行省略。

           void       返回值为空

           String     返回值为字符串(其它省略)

           *             任意返回值

      包:可以省略,但是一般不省略。

           com.test.a         固定包

           com.test.a.*.service   a包下的任意子包下的service包

           com.test.a..        a包下的所有子包

      类:可以省略,但是一般不省略。

           UserServiceImpl    指定类

           *Impl                      以Impl结尾的类

           User*                     以User开头的类

           *                            任意类

      方法名:不能省略。

            addUser               固定方法

            add*                     以add开头的方法

            *Delete                 以Delete结尾的方法

            *                            任意方法 

      参数:不能省略。

            ()                      无参数

            (int)                  一个int参数

            (int,int)            两个int参数

             (..)                   参数任意

       throws,可以省略。一般我们也不写。

  5.3AspectJ通知类型

   

     before:前置通知(应用:各种校验)

     在方法执行前执行,如果通知抛出异常,阻止方法运行

     afterReturning:后置通知(应用:常规数据处理)

     方法正常返回后执行,如果方法中抛出异常,通知无法执行

     必须在方法执行后才执行,所以可以获得方法的返回值。

     around:环绕通知(应用:十分强大,可以做任何事情)

     方法执行前后分别执行,可以阻止方法的执行

     必须手动执行目标方法

     afterThrowing:抛出异常通知(应用:包装异常信息)

     方法抛出异常后执行,如果方法没有抛出异常,无法执行

     after:最终通知(应用:清理现场)

     方法执行完毕后执行,无论方法中是否出现异常

5.3.导入jar包

   导入的jar主要有四个:aop联盟(aopallicance.jar),Spring对AOP联盟的支持(Spring-aop.jar),AspectJ(aspectJ.weaver.jar),Spring对AspectJ的支持(Spring-aspectJ.jar)

5.4.使用AspectJ方式进行AOP编程
     第一步:准备目标类

         目标类所实现的接口:

public interface UserService {
	public void addUser();
	public void updateUser();
	public void deleteUser();
}

         目标类:

public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		System.out.println("addUser");
	}

	@Override
	public void updateUser() {
		System.out.println("updateUser");
	}

	@Override
	public void deleteUser() {
		System.out.println("deleteUser");
	}

}
     第二步:准备切面类

      切面类不需要向AOP联盟那样必须实现他的规范,而是采用AspectJ提供通知名任意(方法名任意)

     代码:

public class MyAspect {

	//JoinPoint 用于描述连接点(目标方法),可以获得目标方法名等。
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知" + joinPoint.getSignature().getModifiers());
	}
	
	//后置通知
	//	通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
	//		参数1:连接点描述
	//		参数2:类型Object,参数名 returning="ret" 配置的
	public void myAfterReturning(JoinPoint joinPoint,Object ret) {
		System.out.println("后置通知"+joinPoint.getSignature().getName()+",---->"+ret);
	}
	
	//环绕通知
	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
		//执行前方法
		System.out.println("前");
		//需要手动执行目标方法
		Object  obj =joinPoint.proceed();
		//执行后方法
		System.out.println("后");
		return obj;
	}
	
	//异常通知
	public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
		System.out.println("抛出异常通知:"+e.getMessage());
	}
	
	//最终通知
	public void myAfter() {
		System.out.println("最终通知");
	}
}
   第三步:Spring配置文件进行配置

    


	
		
	
	
	
	
	
	 
	 	
	 	
	 		
	 		 
	 		  
			
			 			
			
	 	
	 

6.使用AspectJ注解方式

第一步:修改配置文件

   使用注解的方式实现AOP编程,首先需要对Spring配置文件添加context头信息

  然后打开Spring的注解扫描:


    

  接下来是让AspectJ注解生效:


    

整个Spring配置文件代码如下:


    
    
    
    
    

第二步:准备目标类(target)

   目标类所实现的接口:

public interface UserService {
	public void addUser();
	public void updateUser();
	public void deleteUser();
}

   目标类:

@Service("userServiceId")
public class UserServiceImpl implements UserService{

	@Override
	public void addUser() {
		System.out.println("addUser");
	}

	@Override
	public void updateUser() {
		System.out.println("updateUser");
		//int a = 1/0;
	}

	@Override
	public void deleteUser() {
		System.out.println("deleteUser");
	}

}
第三步:准备切面类并直接在切面类添加注解进行AOP编程

在切面类上添加@Aspect注解是指该类为切面类。

在切面类的方法中分别添加以下注解:

(在value中对AspectJ表达式进行引用:首先需要有可引用的pointcut。如何准备pointcut呢?

    在切面类中任意写一个无返回值无参数的方法,并在方法前面加上@Pointcut(里面写表达式)注解。

   后面引用就直接写该方法。

  )

 @Before(value="aspectJ表达式或者aspectJ表达式的引用"):该方法将作为增强代码(advice)织入(weaving)到目标类中(target)。  

代码如下:

@Component
@Aspect
public class MyAspect {
	
	//注解方式生成切入点引用
	@Pointcut(value="execution(* com.test.f_proxy_by_AspectJAnnotation.UserServiceImpl.*(..))")
	private void myPointCut() {
		
	}
	
	//添加前置通知注解:value属性里面写切入点表达式或者切入点引用
	//@Before("execution(* com.test.f_proxy_by_AspectJAnnotation.UserServiceImpl.*(..))")
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知" + joinPoint.getSignature().getModifiers());
	}
	
	//添加后置通知
	//@AfterReturning(value="myPointCut()",returning="ret")
	public void myAfterReturning(JoinPoint joinPoint,Object ret) {
		System.out.println("后置通知"+joinPoint.getSignature().getName()+",---->"+ret);
	}
	
	//添加环绕通知
	//@Around("myPointCut()")
	public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
		//执行前方法
		System.out.println("前");
		//需要手动执行目标方法
		Object  obj =joinPoint.proceed();
		//执行后方法
		System.out.println("后");
		return obj;
	}
	
	//添加抛出异常通知
	//@AfterThrowing(value="myPointCut()",throwing="e")
	public void myAfterThrowing(JoinPoint joinPoint,Throwable e) {
		System.out.println("抛出异常通知:"+e.getMessage());
	}
	
	//添加最后通知
	@After("execution(* com.test.f_proxy_by_AspectJAnnotation.UserServiceImpl.*(..))")
	public void myAfter() {
		System.out.println("最终通知");
	}
}




你可能感兴趣的:(Spring)