目录
Spring框架的核心功能之AOP技术
AOP的概述
Spring的AOP的简单介绍
AOP概述
什么是AOP?
Spring底层AOP实现
Spring的AOP的简介
AOP开发的相关术语
Spring框架的AOP的底层实现
JDK的动态代理(代码了解,理解原理)
CGLIB的代理技术(代码了解)
Spring基于AspectJ的AOP的开发
AOP的相关术语
AspectJ的XML方式完成AOP的开发
切入点的表达式
AOP的通知类型
通用化切入点表达式的写法
Spring框架的核心功能之AOP技术
AOP的概述
1. 什么是AOP的技术? * 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程 * AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构 * AOP最早由AOP联盟的组织提出的,制定了一套规范.Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范 * 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术 * AOP是OOP(面向对象)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型 * 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率 2. AOP:面向切面编程.(思想.---解决OOP遇到一些问题) 3. AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存) 4. 为什么要学习AOP * 可以在不修改源代码的前提下,对程序进行增强!!
Spring的AOP的简单介绍
Spring的AOP的简单介绍,主要包括Spring的AOP的简单介绍使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
AOP概述
什么是AOP?
-
AOP:面向切面编程,AOP是OOP的扩展和延伸,解决OOP中遇到的问题
-
AOP使用了横向抽取代替了传统的纵向继承;下面有一个示例
-
假设Dao层在调用save方法保存到数据库前需要加上一个权限校验,传统的方式是通过写一个通用的BaseDao类并写一个checkPri的权限校验方法,然后让所有需要进行权限校验的类继承通用BaseDao,那么继承BaseDao的类就均能够在执行save方法前调用权限校验方法;这就是传统的纵向继承;
-
而横向抽取是为所有的Dao生成一个代理,然后访问的时候就是访问代理对象UserDaoProxy,相当于一个增强方法
Spring底层AOP实现
Spring底层的AOP采用了两种动态代理:
-
Jdk动态代理:只能对实现接口的类产生代理
-
Cglib动态代理(类似于Javassist第三方代理技术,不要用final修饰类):对没有实现接口的类产生代理对象,生成子类对象
-
两种代理的简单示例:Spring底层AOP的原理示例(JDK动态代理和cglib动态代理)_白丶程序猿的博客-CSDN博客
Spring的AOP的简介
-
AOP思想最早是由AOP联盟组织提出的,Spring是使用这种思想最好的框架。
-
Spring的AOP原来有自己实现的方式(非常繁琐)。AspectJ是一个AOP的框架,Spring在意识到它的不足后,将AspectJ引入到了Spring的AOP中,作为自身的开发
-
Spring有两套AOP的开发方式:(1)Spring传统方式(弃用);(2)Spring基于AspectJ的AOP开发
AOP开发的相关术语
-
连接点(JoinPoint):可以被拦截到的点,通俗的来说就是一个类中的方法就称为连接点
-
切入点(Pointcut):真正被拦截到的点,即一个类中真正被增强的方法,如上图假设我们想要对save方法增强,。
-
通知、增强(Advice):假设对save方法进行权限校验,那么权限校验的方法(checkPri)就是通知;这是针对方法层面的增强
-
引介(Introduction):类层面的增强,比如在类中添加一个属性或者增加一个方法,这个类就被增强了,就称为引介
-
目标(Target):就是被增强的对象
-
代理(proxy):当需要对一个方法增强时,需要生成一个代理
-
切面(Aspect):多个通知和多个切入点的组合
Spring框架的AOP的底层实现
1. Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种 1. 基于JDK的动态代理 * 必须是面向接口的,只有实现了具体接口的类才能生成代理对象 2. 基于CGLIB动态代理 * 对于没有实现了接口的类,也可以产生代理,产生这个类的子类的方式 2. Spring的传统AOP中根据类是否实现接口,来采用不同的代理方式 1. 如果实现类接口,使用JDK动态代理完成AOP 2. 如果没有实现接口,采用CGLIB动态代理完成AOP
JDK的动态代理(代码了解,理解原理)
1. 使用Proxy类来生成代理对象的一些代码如下: /** * 使用JDK的方式生成代理对象 * @author Administrator */ public class MyProxyUtils { public static UserDao getProxy(final UserDao dao) { // 使用Proxy类生成代理对象 UserDao proxy = (UserDao) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), new InvocationHandler() { // 代理对象方法一执行,invoke方法就会执行一次 //参数1 就是那个生成的代理对象 //参数2 代理对象所调用的方法对象 //参数3 代理对象所调用的方法对象中的参数对象数组 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("save".equals(method.getName())){ System.out.println("记录日志..."); // 开启事务 } // 提交事务 // 让dao类的save或者update方法正常的执行下去 return method.invoke(dao, args); } }); // 返回代理对象 return proxy; } }
@Test public void run1(){ // 目标对象 UserDao dao = new UserDaoImpl(); dao.save(); dao.update(); System.out.println("============================="); // 使用工具类,获取到代理对象 UserDao proxy = MyProxyUtils.getProxy(dao); // 调用代理对象的方法 proxy.save(); proxy.update(); }
CGLIB的代理技术(代码了解)
1. 引入CBLIB的开发包 * 如果想使用CGLIB的技术来生成代理对象,那么需要引入CGLIB的开发的jar包,在Spring框架核心包中已经引入了CGLIB的开发包了。所以直接引入Spring核心开发包即可! 2. 编写相关的代码 public static OrderDaoImpl getProxy(){ // 创建CGLIB核心的类 Enhancer enhancer = new Enhancer(); // 设置父类 enhancer.setSuperclass(OrderDaoImpl.class); // 设置回调函数 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if("save".equals(method.getName())){ // 记录日志 System.out.println("记录日志了..."); } return methodProxy.invokeSuper(obj, args); } }); // 生成代理对象 OrderDaoImpl proxy = (OrderDaoImpl) enhancer.create(); return proxy; }
Spring基于AspectJ的AOP的开发
AOP的相关术语
1. Joinpoint(连接点) -- 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点 2. Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义 ,也就是我们想对那个方法做增强,这个方法叫做切入点 3. Advice(通知/增强) -- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) 4. Introduction(引介) -- 引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field 5. Target(目标对象) -- 代理的目标对象 6. Weaving(织入) -- 是指把增强应用到目标对象来创建新的代理对象的过程,简单来说也就是生成代理对象的这个过程,称之为织入。 7. Proxy(代理) -- 一个类被AOP织入增强后,就产生一个结果代理类 8. Aspect(切面) -- 是切入点和通知的结合,切面是以后咱们自己来编写和配置的
AspectJ的XML方式完成AOP的开发
1. 步骤一:创建JavaWEB项目,引入具体的开发的jar包 * 先引入Spring框架开发的基本开发包 com.springsource.org.apache.commons.logging-1.1.1.jar com.springsource.org.apache.log4j-1.2.15.jar spring-beans-5.0.2.RELEASE.jar spring-context-5.0.2.RELEASE.jar spring-core-5.0.2.RELEASE.jar spring-expression-5.0.2.RELEASE.jar * 再引入Spring框架的AOP的开发包 * spring的传统AOP的开发的包 * spring-aop-5.0.2.RELEASE.jar * com.springsource.org.aopalliance-1.0.0.jar 这个在依赖包中找 * aspectJ的开发包 * com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar 这个在依赖包中找 * spring-aspects-5.0.2.RELEASE.jar Spring整合AspectJ的jar包 *想要做测试的话可以引入Spring测试的包 spring-test-5.0.2.RELEASE.jar **当然如果是Maven项目我们只需要在pom.xml文件添加jar包坐标即可 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency> 步骤二:创建Spring的配置文件applicationContext.xml,引入具体的AOP的schema约束 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> </beans>
3. 步骤三:创建包结构,编写具体的接口和实现类
* org.westos.demo3 * CustomerDao -- 接口 * CustomerDaoImpl -- 实现类 4.步骤四:将目标类配置到Spring中 <bean id="customerDao" class="org.westos.demo3.CustomerDaoImpl"/>
5. 步骤五:定义切面类 public class MyAspectXml { // 定义通知 public void log(){ System.out.println("记录日志..."); } } 6. 步骤六:在配置文件中定义切面类 <bean id="myAspectXml" class="org.westos.demo3.MyAspectXml"/> 7. 步骤七:在配置文件中完成aop的配置 <aop:config> <!-- 引入切面类 --> <aop:aspect ref="myAspectXml"> <!-- 定义通知类型:切面类的方法和切入点的表达式 --> <aop:before method="log" pointcut="execution(public void org.westos.demo3.CustomerDaoImpl.save())"/> </aop:aspect> </aop:config> 8. 完成测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class Demo3 { @Resource(name="customerDao") //Java的这个注解,没有开启注解扫描,也是可以用的 private CustomerDao customerDao; @Test public void run1(){ customerDao.save(); customerDao.update(); customerDao.delete(); } }
切入点的表达式
1. 再配置切入点的时候,需要定义表达式,重点的格式如下:execution(public * *(..)),具体展开如下: * 切入点表达式的格式如下: * execution([修饰符] 返回值类型 包名.类名.方法名(参数)) * 修饰符可以省略不写,不是必须要出现的。 public 可以省略不写 * 返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用 * 代替。 例如: public * org.westos.demo3.BookDaoImpl.save() * 包名例如:org.westos.demo3.BookDaoImpl * 首先org是不能省略不写的,但是可以使用 * 代替 例如:org.*.*.BookDaoImpl * 中间的包名可以使用 * 号代替 例如:public void *.*.*.BookDaoImpl.save() * 如果想省略中间的包名可以使用 *..* 例如: public void *..*.BookDaoImpl.save() * 类名也可以使用 * 号代替,也有类似的写法:*DaoImpl 意思是这个类以DaoImp结尾 例如:public void org.westos.demo3.*DaoImpl.save() * 方法也可以使用 * 号代替 例如:public void org.westos.demo3.*DaoImpl.*() 通配所有方法名 例如: public void org.westos.demo3.*DaoImpl.save*() 通配方法以save开头 * 参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 .. 例如:public void org.westos.demo3.*DaoImpl.save(*) 通配配一个参数 例如:public void org.westos.demo3.*DaoImpl.save(..) 统配任意个数的参数,或者没有参数
AOP的通知类型
1. 前置通知 before * 在目标类的方法执行之前执行。 * 配置文件信息:<aop:before method="before" pointcut-ref="myPointcut3"/> * 应用:可以对方法的参数来做校验 2. 最终通知 after * 在目标类的方法执行之后执行,如果程序出现了异常,最终通知也会执行。 * 在配置文件中编写具体的配置:<aop:after method="after" pointcut-ref="myPointcut3"/> * 应用:例如像释放资源 3. 后置通知 * 方法正常执行后的通知。 * 在配置文件中编写具体的配置:<aop:after-returning method="afterReturning" pointcut-ref="myPointcut2"/> * 应用:可以修改方法的返回值 4. 异常抛出通知 * 在抛出异常后通知 * 在配置文件中编写具体的配置: throwing="e" 这个e的意思就是你切面类里面配置的afterThrowingLog(Exception e)这个增强的方法的形参的名称 //当你目标对象中delCustomer()方法中出现了异常,那么切面类里面配置的afterThrowingLog()这个增强的方法就会执行 <aop:after-throwing method="afterThrowingLog" pointcut="execution(public * *..*.Customer*.delCustomer(..))" throwing="e"/>
//切面类中 抛出异常后通知 需要在增强的方法形参里面提供一个异常对象,当你切入点里面抛出你配置的这个异常类型 //这个增强的方法,就会执行 public void afterThrowingLog(Exception e) { System.out.println("方法抛出异常后执行通知 日志记录"); } * 应用:包装异常的信息 5. 环绕通知 * 方法的执行前后执行。 * 在配置文件中编写具体的配置:<aop:around method="around" pointcut-ref="myPointcut2"/> * 要注意:目标的方法默认不执行,需要使用ProceedingJoinPoint对来让目标对象的方法执行。 /** * 环绕通知:方法执行之前和方法执行之后进行通知,默认的情况下,目标对象的方法不能执行的。需要手动让目标对象的方法执行 */ public void around(ProceedingJoinPoint joinPoint){ //ProceedingJoinPoint 固定的一个类 使用他然目标对象的方法执行. System.out.println("环绕通知1..."); try { // 手动让目标对象的方法去执行 joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("环绕通知2..."); }
示例如下
public class XmlAopDemoUserLog { // 方法执行前通知 public void beforeLog() { System.out.println("开始执行前置通知 日志记录"); } // 方法执行完后通知 public void afterLog() { System.out.println("开始执行后置通知 日志记录"); } // 执行成功后通知 public void afterReturningLog() { System.out.println("方法成功执行后通知 日志记录"); } // 抛出异常后通知 public void afterThrowingLog(Exception e) { System.out.println("方法抛出异常后执行通知 日志记录"); } // 环绕通知 public Object aroundLog(ProceedingJoinPoint joinpoint) { Object result = null; try { System.out.println("环绕通知开始 日志记录"); long start = System.currentTimeMillis(); //有返回参数 则需返回值 result = joinpoint.proceed(); long end = System.currentTimeMillis(); System.out.println("总共执行时长" + (end - start) + " 毫秒"); System.out.println("环绕通知结束 日志记录"); } catch (Throwable t) { System.out.println("出现错误"); } return result; } }
通用化切入点表达式的写法
<!--定义切面 指定拦截方法时 做什么--> <bean id="xmlAopDemoUserLog" class="com.ganji.demo.service.aspect.XmlAopDemoUserLog"></bean> <aop:config> <aop:aspect ref="xmlAopDemoUserLog"> <!--指定切面--> <!--定义切点--> <aop:pointcut id="logpoint" expression="execution(* com.ganji.demo.service.user.UserService.GetDemoUser(..))"></aop:pointcut> <!--定义连接点--> <aop:before pointcut-ref="logpoint" method="beforeLog"></aop:before> <aop:after pointcut-ref="logpoint" method="afterLog"></aop:after> <aop:after-returning pointcut-ref="logpoint" method="afterReturningLog"></aop:after-returning> <aop:after-throwing pointcut-ref="logpoint" method="afterThrowingLog"></aop:after-throwing> </aop:aspect> </aop:config>