Spring入门详细教程(三)
前言
本篇紧接着spring入门详细教程(二),建议阅读本篇前,先阅读第一篇和第二篇。链接如下:
Spring入门详细教程(一) https://www.cnblogs.com/jichi/p/10165538.html
Spring入门详细教程(二) https://www.cnblogs.com/jichi/p/10176601.html
本篇主要讲解spring的aop相关。
一、aop的概念
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
AOP主要实现功能日志记录,性能统计,安全控制,事务处理,异常处理等等。将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
AOP主要思想总结为横向重复,纵向抽取。
二、spring实现aop的原理及底层实现
spring实现aop的底层使用了两种代理机制。一种是jdk的动态代理,一种是cglib的动态代理。下面来分析一下两种代理模式。
1、jdk的动态代理
被代理对象必须要实现接口才能产生代理对象,如果被代理对象不能实现接口,则这种方式的动态代理技术无效。
接下来做一个底层代码的编写来进行理解。
(1)首先jdk的动态代理要求被代理对象必须实现接口。我们准备一个接口以及一个接口的实现类。
public interface UserDao {
public void saveUser();
}
public class UserDaoImpl implements UserDao {
public void saveUser(){
System.out.println("保存用户");
}
}
(2)建立一个UserDao的动态代理类,实现接口InvocationHandler。
public class UserProxy implements InvocationHandler{
private UserDao userDao ;
public UserProxy(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 {
System.out.println("动态代理");
return method.invoke(userDao, args);
}
}
(3)进行单元测试,发现第一个方法执行的时候没有被动态代理,第二个执行的时候进行了动态代理。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestJunit {
@Test
public void test3(){
UserDaoImpl userDaoImpl = new UserDaoImpl();
userDaoImpl.saveUser();
UserProxy userProxy = new UserProxy(userDaoImpl);
UserDao createProxy = userProxy.createProxy();
createProxy.saveUser();
}
}
2、cglib动态代理
针对一些不能实现接口的代理对象产生代理,可以对没有被final修饰的任何对象进行继承代理,其底层应用的是字节码增强的技术,生成代理对象的子类对象。如果被final修饰,类不可继承,便不可使用cglib动态代理。
(1)创建一个cglib动态代理对象实现接口。
public class CglibProxy implements MethodInterceptor{
private UserDaoImpl userDaoImpl;
public CglibProxy(UserDaoImpl userDaoImpl) {
this.userDaoImpl = userDaoImpl;
}
public UserDaoImpl createProxy(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserDaoImpl.class);
enhancer.setCallback(this);
UserDaoImpl udi = (UserDaoImpl) enhancer.create();
return udi;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object obj = methodProxy.invokeSuper(proxy, args);
System.out.println("动态代理");
return obj;
}
}
(2)进行单元测试。
public void test4(){
UserDaoImpl userDaoImpl = new UserDaoImpl();
userDaoImpl.saveUser();
CglibProxy cglib = new CglibProxy(userDaoImpl);
UserDaoImpl userDaoImpl2 = cglib.createProxy();
userDaoImpl2.saveUser();
}
可以发现第一个saveUser没有执行动态代理,第二个执行了动态代理。
结论:两种代理技术针对不同情况,互相弥补,从而使任何对象都可以实现动态代理。spring在进行aop的时候,默认使用jdk的动态代理技术,当发现jdk的动态代理技术不好使的情况下,使用cglib动态代理技术,保证被代理对象能够被正常代理。如需使用cglib动态代理可以再spring的配置文件中进行配置。
<aop:config proxy-target-class="true">
三、aop开发中的相关概念
1、Joinpoint(连接点):目标对象中,所有可以增强的方法。
2、Pointcut(切入点):目标对象中,已经增强的方法。
3、Advice(通知):对于目标对象来说,需要给目标对象增强的方法。
4、Target(目标对象):被代理对象。
5、Weaving(织入):将通知应用到切入点的过程。
6、Proxy(代理):将通知织入到目标对象后,形成的增强后的对象。
7、Aspect(切面):切入点和通知的结合。
四、spring中aop的实现方式
分两种方式介绍,一种是xml配置方式,一种是注解方式。
1、xml配置方式
(1)实现spring的aop需要导入aop包,aspect包,aopalliance包,weaver包。在spring教程一中可以找到获取这些包的方法。
(2)编写需要增加的方法类。
public class UserDaoImpl{
public void saveUser(){
System.out.println("保存用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
}
(3)编写通知,也就是说想要增加的代码方法。
public class UserAdvice{
public void before(){
System.out.println("前置通知");
}
public void afterReturning(){
System.out.println("后置通知(不发生异常的情况下调用)");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行前");
Object proceed = pjp.proceed();
System.out.println("执行后");
return proceed;
}
public void afterThrowException(){
System.out.println("发生异常调用");
}
public void after(){
System.out.println("后置通知,发生异常也会调用");
}
}
(4)在spring的配置文件中进行配置
<bean name = "userDaoImpl" class="com.jichi.aop.UserDaoImpl"></bean>
<bean name="userAdvice" class="com.jichi.aop.UserAdvice"></bean>
<aop:config>
<aop:pointcut expression="execution(* com.jichi.aop..UserDaoImpl.*(..))" id="pc"/>
<aop:aspect ref="userAdvice">
<aop:before method="before" pointcut-ref="pc"/>
<aop:after-returning method="afterReturning" pointcut-ref="pc"/>
<aop:around method="around" pointcut-ref="pc"/>
<aop:after-throwing method="afterThrowException" pointcut-ref="pc"/>
<aop:after method="after" pointcut-ref="pc"/>
</aop:aspect>
</aop:config>
(5)进行单元测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAop { @Resource
private UserDaoImpl userDaoImpl; @Test
public void test1(){
userDaoImpl.saveUser();
}
}
结果如下:织入成功。

2、注解配置方式
(1)同第一种方式需要导入包
(2)编写需要增加的方法类。
public class UserDaoImpl{
public void saveUser(){
System.out.println("保存用户");
}
public void deleteUser(){
System.out.println("删除用户");
}
}
(3)编写通知,也就是说想要增加的代码方法。
public class UserAdvice{
public void before(){
System.out.println("前置通知");
}
public void afterReturning(){
System.out.println("后置通知(不发生异常的情况下调用)");
}
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行前");
Object proceed = pjp.proceed();
System.out.println("执行后");
return proceed;
}
public void afterThrowException(){
System.out.println("发生异常调用");
}
public void after(){
System.out.println("后置通知,发生异常也会调用");
}
}
(4)在spring配置文件中进行配置,并开启注解aop
<bean name = "userDaoImpl" class="com.jichi.aop.UserDaoImpl"></bean>
<bean name="userAdvice" class="com.jichi.aop.UserAdvice"></bean>
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(5)在通知类上打上aspect的注解。在方法上打上相应注解
@Aspect
public class UserAdvice{ @Before("execution(* com.jichi.aop..UserDaoImpl.*(..))")
public void before(){
System.out.println("前置通知");
} @AfterReturning("execution(* com.jichi.aop..UserDaoImpl.*(..))")
public void afterReturning(){
System.out.println("后置通知(不发生异常的情况下调用)");
} @Around("execution(* com.jichi.aop..UserDaoImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行前");
Object proceed = pjp.proceed();
System.out.println("执行后");
return proceed;
} @AfterThrowing("execution(* com.jichi.aop..UserDaoImpl.*(..))")
public void afterThrowException(){
System.out.println("发生异常调用");
} @After("execution(* com.jichi.aop..UserDaoImpl.*(..))")
public void after(){
System.out.println("后置通知,发生异常也会调用");
}
}
优化方式:每个方法都配置方法抽取,显得比较臃肿,可以进行提取,方法如下
@Aspect
public class UserAdvice{ @Pointcut("execution(* com.jichi.aop..UserDaoImpl.*(..))")
public void adc(){} @Before("UserAdvice.adc()")
public void before(){
System.out.println("前置通知");
} @AfterReturning("UserAdvice.adc()")
public void afterReturning(){
System.out.println("后置通知(不发生异常的情况下调用)");
} @Around("UserAdvice.adc()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("执行前");
Object proceed = pjp.proceed();
System.out.println("执行后");
return proceed;
} @AfterThrowing("UserAdvice.adc()")
public void afterThrowException(){
System.out.println("发生异常调用");
} @After("UserAdvice.adc()")
public void after(){
System.out.println("后置通知,发生异常也会调用");
}
}
Spring入门详细教程(三)的更多相关文章
- spring入门详细教程(五)
前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...
- Spring入门详细教程(四)
前言 本篇紧接着spring入门详细教程(三),建议阅读本篇前,先阅读第一篇,第二篇以及第三篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/ ...
- Spring入门详细教程(二)
前言 本篇紧接着spring入门详细教程(一),建议阅读本篇前,先阅读第一篇.链接如下: Spring入门详细教程(一) https://www.cnblogs.com/jichi/p/1016553 ...
- Spring入门详细教程(一)
一.spring概述 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的 ...
- ThinkJS框架入门详细教程(二)新手入门项目
一.准备工作 参考前一篇:ThinkJS框架入门详细教程(一)开发环境 安装thinkJS命令 npm install -g think-cli 监测是否安装成功 thinkjs -v 二.创建项目 ...
- RMAN详细教程(三):备份脚本的组件和注释
RMAN详细教程(一):基本命令代码 RMAN详细教程(二):备份.检查.维护.恢复 RMAN详细教程(三):备份脚本的组件和注释 RMAN详细教程(四):备份脚本实战操作 一.基本组件: 1.Ser ...
- 经典Spring入门基础教程详解
经典Spring入门基础教程详解 https://pan.baidu.com/s/1c016cI#list/path=%2Fsharelink2319398594-201713320584085%2F ...
- Xcode和github入门详细教程
Xcode和github详细教程! 主要是参考了现在网上的一些资料给没整过的人一个详细的指南. (1)先在github上注册账号,自行解决! (2)在导航栏右上角new一个repository(仓库) ...
- Android视频录制从不入门到入门系列教程(三)————视频方向
运行Android视频录制从不入门到入门系列教程(二)————显示视频图像中的Demo后,我们应该能发现视频的方向是错误的. 由于Android中,Camera给我们的视频图片的原始方向是下图这个样子 ...
随机推荐
- springboot与ActiveMQ整合
前言 很多项目, 都不是一个系统就做完了. 而是好多个系统, 相互协作来完成功能. 那, 系统与系统之间, 不可能完全独立吧? 如: 在学校所用的管理系统中, 有学生系统, 资产系统, 宿舍系统等等. ...
- ES6躬行记(2)——扩展运算符和剩余参数
扩展运算符(Spread Operator)和剩余参数(Rest Parameter)的写法相同,都是在变量或字面量之前加三个点(...),并且只能用于包含Symbol.iterator属性的可迭代对 ...
- Java中的instanceof和isInstance基础讲解
1. instanceof 是一个操作符 使用方法: ? 1 2 if(a instanceof B){ } 表示:a 是不是 B 这种类型 2. isInstance是Class类的一个方法 ? 1 ...
- Go实用开源库收集
框架 https://github.com/go-martini/martini 图形验证码 https://github.com/dchest/captcha ORM https://github. ...
- 深入浅出zookeeper之一:功能及本质
zookeeper(下文简写为zk)大家都不陌生.但是,看到很多同学对zookeeper的理解过于程式化,有些地方甚至需要背,是大可不必的.把本质理解了,概念性和功能介绍都可以推出来的,而且架构要活学 ...
- 【动画】看动画轻松理解「Trie树」
Trie树 Trie这个名字取自“retrieval”,检索,因为Trie可以只用一个前缀便可以在一部字典中找到想要的单词. 虽然发音与「Tree」一致,但为了将这种 字典树 与 普通二叉树 以示区别 ...
- 【转载】 C#中全角转半角以及半角转全角
半角指的是一个字符占用一个标准字符的位置.全角指一个字符占用两个标准字符位置的状态.在C#中,我们可以通过程序的方法,将相应的半角字符串信息转换为全角类型,也可以实现全角转半角功能. 相应封装好的方法 ...
- WPF系列(1)WPF和XAML基础
终于下定决心开始更新WPF一个系列的文章,这里主要是出于两个目的,一是自己对所学的知识有一个系统的总结,二十希望能对其他人有些帮助,如果您觉得我写的不好,欢迎提意见. 那么既然我要开始写WPF,那我们 ...
- 在 UWP 中实现 Expander 控件
WPF 中的 Expander 控件在 Windows 10 SDK 中并不提供,本文主要说明,如何在 UWP 中创建这样一个控件.其效果如下图: 首先,分析该控件需要的一些特性,它应该至少包括如下三 ...
- JavaAndroid项目结构
使用Java的Android项目的结构 src: 源码文件夹 gen: 自动生成的源码文件夹 assets: 资产目录 bin: 编译生成的文件目录(apk等) libs: jar包 res: 资源文 ...