彪码野郎

  • 首页

  • 分类

  • 归档

Spring之aop

发表于 2019-09-21 阅读次数:

前言

本文有TODO啊。

本文并不会具体写Spring aop的api之类的,大部分为了理解Spring AOP的原理。
如果要看使用的话可以随便看一下网上随便搜的

aop简介

AOP(Aspect Oriented Programming),面向切面编程。

  • 简单来说,就是在执行某项事务前或后要执行通用的操作,有的人可能会说直接复制黏贴不就好了,那如果有许多事务都要求有通用的操作呢?(如:打印日志)这会让代码十分冗余,而且一旦要修改通用功能,每一个事务都要修改,这显然违背了我们的开闭原则 (开放扩展,关闭修改)。这也就引出了aop的思想,在讲解aop之前我们需要先了解代理。

代理模式

代理(Proxy)模式,用户不直接访问目标对象,而由其他对象间接访问。

举个例子,这就和买房一样,你并不会直接和房地产商直接沟通,而是和经销商交涉交易。

虽然我们没和目标直接接触,但事实上目标已经执行了(卖房),而经销商也从这以过程中进行了操作(办理手续等)。

下面来看一下实际的代码吧

静态代理

在web开发中,我们常常由多个Dao,现在由个UserDao的接口,如下

1
2
3
public interface UserDao {
void save();
}

UserDaoImpl实现了接口

1
2
3
4
5
6
7
class UserDaoImpl implements UserDao{

@Override
public void save() {
System.out.println("保存数据");
}
}

此时,我想在dao操作前开启事务,执行后关闭事务,假设有很多操作都要这样。如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void save() {
System.out.println("开启事务");
System.out.println("保存数据");
System.out.println("关闭事务");
}
public void delete() {
System.out.println("开启事务");
System.out.println("---删除数据---");
System.out.println("关闭事务");
}
public void update() {
System.out.println("开启事务");
System.out.println("---更新数据---");
System.out.println("关闭事务");
}
public void query() {
System.out.println("开启事务");
System.out.println("---查询数据---");
System.out.println("关闭事务");
}
//...

上面的代码是不是很恶心,全部都是重复代码。这时我们就要创建一个代理来帮助我们。我们要注意两个点

  • 代理要和userDao用一样的方法
  • 代理只是帮你增强userDao的方法,实际关键点还是UserDao

明白了上面两点我们就可以来写代理类了,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static class UserDaoProxy implements UserDao {

//保存好目标,到时代理要用
private UserDao target;
public UserDaoProxy(UserDao target) {
this.target = target;
}

@Override
public void save() {

System.out.println("开启事务");

target.save(); //目标的关键操作

System.out.println("关闭事务");
}
}

我们可以通过代理来用操作啦

1
2
3
4
5
6
7
8
public static void main(String[] args) {
//目标对象
UserDao target = new UserDaoImpl();

//创建代理
UserDao proxy = new UserDaoProxy(target);
proxy.save(); //通过代理帮我们执行target的save
}

这就是人们常说的静态代理了。

不过仔细看一下这些代码,接口一改,代理也要改,接口一多,代理也多,代理和接口的耦合度也太高了吧。

不要怕,jdk帮我们想好出路了!!就决定时你了!动态代理!

动态代理

动态代理的优点:

  • 代理类不用实现接口,减少了代理类
  • JDKAPI会动态生成代理对象(根据我们指定的目标对象,代理对象,实现接口的类型)

下面我们来看一下如何实现的吧。

JAVA的API中提供了Proxy类,用它的newInstance方法就可以获得目标的代理对象。

newInstance方法有三个参数

  1. loader,代理对象的类装载器
  2. interfaces,目标对象的接口
  3. handler,用于增强方法

由上面的三个参数可以推测出

  • 因为有目标对象的接口,所以代理对象拥有和目标对象一样的方法
  • 代理对象的方法实际都是handler执行的

目标对象

乔尼·乔斯达是JoJo家族的人,会替身和波纹。

1
2
3
4
5
6
7
8
9
10
11
12
static class Johnny implements JoJo {

@Override
public void stand() {
System.out.println("这就是我的替身,牙!!!");
}

@Override
public void bowen() {
System.out.println("欧巴哆啦A梦!!");
}
}

接口

public interface JoJo {

void stand();
void bowen();

}

代理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static class JohnnyProxy {

//维护johnny
Johnny johnny = new Johnny();

//返回代理对象
public JoJo getProxy() {
/**
* @param1 代理类的类装载器
* @param2 目标对象的接口
* @param3 handler的实现类
*/
return (JoJo) java.lang.reflect.Proxy.newProxyInstance(JohnnyProxy.class.getClassLoader(),johnny.getClass().getInterfaces(),
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

System.out.println("前置增强");
//johnny使用了方法啦,还有参数都给你准备好了
method.invoke(johnny,args);
System.out.println("后置增强");
return null;
}
}
);
}
}

测试

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
/
//动态代理

JohnnyProxy johnnyProxy = new JohnnyProxy();
//代理里面就有johnny
JoJo proxy = johnnyProxy.getProxy();
proxy.stand("牙!!1");

}

我们对比一下静态代理和动态代理:

  • 静态代理

    • 1.代理类要实现目标的接口,并实现方法,所以修改接口,代理类也要随之修改。
    • 2.代理类要预先创建
  • 动态代理

    • 1.代理类不用实现目标接口,改接口后不用改代理类(因为反射都帮你把方法和参数都自动写了)
    • 2.JDK会动态的帮你生产所需的代理对象(反射嘛)
    • 3.目标对象一定要有接口

总结一下,代理模式的作用,就是增强方法,在原方法的前后都可以添加操作。

休息一下,然后看看Spring中的aop是怎么实现的吧。

Spring中的aop

cglib代理简介

回忆一下,我们因为静态代理需要实现目标的接口,一旦修改接口,代理类也要修改。
所以我们学了动态代理,但动态代理也不是完美的,它需要目标对象拥有接口。

cglib代理又叫做子类代理,从内存中构建一个子类进目标对象进行增强。

编写cglib代理

在编写cglib代理前要注意一下几点

  1. 代理的类为final会报错,因为cglib就是构建子类进行增强的,目标为final就不能继承了。

  2. 若目标对象的方法时final,则不会对该方法进行增强。

  3. 编写cglib代理需要引入cglib包,cblib.jar已经在spring的核心包中有了,所以我们只要导入Spring-core-x.x.x.jar即可。

  • cglib包是一个高性能的代码生成包,它可以在运行时扩展类和实现接口。。
    它在许多AOP框架中使用,如Spring AOP(interception)。

  • 实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class ProxyFactory implements MethodInterceptor{

//保存目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}

public Object getProxyInstance() {

//创建工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数,MethodInterceptor就是继承了CallBack的
en.setCallback(this);
//4.创建子类
return en.create();
}

//增强
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

System.out.println("开始事务");

Object ret = method.invoke(target, args);

System.out.println("结束事务");
return ret;
}

}
  • 测试如下:
1
2
3
4
5
6
7
8
9
@Test
public void test() {

UserDao ud = new UserDao();
UserDao proxy = (UserDao) new ProxyFactory(ud).getProxyInstance();
proxy.save();
//看看是不是真的是子类吧
System.out.println(proxy.getClass().getSuperclass());
}
  • cglib扩展阅读

手撕基于代理的Spring AOP

aop面向切面编程,全称:aspect object programming 面向切面编程。

  • 功能: 让业务代码和重复的代码分离。

看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
public void save() {
System.out.println("开启事务");
System.out.println("保存数据");
System.out.println("关闭事务");
}
public void delete() {
System.out.println("开启事务");
System.out.println("---删除数据---");
System.out.println("关闭事务");
}

//还有很多和这方法很像的方法...

是不是发现和上面学代理的时候情况很像,没错,我就是照抄的233。

  • 这里我们为了方便我们把公共部分需要重复编写的代码统称为切面,把重点的业务称作切点,把切点放入切面中称作织入。
  • 厨师在做三文治的时候是要吐司(切面)和馅料(切点),客人点单后,厨师们动态地把馅料,放在吐司中间(织入)。
    注意,这里吐司和馅料都可以随便换的,比如我想吃黑麦肉松三文治(黑麦吐司+肉松),你想吃黑麦火腿三文治(黑麦吐司+火腿)。

  • Spring aop 做的事事情就和做三文治一样的,你提供一个切面,和切点,它帮你进行织入。

下面我们就来自己实现一下Spring aop 吧。

这种对方法的增强,肯定是我代理的天下啦。这里我们使用cglib

  1. 把切面的功能封装起来
1
2
3
4
5
6
7
8
9
10
public class AOP {

public void openTransaction() {
System.out.println("打开事务");
}

public void closeTransaction() {
System.out.println("关闭事务");
}
}
  1. 创建代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ProxyFactory2 implements MethodInterceptor {

//这里需要管理切面和需要增强的对象
private AOP aop;
private Object target;
public ProxyFactory2(AOP aop, Object obj){
this.aop = aop;
this.target = obj;
}

public Object getProxyInstance() {

//创建工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数,MethodInterceptor就是继承了CallBack的
en.setCallback(this);
//4.创建子类
return en.create();
}

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {

aop.openTransaction();
Object ret = method.invoke(target, args);
aop.closeTransaction();
return ret;
}
}
  1. 测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test() {
    //这里直接new了,spring的话注入就完事了
    UserDao ud = new UserDao();
    AOP aop = new AOP();
    //指定切面和切入点
    UserDao proxy = (UserDao) new ProxyFactory2(aop,ud).getProxyInstance();
    proxy.save();
    }
  • 上面我们是基于cglib代理进行实现的aop,实际上Spring AOP有两套实现方式,我们常用的是Spring AspectJ
    • 基于代理的经典SpringAOP(advisor)
      • 在这种方式下Spring AOP 默认使用的是JDK动态代理,若目标类没有接口则用cglib代理。上面的代码就是简化版的
    • 基于AspectJ的AOP(aspect)
      • Spring借鉴了AspectJ的做法,但Spring AOP本质上还是动态代理。所以Spring AOP不需要专门的编辑器

AspectJ是语言级别的AOP实现,扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有专门的编译器用来生成遵守Java字节码规范的Class文件(Spring aop是在运行时织入)

Spring AspectJ

在讲Spring AspectJ之前我们需要先补充两个专业名词:

  • 通知(Advice):表示添加到切点的代码,并携带了具体位置信息(前置、后置、返回、异常、环绕)。

  • 引入/引介(Introduction):向现有的类添加新方法或属性,是一种特殊的增强。

Spring 给我们提供了更强大的aop,它能够指定单体某个方法作为切点,并用通知(Advice)来确定织入的具体位置(如切点的前置、后置、返回等)。

基本的注解和xml随便找个博客就能抄了,很简单就不写了。

使用引入/引介实现为Bean新增方法

现在我们有Student和Teacher两个接口及其实现类如下,

现在小明想要造反了,想要改试卷,问题是小明不可能成为老师的(不能实现teacher接口),这可咋整呢。这时候就可以使用引入切面了,让代理来帮助小明“改卷”,

  • 创建切面类
1
2
3
4
5
6
7
@Aspect
public class Aspect {

@DeclareParents(value = "com.xxx.XiaoMing", // 指定学生具体的实现
defaultImpl = MissLi.class) // 老师具体的实现
public Teacher miss; // 要实现的目标接口
}

当你写完并引入这个切面的时候,spring会自动帮XiaoMing实现Teacher接口。
只要强转一下,小明也可以改试卷啦!
。。。

然而事实是这样的

  • 当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中。
    • 也可以这么理解,在代理类中,我们有两个对象。

总结与注意事项

其实AOP还有很多很多的知识点,但大致上就是上面那些了。

  • AOP的底层就是动态代理(JDK代理和cglib代理),是运行是的增强
  • 使用单例的话,最好使用CGLib代理,因为CGLib代理对象运行速度要比JDK的代理对象要快
spring之ioc
flex布局快速入门
  • 文章目录
  • 站点概览
Weapon

Weapon

40 日志
6 分类
4 标签
  1. 1. 前言
  2. 2. aop简介
  3. 3. 代理模式
    1. 3.1. 静态代理
    2. 3.2. 动态代理
      1. 3.2.1. 目标对象
      2. 3.2.2. 接口
      3. 3.2.3. 代理类
      4. 3.2.4. 测试
  4. 4. Spring中的aop
    1. 4.1. cglib代理简介
      1. 4.1.1. 编写cglib代理
    2. 4.2. 手撕基于代理的Spring AOP
    3. 4.3. Spring AspectJ
      1. 4.3.1. 使用引入/引介实现为Bean新增方法
  5. 5. 总结与注意事项
© 2019 Weapon
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Pisces v7.3.0