Spring Aop: 从醉生梦死到豁然开朗

AOP:Aspect Oriented Programming 面向切面编程。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP只是一种编程思想,不要想太多。

相信很多朋友在第一次接触AOP的时候是很蒙蔽的,从到学到尾都不太清楚????我是谁我在哪?笔者初学的时候也是一样,但是对于AOP我们又不得不去深入了解一下,因为热爱(生活所迫)是吧!这篇文章也是我对于SSM深入剖析的开篇,最近事情太多了文章可能做不到源码级别的深度,但是正确认识Aop是没有问题的,告别蒙蔽是没问题的!????

????看了一眼IOC和AOP的源码,得花点时间来整理,????莽着看实在是太头疼了!

下面就由浅入深来剖析一下Aop的特性和原理。

Aop应用场景

  • 事务管理、性能监视、安全检查、缓存 、日志等

Aop术语

1.target:目标类,需要被代理的类。例如:UserService
2.Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法。例如:所有的方法
3.PointCut 切入点:已经(我让他加强)被增强的连接点。例如:addUser()
4.advice 通知/增强,增强代码。例如:after、before
5.Weaving(织入):是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程.
6.proxy 代理类
7. Aspect(切面): 是切入点pointcut(被增强的方法)和通知advice(增强方法)的结合(连线)
一个切入点和一个通知,组成成一个特殊的面。

这张图画的很好直接拿来用了。

Spring Aop: 从醉生梦死到豁然开朗
这里基本上可以引出一个设计模式:代理模式,不清楚的可以看我以前的博客设计模式—代理模式,代理可以分为动态和静态代理,AOP里面主要使用了cglib的动态代理。具体代理实现我就不在这里多做介绍了!

实现一个AOP

介绍顺序:半自动—全自动—xml方式—注解方式。

半自动代理实现
/**
 * 
 * @author 作者xianglei:
 * 
 * @version 创建时间:2019年4月3日 下午7:36:33 com.dxl.aspect
 *  切面类
 * 
 */

public class MyAspect implements MethodInterceptor {

	public Object invoke(MethodInvocation arg0) throws Throwable {
		System.out.println("前");

		// 手动执行目标方法
		Object obj = arg0.proceed();

		System.out.println("后");
		return obj;
	}

}

/**
 * 
 * @author 作者xianglei:
 * 
 * @version 创建时间:2019年4月3日 下午7:30:40 com.xianglei.service
 *
 * 
 */

public interface PersonService {
	void add();
	void update();
	void delete();
}

/**
 * 
 * @author 作者xianglei:
 * 
 * @version 创建时间:2019年4月3日 下午7:39:04 com.dxl.test
 *
 * 
 */

public class BanZdTest {
	public static void main(String[] args) {
		ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("SpringB.xml");
		PersonService bean = (PersonService) classPathXmlApplicationContext.getBean("proxyServiceId");
		bean.add();
		bean.delete();
		bean.update();
	}
}

配置文件

<!-- 1 被增强的目标类 -->
	<bean id="userServiceId" class="com.quanzidong.service.impl.PersonServiceImpl"></bean>  <!-- bean 注入只能是类 -->
	<!-- 2 创建切面类 设定通知类型 -->
	<bean id="myAspectId" class="com.dxl.aspect.MyAspect"></bean>
	<!-- 3 代理类处理 链接(织入)过程 -->
	<bean id="proxyServiceId" class="org.springframework.aop.framework.ProxyFactoryBean">
		<property name="interfaces" value="com.dxl.service.PersonService"></property>   <!--必须是一个接口 -->
		<property name="target" ref="userServiceId"></property>
		<property name="interceptorNames" value="myAspectId"></property>
	</bean>

ProxyFactoryBean类是隶属Spring框架下的一个代理类。使用Spring提供的类ProxyFactoryBean是创建AOP的最基本的方式。用的不多了解就行
Spring Aop: 从醉生梦死到豁然开朗

这里的配置文件里面注入了目标类和切面类对着上面那个图片来看 org.springframework.aop.framework.ProxyFactoryBean 这个类就是实现了了一个(代理)织入的过程。你只需要给他注入 interfaces,target,interceptorNames便可以实现对目标类的方法的增强。

全自动实现Aop
public class MyAspect implements MethodInterceptor {

	public Object invoke(MethodInvocation arg0) throws Throwable {
		System.out.println("前");

		// 手动执行目标方法
		Object obj = arg0.proceed();

		System.out.println("后");
		return obj;
	}

}

public interface PersonService {
	void add();
	void update();
	void delete();
}

public class PersonServiceImpl implements PersonService {

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

	}

	public void update() {
		System.out.println("更新");

	}

	public void delete() {
		System.out.println("删除");

	}

}
public class c {
	public static void main(String[] args) {
		ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("SpringQ.xml");
		PersonService bean = (PersonService) classPathXmlApplicationContext.getBean("userServiceId");
		bean.add();
		bean.delete();
		bean.update();
	}
}

配置文件

	<!-- 1 被增强的目标类 -->
	<bean id="userServiceId" class="com.quanzidong.service.impl.PersonServiceImpl"></bean>  <!-- bean 注入只能是类 -->
	<!-- 2 创建切面类 设定通知类型 -->
	<bean id="myAspectId" class="com.quanzidong.aspect.MyAspect"></bean>
	<!-- true 使用cglib -->
	<aop:config proxy-target-class="true">
		<!-- aop:pointcut 我想要增强的点 -->
		<aop:pointcut expression="execution(* com.quanzidong.service.*.*(..))"
			id="myPointCut" />
		<!-- 拿哪些内容增强到哪些方法 -->
		<aop:advisor advice-ref="myAspectId" pointcut-ref="myPointCut" />
	</aop:config>

这个和前面那个半自动的比起来有什么区别呢?咋就成了全自动了?全自动体现在:**不需要通过id获取代理类了(getBean(“class”)),直接在执行被增强的方法时执行增强(注意两处代理的getbean的区别)。**也就是你拿你要用的对象,调用需要调用的方法,其他的交给天意。????


AOP的基础的使用,上面的方法其实就已经可以实现了,但懒惰是永无止境的,于是有了用注解的方式来将各种bean的管理通过扫包交给Spring框架管理。

AspectJ注解技术实现AOP

@AspectJ 是AspectJ1.5新增功能,通过JDK5注解技术,允许直接在Bean类中定义切面。新版本Spring框架,建议使用AspectJ方式来开发AOP。
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。

先来个代码尝尝咸淡????

  • Xml方式实现

      * 配置切面类 (重复执行代码形成的类)
      * aop配置
      	拦截哪些方法 / 拦截到方法后应用通知代码
    


package com.xml.aspect;

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

/**
 * 
 * @author 作者xianglei:
 * 
 * @version 创建时间:2019年4月3日 下午8:10:27 com.xml.aspect
 * 
 * 
 */

public class MyAspect {
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知 : " + joinPoint.getSignature().getName());
	}

	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(JoinPoint joinPoint) {
		System.out.println("最终通知");
	}

}



package com.xml.service;

/**
 * 
 * @author 作者xianglei:
 * 
 * @version 创建时间:2019年4月3日 下午7:30:40 com.xianglei.service
 *
 * 
 */

public interface PersonService {
	void add();
	void update();
	void delete();
}


/**
 * 
 * @author 作者xianglei:
 * 
 * @version 创建时间:2019年4月3日 下午7:30:40 com.xianglei.service
 *
 * 
 */

public class PersonServiceimpl implements com.xml.service.PersonService {

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

	}

	public void update() {
		// TODO Auto-generated method stub
		System.out.println("更新");
	}

	public void delete() {
		// TODO Auto-generated method stub
		System.out.println("删除");
	}

}
public class test {
	public static void main(String[] args) {
		ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("Springxml.xml");
		PersonService bean = (PersonService) classPathXmlApplicationContext.getBean("userServiceId");
		bean.add();
		bean.delete();
		bean.update();
	}
}


<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" xmlns:tx="http://www.springframework.org/schema/tx"
	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
         http://www.springframework.org/schema/tx
     	 http://www.springframework.org/schema/tx/spring-tx.xsd">
	<!-- 1 被增强的目标类 -->
	<bean id="userServiceId" class="com.xml.service.impl.PersonServiceimpl"></bean>  <!-- bean 注入只能是类 -->
	<!-- 2 创建切面类 设定通知类型 -->
	<bean id="myAspectId" class="com.xml.aspect.MyAspect"></bean>
	<!-- 不使用cglib proxy-target-class="false"-->
	<aop:config >
		<aop:aspect ref="myAspectId">
			<aop:pointcut expression="execution(* com.xml.service.*.*(..))" id="myPointCut"/>
			
			<!-- 3.1 前置通知 
				<aop:before method="" pointcut="" pointcut-ref=""/>
					method : 通知,及方法名
					pointcut :切入点表达式,此表达式只能当前通知使用。
					pointcut-ref : 切入点引用,可以与其他通知共享切入点。
				通知方法格式:public void myBefore(JoinPoint joinPoint){
					参数1:org.aspectj.lang.JoinPoint  用于描述连接点(目标方法),获得目标方法名等
				例如:
					-->
			<aop:before method="myBefore" pointcut-ref="myPointCut"/>
		
			
			<!-- 3.2后置通知  ,目标方法后执行,获得返回值
				<aop:after-returning method="" pointcut-ref="" returning=""/>
					returning 通知方法第二个参数的名称
				通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
					参数1:连接点描述
					参数2:类型Object,参数名 returning="ret" 配置的
				例如:
		
			-->
				<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
			<!-- 3.3 环绕通知 
				<aop:around method="" pointcut-ref=""/>
				通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
					返回值类型:Object
					方法名:任意
					参数:org.aspectj.lang.ProceedingJoinPoint
					抛出异常
				执行目标方法:Object obj = joinPoint.proceed();
				例如:
			
			-->
			<aop:around method="myAround" pointcut-ref="myPointCut"/>
			<!-- 3.4 抛出异常
				<aop:after-throwing method="" pointcut-ref="" throwing=""/>
					throwing :通知方法的第二个参数名称
				通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
					参数1:连接点描述对象
					参数2:获得异常信息,类型Throwable ,参数名由throwing="e" 配置
				例如:
			
			-->
			<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
			<!-- 3.5 最终通知 -->			
			
			<aop:after method="myAfter" pointcut-ref="myPointCut"/>
			
			
		</aop:aspect>
	</aop:config>

</beans>

xml方式实现的你就的先关注xml配置文件,看不懂?没关系补充几个知识点。

AspectJ 通知类型
  • before:前置通知(应用:各种校验)
    在方法执行前执行,如果通知抛出异常,阻止方法运行
  • afterReturning:后置通知(应用:常规数据处理)
    方法正常返回后执行,如果方法中抛出异常,通知无法执行
    必须在方法执行后才执行,所以可以获得方法的返回值。
  • around:环绕通知(应用:十分强大,可以做任何事情)
    方法执行前后分别执行,可以阻止方法的执行
    必须手动执行目标方法
  • afterThrowing:抛出异常通知(应用:包装异常信息)
    方法抛出异常后执行,如果方法没有抛出异常,无法执行
  • after:最终通知(应用:清理现场)
    方法执行完毕后执行,无论方法中是否出现异常
表达式语言
语法:execution(修饰符  返回值  包.类.方法名(参数) throws异常)
		修饰符,一般省略
			public		公共方法
			*			任意
		返回值,不能省略
			void			返回没有值
			String		返回值字符串
			* 			任意
		包,[省略]
			com.xianglei.crm			固定包
			com.xianglei.crm.*.service	crm包下面子包任意 (例如:com.xianglei.crm.staff.service)
			com.xianglei.crm..			crm包下面的所有子包(含自己)
			com.xianglei.crm.*.service..	crm包下面任意子包,固定目录service,service目录任意包
		类,[省略]
			UserServiceImpl			指定类
			*Impl					以Impl结尾
			User*					以User开头
			*						任意
		方法名,不能省略
			addUser					固定方法
			add*						以add开头
			*Do						以Do结尾
			*						任意
		(参数)
			()						无参
			(int)						一个整型
			(int ,int)					两个
			(..)						参数任意
  • 注解方式实现
@Component   // 在此处声明一个Component 是因为Spring扫描注解并不能识别AspectJ 因此在此处声明 
@Aspect
public class MyAspect {

	@Before("execution(* com.anno.service.impl.*(..))")
	//@Before("execution(* com.anno.service.impl.*(..))")
	public void myBefore(JoinPoint joinPoint) {
		System.out.println("前置通知 : " + joinPoint.getSignature().getName());
	}

/*	// 可以声明公共切入点 然后通过value设定切入点
	@Pointcut("execution(*com.anno.service.impl.*(..))")
	private void myPointCut() {
	}
	
	@AfterReturning(value="myPointCut()" ,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(JoinPoint joinPoint) {
		System.out.println("最终通知");
	}

}

这里你可以设置一个公共切入点,其他通知就可以通过设置value来联系这个切入点,不用也行。二选一。

@Service
public class PersonServiceImpl implements PersonService {

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

	}

	public void update() {
		// TODO Auto-generated method stub
		System.out.println("更新");
	}

	public void delete() {
		// TODO Auto-generated method stub
		System.out.println("删除");
	}

}

public class Test {
	public static void main(String[] args) {
		ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("Springanno.xml");
		PersonService bean = (PersonService) classPathXmlApplicationContext.getBean("personServiceImpl");
		bean.add();
		bean.delete();
		bean.update();
	}
}

配置文件

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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/aop 
       					   http://www.springframework.org/schema/aop/spring-aop.xsd
       					   http://www.springframework.org/schema/context 
       					   http://www.springframework.org/schema/context/spring-context.xsd">
	<!-- 1.扫描 注解类 -->
	<context:component-scan base-package="com.anno"></context:component-scan>
	<!-- 2.确定 aop注解生效 -->
	<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
	<!--<bean  id="aspectBean" class="com.anno.aspect.MyAspect"></bean> 手动注入切面类-->
</beans>

配置文件看起来简洁了许多,但是注解方式有一个缺点就是对源码的侵入性比起xml方式要大一些,毕竟xml方式在不需要AOP的时候不使用就行了。
这里就两点,一个是包扫描:让Spring知道哪些组件分别对应了哪些类。一个是开启Aspect的注解,使@Aspect注解生效。

官方关于开启AspectJ支持可以使用一下两种方法。

Spring Aop: 从醉生梦死到豁然开朗

抛俩问题?
  • SpringAOP 和AspectJ有什么关系
    Spring是借助AspectJ的语法来实现自己的Aop Spring自己的那一套用起来很很蛋疼。+
  • SpringAop和Aop有什么区别
    前者只是实现Aop的一种手段,后者是AOP思想的最终目标。

总结

回看这篇文章你应该知道了 1.AOP实现原理,2. AOP的xml和注解使用方式。3.清楚了AOP的基本术语。