@[toc] 在 Spring框架学习一 中主要讲的是一些Spring的概述、Spring工厂、Spring属性注入以及IOC入门,其中最重要的是IOC,上一篇中IOC大概讲的小结一下:
然后呢这一篇中主要讲一下Spring中除了IOC之外的另一个重要的核心:AOP,在Spring中IOC也好,AOP也好,都必须会二者的XML开发以及注解开发,也就是说 IOC和AOP的XML开发以及注解开发都要掌握
从专业的角度来讲(千万不要问我有多专业,度娘是我表锅不对是表嫂QAQ):
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
从通俗易懂且不失风趣的角度来讲:(来自武哥文章 谈谈Spring中的IOC和AOP概念 )
面向切面编程的目标就是分离关注点。什么是关注点呢?就是你要做的事,就是关注点。假如你是个公子哥,没啥人生目标,天天就是衣来伸手,饭来张口,整天只知道玩一件事!那么,每天你一睁眼,就光想着吃完饭就去玩(你必须要做的事),但是在玩之前,你还需要穿衣服、穿鞋子、叠好被子、做饭等等等等事情,这些事情就是你的关注点,但是你只想吃饭然后玩,那么怎么办呢?这些事情通通交给别人去干。在你走到饭桌之前,有一个专门的仆人A帮你穿衣服,仆人B帮你穿鞋子,仆人C帮你叠好被子,仆人C帮你做饭,然后你就开始吃饭、去玩(这就是你一天的正事),你干完你的正事之后,回来,然后一系列仆人又开始帮你干这个干那个,然后一天就结束了! AOP的好处就是你只需要干你的正事,其它事情别人帮你干。也许有一天,你想裸奔,不想穿衣服,那么你把仆人A解雇就是了!也许有一天,出门之前你还想带点钱,那么你再雇一个仆人D专门帮你干取钱的活!这就是AOP。每个人各司其职,灵活组合,达到一种可配置的、可插拔的程序结构。 从Spring的角度看,AOP最大的用途就在于提供了事务管理的能力。事务管理就是一个关注点,你的正事就是去访问数据库,而你不想管事务(太烦),所以,Spring在你访问数据库之前,自动帮你开启事务,当你访问数据库结束之后,自动帮你提交/回滚事务!
Spring 的 AOP 的由来:AOP 最早由 AOP 联盟的组织提出的,制定了一套规范.Spring 将 AOP 思想引入到框架中,必须遵守 AOP 联盟的规范.
Spring 的 AOP 的底层用到两种代理机制:
spring底层会完成自动代理,实现了接口的类默认使用的是JDK 的动态代理,相反的,没有实现接口的类默认使用的是Cglib 的动态代理,底层代码可以不懂但这个概念一定要知道,不然会被鄙视的,O(∩_∩)O哈哈~,下面是底层代码,有兴趣的可以了解了解。
public class MyJDKProxy implements InvocationHandler { private UserDao userDao; public MyJDKProxy(UserDao userDao) { this.userDao = userDao; } // 编写工具方法:生成代理: public UserDao createProxy() { UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao .getClass().getClassLoader(), userDao.getClass() .getInterfaces(), this); return userDaoProxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("save".equals(method.getName())) { System.out.println("权限校验================"); } return method.invoke(userDao, args); } } 复制代码
public class MyCglibProxy implements MethodInterceptor { private CustomerDao customerDao; public MyCglibProxy(CustomerDao customerDao) { this.customerDao = customerDao; } // 生成代理的方法: public CustomerDao createProxy() { // 创建 Cglib 的核心类: Enhancer enhancer = new Enhancer(); // 设置父类: enhancer.setSuperclass(CustomerDao.class); // 设置回调: enhancer.setCallback(this); // 生成代理: CustomerDao customerDaoProxy = (CustomerDao) enhancer.create(); return customerDaoProxy; } @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { if ("delete".equals(method.getName())) { Object obj = methodProxy.invokeSuper(proxy, args); System.out.println("日志记录================"); return obj; } return methodProxy.invokeSuper(proxy, args); } } 复制代码
首先,Spring为什么不直接进行Spring的AOP开发呢,而要基于Aspectj呢,是因为,Spring自己的AOP开发实现方式(传统的AOP开发)繁琐且复杂,效率极低,于是传统的AOP开发基本上弃用了,相反Aspectj的AOP开发效率高,所以AOP开发一般是Spring 的基于 AspectJ 的 AOP 开发。
Aop是一种非常高深的思想,当然会有非常专业的相关术语了(这弯绕的,你打几分?)
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只 支持方法类型的连接点.Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义.Advice(通知/增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知.通知分为前置通知,后置 通知,异常通知,最终通知,环绕通知(切面要完成的功能)Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类 动态地添加一些方法或 Field.Target(目标对象):代理的目标对象Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程. spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装在期织入Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类Aspect(切面): 是切入点和通知(引介)的结合
基于专业的角度实例分析(相对来说易理解,什么?画质差?咳咳...1080p蓝光画质...哎哎哎..大哥..别打...别打...别打脸):
引入jar包:基础六个jar包、AOP联盟jar包、spring的AOPjar包、aspectJ的jar包、spring整合aspectj的jar包
spring 的传统 AOP 的开发的包: spring-aop-4.2.4.RELEASE.jar com.springsource.org.aopalliance-1.0.0.jar
aspectJ 的开发包: com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aspects-4.2.4.RELEASE.jar
引入 AOP 约束:
<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> 复制代码
创建接口和类:
public interface OrderDao { public void save(); public void update(); public void delete(); public void find(); } public class OrderDaoImpl implements OrderDao { @Override public void save() { System.out.println("保存订单..."); } @Override public void update() { System.out.println("修改订单..."); } @Override public void delete() { System.out.println("删除订单..."); } @Override public void find() { System.out.println("查询订单..."); } } 复制代码
<!-- 目标类配置:被增强的类 --> <bean id="orderDao" class="com.gx.spring.demo3.OrderDaoImpl"></bean> 复制代码
前提:引入 spring-test.jar 测试的jar包,整合 Junit 单元测试之后就不需要每次都重复注册工厂,只要固定格式在测试类上写两个注解,需要的属性直接注入,之后只关心自己的测试类即可
//固定注解写法(前提:引入 spring-test.jar 测试的jar包) @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo3 { @Resource(name = "orderDao") //需要的属性直接注入(前提:引入 spring-test.jar 测试的jar包) private OrderDao orderDao; @Test public void demo1() { orderDao.save(); orderDao.update(); orderDao.delete(); orderDao.find(); } } 复制代码
运行demo出现如下效果:
到这里,就需要需要对通知类型了解一下(前三者常用):
前置通知:在目标方法执行之前执行.
后置通知:在目标方法执行之后执行
如果要获得后置通知中的返回值,必须注意的是: 环绕通知:在目标方法执行前和执行后执行
异常抛出通知 :在目标方法执行出现 异常的时候 执行 最终通知:无论目标方法是否出现异常 最终通知都会 执行.
execution(表达式)
表达式 : [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数)
切入点表达式所以就是execution( [方法访问修饰符] 方法返回值 包名.类名.方法名(方法的参数))
其中 [ ] 中的方法访问修饰符可有可无
切入点表达式各类型例子:
public * com.gx.spring.dao. * .*(..) com.gx.spring.dao.*.*(..) com.gx.spring.dao.UserDao+.*(..) com.gx.spring.dao..*.*(..) 复制代码
好了,了解了通知类型以及切入点表达式之后就可以来 编写一个切面类玩起来了QAQ
public class MyAspectXml { // 前置增强 public void before(){ System.out.println("前置增强==========="); } } 复制代码
<!-- 配置切面类 --> <bean id="myAspectXml" class="com.gx.spring.demo3.MyAspectXml"></bean> <!-- 进行 aop 的配置 --> <aop:config> <!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 --> <aop:pointcut expression="execution(* com.gx.spring.demo3.OrderDao.save(..))" id="pointcut1"/> <!-- 配置切面 --> <aop:aspect ref="myAspectXml"> <aop:before method="before" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config> 复制代码
需要注意的点我都规划出来了(不用夸我,我知道我长得帅QnQ)
<!-- 配置切面类 --> <bean id="myAspectXml" class="com.gx.demo3.MyAspectXml"></bean> <!-- 进行 aop 的配置 --> <aop:config> <!-- 配置切入点表达式:哪些类的哪些方法需要进行增强 --> <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.save(..))" id="pointcut1"/> <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.delete(..))" id="pointcut2"/> <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.update(..))" id="pointcut3"/> <aop:pointcut expression="execution(* com.gx.spring.demo3.*Dao.find(..))" id="pointcut4"/> <!-- 配置切面 --> <aop:aspect ref="myAspectXml"> <aop:before method="before" pointcut-ref="pointcut1"/> <aop:after-returning method="afterReturing"pointcut-ref="pointcut2"/> <aop:around method="around" pointcut-ref="pointcut3"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4"/> <aop:after method="after" pointcut-ref="pointcut4"/> </aop:aspect> </aop:config> 复制代码
引入的jar包如下:
编写目标类:
package com.gx.spring.demo1; public class OrderDao { public void save(){ System.out.println("保存订单..."); } public void update(){ System.out.println("修改订单..."); } public String delete(){ System.out.println("删除订单..."); return "鄢寒"; } public void find(){ System.out.println("查询订单..."); } } 复制代码
XML配置:
<!-- 配置目标类 --> <bean id="orderDao" class="com.gx.spring.demo1.OrderDao"> </bean> 复制代码
编写切面类
package com.gx.spring.demo1; 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; /** * 切面类:注解的切面类 * @author jt */ public class MyAspectAnno { public void before(){ System.out.println("前置增强==========="); } } 复制代码
XML配置:
<!-- 配置切面类 --> <bean id="myAspect" class="com.gx.spring.demo1.MyAspectAnno"> </bean> 复制代码
1、在配置文件中打开注解的AOP开发
<!-- 在配置文件中开启注解的AOP的开发 --> <aop:aspectj-autoproxy/> 复制代码
2、在切面类上使用注解 在类上使用@Aspect注解代表这是一个切面类 在方法上注入属性@Before(execution表达式)代表前置增强
@Aspect public class MyAspectAnno { @Before(value="execution(* com.gx.spring.demo1.OrderDao.save(..))") public void before(){ System.out.println("前置增强==========="); } } 复制代码
package com.gx.spring.demo1; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Spring的AOP的注解开发 * */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo1 { @Resource(name="orderDao") private static OrderDao orderDao; public static void main(String[] args) { orderDao.save(); orderDao.update(); orderDao.delete(); orderDao.find(); } } 复制代码
测试结果:
@Aspect public class MyAspectAnno { @Before(value="execution(* com.gx.spring.demo1.OrderDao.save(..))") public void before(){ System.out.println("前置增强==========="); } } 复制代码
后置通知可以获取方法返回值
// 后置通知: @AfterReturning(value="execution(* com.gx.spring.demo1.OrderDao.save(..))") public void afterReturning(Object result){ System.out.println("后置增强==========="+result); } 复制代码
借用一下XML方式的图,意思意思啦,意思还是那个意思QnQ
// 环绕通知: @Around(value="execution(* com.gx.spring.demo1.OrderDao.save(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("环绕前增强=========="); Object obj = joinPoint.proceed(); System.out.println("环绕后增强=========="); return obj; } 复制代码
测试前记得制造出个异常qnq
// 异常抛出通知: @AfterThrowing(value="execution(* com.gx.spring.demo1.OrderDao.save(..))" throwing="e") public void afterThrowing(Throwable e){ System.out.println("异常抛出增强========="+e.getMessage()); } 复制代码
// 最终通知 @After(value="execution(* com.gx.spring.demo1.OrderDao.save(..))") public void after(){ System.out.println("最终增强============"); } 复制代码
首先,我们发现在Spring 基于AspectJ 进行 AOP 的开发入门(注解的方式)的过程中如果方法过多,通知过多并且作用于一个方法,需求一改变就需要更改相应的源代码,为了更好的维护,于是有了AOP的切入点的配置,AOP的切入点的配置能很好地决绝改问题!只需要管理AOP的切入点的配置即可!
具体代码如下:
package com.gx.spring.demo1; 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; /** * 切面类:注解的切面类 * @author jt */ @Aspect public class MyAspectAnno { // 前置通知: @Before(value="MyAspectAnno.pointcut2()") public void before(){ System.out.println("前置增强==========="); } // 后置通知: @AfterReturning(value="MyAspectAnno.pointcut4()",returning="result") public void afterReturning(Object result){ System.out.println("后置增强==========="+result); } // 环绕通知: @Around(value="MyAspectAnno.pointcut3()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("环绕前增强=========="); Object obj = joinPoint.proceed(); System.out.println("环绕后增强=========="); return obj; } // 异常抛出通知: @AfterThrowing(value="MyAspectAnno.pointcut1()",throwing="e") public void afterThrowing(Throwable e){ System.out.println("异常抛出增强========="+e.getMessage()); } // 最终通知 @After(value="MyAspectAnno.pointcut1()") public void after(){ System.out.println("最终增强============"); } // 切入点注解: @Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.find(..))") private void pointcut1(){} @Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.save(..))") private void pointcut2(){} @Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.update(..))") private void pointcut3(){} @Pointcut(value="execution(* com.gx.spring.demo1.OrderDao.delete(..))") private void pointcut4(){} } 复制代码
欢迎各位关注我的公众号,一起探讨技术,向往技术,追求技术,说好了来了就是盆友喔...