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(增强方法)的结合(连线)
一个切入点和一个通知,组成成一个特殊的面。
这张图画的很好直接拿来用了。
这里基本上可以引出一个设计模式:代理模式,不清楚的可以看我以前的博客设计模式—代理模式,代理可以分为动态和静态代理,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的最基本的方式。用的不多了解就行。
这里的配置文件里面注入了目标类和切面类对着上面那个图片来看 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支持可以使用一下两种方法。
抛俩问题?
- SpringAOP 和AspectJ有什么关系
Spring是借助AspectJ的语法来实现自己的Aop Spring自己的那一套用起来很很蛋疼。+ - SpringAop和Aop有什么区别
前者只是实现Aop的一种手段,后者是AOP思想的最终目标。
总结
回看这篇文章你应该知道了 1.AOP实现原理,2. AOP的xml和注解使用方式。3.清楚了AOP的基本术语。