Java面向切面原理与实践
Java面向切面原理与实践
一. 面向切面编程是什么
首先用一句话概括:面向切面编程(AOP)就是对某些具有相似点
的代码进行增强
。
相似点可以是同一个包、使用相同的注解、public的方法、以Impl结尾的类名等等。这些相似点也叫切点
,我们可以想象一堆密密麻麻的切点在二维空间上排列,组成了一个面,这个面就叫切面
,所以切面也是一堆相似代码的集合。
我们在开发时经常因为业务变更去修改已有的代码,这样做不满足设计模式的封闭-开放
原则。修改已有代码可能有风险,也可能会让已有代码变得不好维护、逻辑变得复杂,因此我们不想去修改已有代码,同时还想要对已有代码进行功能性的增强
。AOP就是要解决这类问题出现的。
举个例子:现在有个注册用户的方法如下所示
public boolean regist(String username, String password) {
return userDao.regist(username, password);
}
假如业务变更,我们需要去对参数进行校验,于是封装了一个Assert类:
public boolean regist(String username, String password) {
Assert.notEmpty(username);
Assert.notEmpty(password);
Assert.regexMatch(username, "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$");
return userDao.regist(username, password);
}
假如以后需要加上事务、分布式锁功能都在regist方法中写的话就会导致业务逻辑复杂,实际上真正做业务逻辑的只是调用userDao的regist方法一行代码而已。
AOP有两个目的(或其中之一):
- 不修改已有代码进行功能增强
- 剔除方法中的非核心逻辑,精简代码
二. Java面向切面原理
Java实现AOP一般是有两种实现方式,一种是静态代理,一种是动态代理。
(一) 静态代理
静态代理指的是通过与某个类或接口强绑定从而去实现代理模式。根据下面的代码,可以发现继承可以天然地实现增强,类似的门面模式也是属于静态代理。
public CheckUserService extends UserService {
@Override
public boolean regist(String username, String password) {
check(username, password); // 前置增强
boolean success = super.regist(username, password);
logger.info("注册结果: " + success); // 后置增强
return success;
}
...
}
静态代理一般用来实现拦截器,通常出现在表现层框架中。
(二) 动态代理
动态代理可以不与某个类或接口强绑定,要说明动态代理首先得了解一下Java类加载相关的原理。
Java是个编译型的语言,首先会把Java代码编译成class字节码,然后JVM去加载、解释字节码,通过ClassLoader
类可以在程序运行期间动态的加载字节码生成一个类。
动态代理的原理就是在程序运行期间动态的生成一个比特数组,这个数组能够表示为目标类的子类,然后把数组交给ClassLoader
进行解析,并返回子类的实例,这个子类的实例实际上可以看做目标类的代理类。以静态代理中的代码例子来说,动态代理根据UserService
类在运行时生成了CheckUserService
,而增强的代码其实就是子类实现的regist
方法。
当前Java实现动态代理有两种方式,一种是JDK自带的动态代理,要求目标类必须实现一个接口,接口方法就是增强方法;另一种是CGLIB动态代理,要求目标类不能为final,否则不能生成子类。
简单来说,所谓代理就是利用面向对象的多态性去生成一个子类,把子类当作父类来使用,同时子类覆写了父类的方法,从而达到对父类方法进行增强或改变行为的效果。
(三) 面向切面编程与代理
假如现在要对com.baidu.waimai.service
包下的所有文件增加计时日志,AOP是怎么利用代理做的呢?
- 用户编写增强类,做计时处理
- 利用Java的反射功能扫描
com.baidu.waimai.service
包下的所有类 - 对每一个类进行动态代理生成子类,覆写父类方法,在覆写方法中回调用户的增强类
- 返回代理子类
- 用户调用子类的覆写方法时,实际上会调用增强类的方法
下面以CGLIB简单展示计时增强:
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = methodProxy.invokeSuper(o, args);
long endTime = System.currentTimeMillis();
logger.info("costTime: " + (endTime - startTime));
return result;
}
});
其中Enhancer就是增强类,当UserService的代理子类的public方法被调用时,都会走上面的intercept方法,然后由methodProxy的invokeSuper方法去真正调用父类的方法。
(四) 面向切面编程与Bean容器
只是拥有代理还不能实现不修改已有代码进行增强,我们还得在实例化父类的地方改成实例化代理子类,因此AOP经常与Bean容器结合使用。例如使用Spring框架时,我们会在XML中配置切面、增强,获取Bean的时候当作父类来处理就行,当切面、增强需要修改的时候可以只需要修改XML配置和增强类,不需要修改已有的业务代码。如下代码所示定义了切点为com.baidu.waimai.service
包下的所有public方法,增强为costTimeAdvice。
<aop:config proxy-target-class="true">
<aop:pointcut id="costTimePointCut" expression="execution(public * com.baidu.waimai.service.*(..))"/>
<aop:advisor pointcut-ref="costTimePointCut" advice-ref="costTimeAdvice"/>
</aop:config>
三. 实践
(一) 日志
此功能已展示,不再赘述。
(二) 重试
通常在连接数据库或者调用远程服务时,可能由于各种原因会失败,因此我们想要在这些代码加上重试功能,可能还要判断哪些异常需要重试哪些不需要重试、重试次数、重试睡眠时间等等操作,这些代码都写在一个方法里就会大大增加耦合。
如下代码只通过增加一个@Retryable
实现了对RuntimeException
、Error
异常进行重试,重试间隔1秒的重试功能,这种方式使得adhocQuery
的方法体的业务代码更加清晰。
@Retryable(include = {RuntimeException.class, Error.class}, backoff = @Backoff(1000))
public void adhocQuery(String sql) {
...
}
另一种方式是在XML配置<aop:config>
标签指明重试的切面和增强,这种方式能不修改adhocQuery方法。
这两种方式的好坏就是见仁见智了,个人认为增加注解的方式要更加方便一些。
(三) 缓存
缓存也是一个绝佳的需要AOP改造的功能!想想假如当前我们用ConcurrentHashMap
,我们想要改成Redis
或者Hbase
做缓存,缓存判断代码和获取数据的代码挤在一个方法里,我们就不得不改一大段代码了。有了AOP就会十分简单。
@Cacheable(value = "sqlResultCache", cacheManager = "redisCacheManager")
public void adhocQuery(String sql) {
...
}
在cacheManager
中可以定义数据存储时间、并发数、垃圾回收策略等等,不影响adhocQuery
的核心逻辑。
(四) 参数校验
如下代码,参数校验修改到了User
bean中,regist
方法增加了一个@Valid
即可,推荐在Bean中加上校验注解。
public boolean regist(@Valid User user) {
...
}
public class User {
@NotEmpty("{\"status\": 301, \"msg\": \"用户名不能为空\"}")
@Pattern(
regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$",
message = "{\"status\": 201, \"msg\": \"用户名必须是邮箱格式\"}"
)
private String username;
@NotEmpty("{\"status\": 302, \"msg\": \"密码不能为空\"}")
private String password;
...
}
(五) 异常处理
用AOP处理异常通常是为了防止异常抛出到前端界面、统一记录异常日志。
(六) SQL映射
MyBatis框架使用AOP来处理SQL映射,把数据访问对象层的方法映射到配置文件的SQL,这样做的好处是SQL和Java文件分开容易DBA对SQL进行优化,当SQL需要变更时不需要修改代码。
(七) 事务
最普通的情况下使用事务时可能是这样的:
public void regist(String username) {
Connection connection = null;
try {
connection = connectionPool.getConnection();
// do Something
connection.commit(); // 提交
} catch (Throwable t) {
if (connection != null) {
connection.rollback(); // 回滚
}
throw t;
} finally {
if (connection != null) {
connection.close(); // 归还连接池
}
}
}
事务通常从进入业务逻辑层开始,退出业务逻辑层结束,通过Spring XML是这么做:
<!-- 事务增强 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- 事务切面 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="txPointCut" expression="execution(public * com.baidu.waimai.service.*(..))"/>
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice"/>
</aop:config>
这样能够自动在service包中的所有public方法打开事务、提交、回滚、归还连接。当然这样一刀切的打开事务并不好,因此要注意配置好切点。
改造后的regist
方式是这样:
public void regist(String username) {
// do Something
}
(八) HTTP客户端
由于AOP通常是利用动态代理实现的,因此我们可以只定义接口,让增强去实现具体的子类。如下代码,增强代码将会根据注解去发送HTTP请求,自动处理类型转换和异常捕获,大幅度减少代码量。
@SophieClient(
value = "adhocService",
proxy = "adhocSohpieProxy",
url = "http://aaaa:8288/bbb/rest"
)
public interface AdhocService {
@RequestMapping(value = "sql", method = RequestMethod.POST)
JSONArray query(
@RequestParam("username") String username,
@RequestParam("sql") String sql,
@RequestParam("queryName") String queryName,
@RequestParam("useHive") Boolean useHive,
@RequestParam("useGPDB") Boolean useGPDB);
}
四. 总结
面向切面编程是在运行时生成代理子类覆写父类的方法去回调增强方法,结合Bean容器实现无修改或少量修改去增强已有代码,使得已有代码内容紧凑,降低代码耦合。十分推荐大家去使用!
Java面向切面原理与实践的更多相关文章
- Java 面向切面 AOP
参考: :http://www.blogjava.net/supercrsky/articles/174368.html AOP: Aspect Oriented Programming 即面向切面编 ...
- Java 面向切面编程(Aspect Oriented Programming,AOP)
本文内容 实例 引入 原始方法 装饰者模式 JDK 动态代理和 cglib 代理 直接使用 AOP 框架--AspectWerkz 最近跳槽了,新公司使用了 AOP 相关的技术,于是查点资料,复习一下 ...
- java面向切面编程总结-面向切面的本质
面向切面的本质:定义切面类并将切面类的功能织入到目标类中: 实现方式:将切面应用到目标对象从而创建一个新的代理对象的过程.替换: 使用注解@Aspect来定义一个切面,在切面中定义切入点(@Point ...
- 深入理解JVM虚拟机11:Java内存异常原理与实践
本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...
- Java实战之03Spring-03Spring的核心之AOP(Aspect Oriented Programming 面向切面编程)
三.Spring的核心之AOP(Aspect Oriented Programming 面向切面编程) 1.AOP概念及原理 1.1.什么是AOP OOP:Object Oriented Progra ...
- Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法
Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...
- Method Swizzling和AOP(面向切面编程)实践
Method Swizzling和AOP(面向切面编程)实践 参考: http://www.cocoachina.com/ios/20150120/10959.html 上一篇介绍了 Objectiv ...
- Java基于自定义注解的面向切面的实现
目的:实现在任何想要切的地方添加一个注解就能实现面向切面编程 自定义注解类 @Target({ElementType.PARAMETER, ElementType.METHOD}) @Retentio ...
- C# 中使用面向切面编程(AOP)中实践代码整洁
1. 前言 最近在看<架构整洁之道>一书,书中反复提到了面向对象编程的 SOLID 原则(在作者的前一本书<代码整洁之道>也是被大力阐释),而面向切面编程(Aop)作为面向对象 ...
随机推荐
- mybatis批量插入、批量更新和批量删除
转载 https://www.jianshu.com/p/041bec8ae6d3
- rman备份简介
登陆rman: [oracle@oracle ~]$ rman target / connected to target database: FSDB (DBID=1179347208) 执行全备: ...
- php 扩展 suhosin 配置不当引发的报错及其解决方法
1. /var/log/messages 频繁报错: Jul :: localhost suhosin[]: ALERT - script tried to increase memory_limit ...
- php7 memcache和memcached.so扩展
php7安装memcache和memcached扩展 https://github.com/websupport-sk/pecl-memcache https://github.com/php-mem ...
- 紫书 习题11-11 UVa 1644 (并查集)
这道题感觉思路非常巧妙, 我是看了别人的博客才想明白的. 这里用到了并查集, 以根节点为中心城市, 然后把边从大到小排序, 每次的当前的边即为容量, 因为是目前的最小值, 然后去算总的容量, 每次选容 ...
- 2019年北航OO第三单元(JML规格任务)总结
一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...
- [置顶]
Netty学习总结(1)——Netty入门介绍
1.Netty是什么? Netty是一个基于JAVA NIO类库的异步通信框架,它的架构特点是:异步非阻塞.基于事件驱动.高性能.高可靠性和高可定制性. 2.使用Netty能够做什么? 开发异步.非阻 ...
- List Slider
http://www.jssor.com/download-jssor-slider-development-kit.html
- hihocoder 1124 : 好矩阵 dp
好矩阵 时间限制:3000ms 单点时限:1000ms 内存限制:256MB 描写叙述 给定n, m.一个n × m矩阵是好矩阵当且仅当它的每一个位置都是非负整数,且每行每列的和 ≤ 2.求好矩阵的个 ...
- Android设置头像,手机拍照或从本地相冊选取图片作为头像
[Android设置头像,手机拍照或从本地相冊选取图片作为头像] 像微信.QQ.微博等社交类的APP,通常都有设置头像的功能,设置头像通常有两种方式: 1,让用户通过选择本地相冊之类的图片库中已 ...