本文主要讲解内容如下:

  1. Spring的核心之一 - AOP思想

(1) 代理模式- 动态代理

① JDK的动态代理 (Java官方)

② CGLIB 第三方代理

AOP概述

什么是AOP(面向切面编程)

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式运行时动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,进而提高开发效率。

传统Web开发模型: 纵向的编程

面向切面编程: 纵横配合的编程.

AOP的作用及优势

作用:在程序运行期间,不修改任何相关源码对已有方法进行增强。

优势:减少重复代码、提高开发效率、方便后期维护。

AOP的实现方式

使用动态代理模式来实现

可能通过上面的介绍,我们还是没有对AOP有一个清晰的认识。没关系,我们看看下面的具体应用。

案例中问题

通过下面的代码,我们能看出什么问题吗?

模拟事务管理器

public class TransactionMangerHandler {

    public void begin() {
System.out.println("开启事务");
} public void commit() {
System.out.println("提交事务");
} public void rollback() {
System.out.println("回滚事务");
} public void closeSession() {
System.out.println("关闭session");
}
}

Service层代码

public class UserServiceImpl implements UserService {
// 引入事务管理器
private TransactionManagerHandler txManager = new TransactionManagerHandler(); @Override
public void insert() {
try {
txManager.begin();
System.out.println("调用dao层insert方法");
txManager.commit();
} catch (Exception e) {
txManager.rollback();
} finally {
txManager.close();
}
} @Override
public void update() {
try {
txManager.begin();
System.out.println("调用dao层update方法");
txManager.commit();
} catch (Exception e) {
txManager.rollback();
} finally {
txManager.close();
}
}
}

存在的问题

上面代码的问题就是我们的事务控制的代码是重复性的。这还只是一个业务类,如果有多个业务类,每个业务类中都会有这些重复性的代码。是不是重复代码太多了?

通过传统方式来控制业务层的事务,那么会出现大量的冗余代码,例如开启事务、提交事务、事务回滚、关闭资源。这些冗余的代码,降低了开发效率,并且不利于代码后期的维护。我们可以借助AOP思想来集中管理冗余的代码。AOP可以理解为在业务方法的执行前后植入一些增强(事务开始、事务提交、事务回滚、资源关闭),到这里,AOP总的来说就是解决方法内部代码冗余的问题,并且在一定程度上使得方法内部的非业务逻辑代码和业务逻辑代码解耦,提高开发效率、质量的同时方便代码的后期维护!

解决上述问题的方案

  1. JDK的动态代理 - Java官方。

  2. CGLIB动态代理 -第三方组织开源。

  3. Spring的AOP技术(底层就是JDK动态代理和CGLIB代理技术)。

什么是动态代理技术?

Java中的动态代理,就是使用者使用的不是真实的对象,而是使用一个代理对象,而这个代理对象中包含的是真实的对象,代理对象的作用就是不改变原有对象的功能方法的基础之上封装新的功能。可以认为是真实对象的方法在代理对象的某一个制定好规则的方法中执行。如事务管理。

动态代理技术,可以使得业务逻辑代码和非业务逻辑代码相分离,提高了开发效率,控制事务的代码都已经在代理对象的方法中搭建好了,当需要调用真实对象的方法时,那么就会调用代理对象中已经搭建好框架的方法,只需将真实对象的方法植入到框架的指定位置,即可完成真实对象方法的访问。

JDK动态代理

JDK动态代理是Java官方的动态代理技术。

使用JDK官方的Proxy类创建代理对象:

  1. 需要通过Proxy类创建代理对象。

  2. 创建代理对象必须要有一个代理处理类(实现接口InvocationHandler的类)。

JDK动态代理API分析

1、java.lang.reflect.Proxy 类:

Java动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

主要方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler hanlder)

方法职责:为指定类加载器、一组接口及调用处理器生成动态代理类实例

参数:

  • loader: 类加载器
  • interfaces : 模拟的接口
  • hanlder: 代理执行处理器

返回:动态生成的代理对象

2、java.lang.reflect.InvocationHandler接口:

public Object invoke(Object proxy, Method method, Object[] args)

方法职责:负责集中处理动态代理类上的所有方法调用

参数:

  • proxy :生成的代理对象
  • method :当前调用的真实方法对象
  • args :当前调用方法的实参

返回: 真实方法的返回结果

3、JDK动态代理操作步骤:

① 实现InvocationHandler接口,创建增强代码处理器。

② 给Proxy类提供ClassLoader对象和代理接口类型数组,创建动态代理对象。

③ 在处理器中实现增强操作。

案例代码

在我们的UserServiceImpl实现类中,使用JDK动态代理,进行方法的增强,增强事物的功能。

--UserService&UserServiceImpl

public interface UserService {

    void insert();

    void update();
}
public class UserServiceImpl implements UserService { @Override
public void insert() {
System.out.println("执行了service层的insert方法");
} @Override
public void update() {
System.out.println("执行了service层的update方法");
}
}

--JDK动态代理工具类

/**
* 获取代理对象的工具类
* 增强功能:事务管理
*/
@Component
public class DynamicProxyUtil { // 模拟的事务管理器
@Autowired
private TransactionMangerHandler tx; /**
* 根据类的字节码获取代理对象
* @param cls 被代理类的字节码
* @param <T> 被代理类的类型
* @return 代理对象
*/
public <T> T getProxyObject(Class<T> cls) {
/*
Proxy类是JDK动态代理类
静态方法newProxyInstance用于创建类的代理对象dd
*/
Object proxyObj = Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; // 代理方法执行结果
try {
tx.begin(); // 开启事务
result = method.invoke(cls.newInstance(), args); // 执行被代理方法
tx.commit(); // 提交事务
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); // 回滚事务
} finally{
tx.closeSession(); // 关闭资源
}
return result; // 返回被代理方法的执行结果
}
});
return (T) proxyObj; // 返回代理对象
}
}

--配置类

@Configuration
public class SpringConfig { @Bean(name = "tx")
public TransactionMangerHandler getTransactionMangerHandler() {
return new TransactionMangerHandler();
} @Bean(name = "dynamicProxyUtil")
public DynamicProxyUtil getDynamicProxyUtil() {
DynamicProxyUtil proxyUtil = new DynamicProxyUtil();
return proxyUtil;
}
}

--测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DynamicProxyTest { @Resource(name = "dynamicProxyUtil")
private DynamicProxyUtil proxyUtil; @Test
public void testInsert() {
// 通过ProxyUtil工具类获取某个类的代理对象
UserService userService = proxyUtil.getProxyObject(UserServiceImpl.class);
userService.insert();
} @Test
public void testUpdate() {
UserService userService = proxyUtil.getProxyObject(UserServiceImpl.class);
userService.update();
}

--测试结果

JDK动态代理的不足

  1. JDK动态代理的对象必须要实现接口,因为JDK动态代理是基于接口代理的。

  2. 需要为每个对象创建代理对象。

  3. 代理粒度大,动态代理的最小单位是类(所有类中的方法都会被处理),但是查询方法不需要事务,因此不需要被代理。

更详细内容参考https://www.cnblogs.com/satire/p/14620634.html

CGLIB动态代理(第三方代理)

CGLIB(Code Generation Library)是一个开源项目。

CGLIB和JDK动态代理一样都是动态代理,但是CGLIB可以代理没有实现接口的类。

CGLIB代理没有实现接口的类时,程序在JVM运行过程中动态的为这个类创建一个子类,并重写父类方法,在调用父类方法时,在方法执行之前、之后、异常、最终做增强。

Spring默认已经集成CGLIB代理,直接可以使用即可,不用拷贝任何jar包。

案例代码

在我们的UserServiceImpl的实现类中,使用CGLIB的动态代理,进行方法的增强,增强事物的功能。

--UserServiceImpl2

public class UserServiceImpl2 {

    public void insert() {
System.out.println("执行了service层的insert方法");
} public void update() {
System.out.println("执行了service层的update方法");
}
}

--CGLIB代理工具类

@Component("cglibProxyUtil")
public class CglibProxyUtil { @Autowired
@Qualifier("tx")
private TransactionMangerHandler tx; /**
* 返回一个代理对象,代理对象就做了方法的增强,(事物管理,日志控制,权限管理等等)
* @param cls 被代理类的字节码
* @return
*/
public <T> T getProxyObject(Class<T> cls) { Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(cls.getClassLoader());
// 设置被代理类的字节码
enhancer.setSuperclass(cls);
// 设置功能增强
/**
* 代理类的增强方法,正常增强的地方
* @param proxy 代理对象
* @param method 被代理对象的方法
* @param args 被代理对象方法的参数
* @return 被代理对象执行的结果
* @throws Throwable
*/
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
Object result = null;
try {
tx.begin();
result = method.invoke(cls.newInstance(), args);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
tx.closeSession();
}
return result;
}
});
// 创建代理对象
Object proxyObj = enhancer.create();
return (T) proxyObj;
}
}

--配置类

@Configuration
public class SpringConfig { @Bean(name = "tx")
public TransactionMangerHandler getTransactionMangerHandler() {
return new TransactionMangerHandler();
} @Bean(name = "cglibProxyUtil")
public CglibProxyUtil getDynamicProxyUtil() {
CglibProxyUtil proxyUtil = new CglibProxyUtil();
return proxyUtil;
}
}

--测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class CglibProxyTest { @Resource(name = "cglibProxyUtil")
private CglibProxyUtil proxyUtil; @Test
public void testInsert() {
// 通过ProxyUtil工具类获取某个类的代理对象
UserServiceImpl2 proxyObj = proxyUtil.getProxyObject(UserServiceImpl2.class);
proxyObj.insert();
} @Test
public void testUpdate() {
UserServiceImpl2 proxyObj = proxyUtil.getProxyObject(UserServiceImpl2.class);
proxyObj.update();
}
}

--测试结果

CGLIB代理的不足

  1. CGLIB可以标类的子类,并重写父类非final修饰符的方法。

  2. 要求类不能是final的要代理的方法要是非final、非static、非private的

  3. 动态代理的最小单位是类(所有类中的方法都会被处理)。

小结

解决代码重复的方案

在Spring中:

  • 若目标对象实现了若干接口,Spring就会使用JDK动态代理。

  • 若目标对象没有实现任何接口,Spring就使用CGLIB动态代理。

直接使用代理的缺陷

如果直接使用动态代理技术(JDK代理、CGLIB代理)解决代码重复问题,我们会发现,我们每一个类都要配置代理类,非常的麻烦。

动态代理模式的缺陷是什么

动态代理模式的缺陷是:

  1. 实现类必须要实现接口 -JDK动态代理

  2. 无法通过规则制定拦截无需功能增强的方法

如何解决这个问题?

使用Spring提供了AOP的实现。

【Spring Framework】Spring入门教程(五)AOP思想和动态代理的更多相关文章

  1. 转账示例(四):service层面实现(线程管理Connection,AOP思想,动态代理)(本例采用QueryRunner来执行sql语句,数据源为C3P0)

    用了AOP(面向切面编程),实现动态代理,service层面隐藏了开启事务.1.自行创建C3P0Uti,account数据库,导入Jar包 2.Dao层面 接口: package com.learni ...

  2. spring框架学习(四)AOP思想

    什么是AOP 为什么需要AOP 从Spring的角度看,AOP最大的用途就在于提供了事务管理的能力.事务管理就是一个关注点,你的正事就是去访问数据库,而你不想管事务(太烦),所以,Spring在你访问 ...

  3. 无废话ExtJs 入门教程五[文本框:TextField]

    无废话ExtJs 入门教程五[文本框:TextField] extjs技术交流,欢迎加群(201926085) 继上一节内容,我们在表单里加了个两个文本框.如下所示代码区的第42行位置,items: ...

  4. Spring AOP中的动态代理

    0  前言 1  动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2  Spring AOP中的动态代理机制 2.1  ...

  5. 转:Spring AOP中的动态代理

    原文链接:Spring AOP中的动态代理 0  前言 1  动态代理 1.1 JDK动态代理 1.2 CGLIB动态代理 1.2.1 CGLIB的代理用法 1.2.2 CGLIB的过滤功能 2  S ...

  6. spring---aop(4)---Spring AOP的CGLIB动态代理

    写在前面 前面介绍了Spring AOP的JDK动态代理的过程,这一篇文章就要介绍下Spring AOP的Cglib代理过程. CGLib全称为Code Generation Library,是一个强 ...

  7. Spring Boot实践——Spring AOP实现之动态代理

    Spring AOP 介绍 AOP的介绍可以查看 Spring Boot实践——AOP实现 与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改 ...

  8. PySide——Python图形化界面入门教程(五)

    PySide——Python图形化界面入门教程(五) ——QListWidget 翻译自:http://pythoncentral.io/pyside-pyqt-tutorial-the-qlistw ...

  9. Elasticsearch入门教程(五):Elasticsearch查询(一)

    原文:Elasticsearch入门教程(五):Elasticsearch查询(一) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:h ...

随机推荐

  1. 【Jenkins】jenkins构建python项目提示:'python' 不是内部或外部命令,也不是可运行的程序或批处理文件

    一.问题:jenkins构建python项目提示:'python' 不是内部或外部命令,也不是可运行的程序或批处理文件 二.原因:要在jenkins配置本地环境变量 三.解决方案:添加python.e ...

  2. JSON数据和Java对象的相互转换

    JSON解析器: 常见的解析器: Jsonlib, Gson, fastjson, jackson 其中应用最广泛的是jackson,阿里的fastjson虽然比jackson快一点,但存在的问题比较 ...

  3. 学信网改绑手机号码,但是忘记了老号码怎么办?利用node.js + puppeteer 跑脚本实现改绑手机号

    最近登录学信网发现自己学信网上绑定的手机号码不是目前自己使用的手机号码,于是想改绑手机号,但是发现不记得之前的手机号码了: 于是百度各种方法都无济于事:也不想重新注册账号,最后看见一篇文章通过Pyth ...

  4. 快速排序平均时间复杂度O(nlogn)的推导

    快速排序作为随机算法的一种,不能通过常规方法来计算时间复杂度 wiki上有三种快排平均时间复杂度的分析,本文记录了一种推导方法. 先放快速排序的伪代码,便于回顾.参考 quicksort(int L, ...

  5. Python 爬取 房天下

    ... import requests from requests import ConnectionError from bs4 import BeautifulSoup import pymong ...

  6. Springboot 加载配置文件源码分析

    Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...

  7. BootStrap中模态框踩坑

    在模态框中使用html标签上的自定义属性来打开模态框后,在使用JS关闭模态框,就会出现多层蒙板问题 出现这个问题的原因就是没有仔细看bootstrap的官方文档,我人麻了,搞了好久 务必将模态框的 H ...

  8. 【Microsoft Azure 的1024种玩法】五、基于Azure Cloud Shell 一站式创建Linux VM

    [文章简介] Azure Cloud Shell 是一个用于管理 Azure 资源的.可通过浏览器访问的交互式经验证 shell. 它使用户能够灵活选择最适合自己工作方式的 shell 体验,无论是 ...

  9. [luogu7600]封闭道路

    对于确定的$K$,问题也可以看作每一个点最多选$K$条出边,并最大化选择的边权和 关于这个问题,有如下的树形dp-- 令$f_{k,0/1}$表示以$k$为根的子树中,根节点选择不超过$K/K-1$个 ...

  10. Java遍历map的五种方式

    使用For-Each迭代entries 这是最常见的方法,并在大多数情况下更可取的.当你在循环中需要使用Map的键和值时,就可以使用这个方法 Map<Integer, Integer> m ...