代理模式——结合SpringAOP讲解

2,514 阅读10分钟

前言

  笔者最近想学学Spring源码,各种设计模式在Spring的源码中运用得淋漓尽致,笔者也不得不感叹原来广大的开发者一直站在牛人的肩膀上进行编程。谈到Spring,面试问得最多的就是Spring的两大核心,IOC以及AOP。IOC本质上来说就是对bean反射以及依赖注入,管理bean生命周期的容器。而AOP本质上就是动态代理。今天笔者就来讲讲动态代理。接下来我将从下面几个方面阐述动态代理:

  • 静态代理
  • JDK动态代理
  • CGlib动态代理
  • 谈谈笔者自己在实际项目使用AOP遇到的坑

静态代理

  静态代理很简单,咱们自己在写代码的时候都会写到这种类似静态代理的代码。简单来说,就是把被代理类作为参数传给代理类的构造方法,让代理类替被代理类实现更强大的功能。

 1package com.bingo.designPatterns.proxy;
2
3/**
4 * Description:静态代理
5 * User: bingo
6 */

7
8public class StaticProxyTest {
9
10    public static void main(String[] args) {
11
12        UserService userService = new UserService();
13
14        LogProxy logProxy = new LogProxy(userService);
15        logProxy.addUser();
16        logProxy.deleteUser();
17    }
18}
19
20interface IUserService{
21    void addUser();
22    void deleteUser();
23}
24
25
26class UserService implements IUserService{
27
28    @Override
29    public void addUser() {
30        System.out.println("添加用户");
31    }
32
33    @Override
34    public void deleteUser() {
35        System.out.println("删除用户");
36    }
37}
38
39//日志代理
40class LogProxy implements IUserService{
41
42    //目标类
43    private UserService target;
44
45    public LogProxy(UserService target){
46        this.target = target;
47    }
48
49    @Override
50    public void addUser() {
51        System.out.println("记录日志开始");
52        target.addUser();
53        System.out.println("记录日志结束");
54    }
55
56    @Override
57    public void deleteUser() {
58        System.out.println("记录日志开始");
59        target.deleteUser();
60        System.out.println("记录日志结束");
61    }
62}

  虽然静态代理实现比较简单,但是在实际项目中我们需要为每个类都写一个代理类,需要写很多重复冗余的代码,不利于代码的解耦与扩展。但是动态代理便很好的解决了上述问题,真真正正地实现了业务逻辑代码与增强功能代码的解耦。

动态代理

  在Spring源码中,用到的动态代理主要有两种,JDK动态代理以及CGLib动态代理。两者主要区别是:
  • JDK动态代理一般针对实现了接口的类生成代理。(下面讲AOP遇到的坑时更能理解这句话的含义)
  • 目标对象没有实现接口,则默认会采用CGLIB代理。如果目标对象实现了接口,可以强制使用CGLIB实现代理(添加CGLIB库)
    其实,上面的区别阐述虽然不够完全,但足以区分二者。
相同点:
  • 两种动态代理本质上都是:字节码组装
AOP动态代理的应用场景:
  • 日志
  • 事务
  • 权限
  • 缓存
  • 懒加载

JDK动态代理

JDK动态代理的代理类一般需要实现接口

 1package com.bingo.designPatterns.proxy;
2
3import java.lang.reflect.InvocationHandler;
4import java.lang.reflect.Method;
5import java.lang.reflect.Proxy;
6
7/**
8 * Description: jdk动态代理
9 * User: bingo
10 */

11public class JdkProxyTest {
12
13    public static void main(String[] args) {
14        IPersonService personService = JdkDynamicProxy.getProxy();
15        personService.addPerson();
16        personService.deletePerson();
17    }
18}
19
20interface IPersonService{
21    void addPerson();
22    void deletePerson();
23}
24
25class PersonService implements IPersonService{
26    @Override
27    public void addPerson() {
28        System.out.println("添加人物");
29    }
30
31    @Override
32    public void deletePerson() {
33        System.out.println("删除人物");
34    }
35}
36
37
38/**
39 * newProxyInstance方法参数说明:
40 *      ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
41 *      Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
42 *      InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
43 */

44class JdkDynamicProxy{
45
46    public static IPersonService getProxy(){
47
48        IPersonService personService = new PersonService();
49
50        IPersonService proxy = (IPersonService) Proxy.newProxyInstance(IPersonService.class.getClassLoader(), new Class<?>[]{IPersonService.class}, new InvocationHandler() {
51
52            @Override
53            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
54                System.out.println("记录日志开始");
55                Object obj = method.invoke(personService, args);
56                System.out.println("记录日志结束");
57                return obj;
58            }
59        });
60
61        return proxy;
62    }
63}

CGLib动态代理

  需要导入cglib.jar,asm.jar包才能使用此代理

 1package com.bingo.designPatterns.proxy;
2
3import net.sf.cglib.proxy.Enhancer;
4import net.sf.cglib.proxy.MethodInterceptor;
5import net.sf.cglib.proxy.MethodProxy;
6
7import java.lang.reflect.Method;
8
9/**
10 * Description: cglib动态代理
11 * User: bingo
12 */

13
14public class CglibProxyTest {
15    public static void main(String[] args) {
16
17        CglibProxy proxy = new CglibProxy();
18        Train t = (Train)proxy.getProxy(Train.class);
19        t.move();
20    }
21}
22
23class Train {
24
25    public void move(){
26        System.out.println("火车行驶中...");
27    }
28}
29
30class CglibProxy implements MethodInterceptor {
31
32    private Enhancer enhancer = new Enhancer();
33
34    public Object getProxy(Class clazz){
35        //设置创建子类的类
36        enhancer.setSuperclass(clazz);
37        enhancer.setCallback(this);
38
39        return enhancer.create();
40    }
41
42    /**
43     * 拦截所有目标类方法的调用
44     * obj  目标类的实例
45     * m   目标方法的反射对象
46     * args  方法的参数
47     * proxy代理类的实例
48     */

49    @Override
50    public Object intercept(Object obj, Method m, Object[] args,
51                            MethodProxy proxy)
 throws Throwable 
{
52        System.out.println("日志开始...");
53        //代理类调用父类的方法
54        proxy.invokeSuper(obj, args);
55        System.out.println("日志结束...");
56        return null;
57    }
58}

项目中使用AOP遇到的坑

  笔者之前在开发小程序服务接口时遇到的问题:
1.在Spring配置文件中配置了事务管理器,如下:

1<!--事务管理器配置,单数据源事务-->
2<tx:annotation-driven transaction-manager="transactionManager" />  
3<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4    <property name="dataSource" ref="dataSource" />
5</bean>

2.配置了事务管理器后,加入了@Transactional注解

1@Service
2@Transactional
3public class AccountService{
4    //to do something
5}

  乍一看,这个配置与使用都没什么毛病,对吧,但是项目一放到Tomcat服务上就报错,具体报什么错,笔者已经记不得了,但是笔者很记得出错的原因就是transaction注解的问题,于是没办法,翻看原来公司的项目代码,对比自己的代码,发现各种配置也不差,不知道问题出在哪,但是原来的项目启动正常,我的项目却报错。细看发现,公司原来的项目中Service都定义了一个接口作为规范,@Transactional注解都是加在接口实现类上的。于是乎,我半信半疑的为每个Service的类定义了一个接口规范,实现类加上@Transactional注解,如下:

1@Service
2@Transactional
3public class AccountService implements IAccountService {
4    //to do something
5}

配置没变,只是简单地为Service层定义了接口,并实现接口,项目就运行正常了。

问题根源出在哪呢?就在这里:
1<tx:annotation-driven transaction-manager="txManager"/>

上面配置默认启用JDK动态代理,JDK只能代理接口不能代理类。而我的项目中用的是这个配置,却因为没有定义Service接口导致项目启动报错。

如果需要使用CGLIB动态代理:配置如下:

1<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/> 

  不知道有没有读者遇到过跟我同样的坑,如果不了解动态代理,就有可能出现和我相同的问题。但是写程序规范很重要,MVC三层结构中,除了Controller层,Service、Dao层都需要定义接口,这是企业开发的规范。那段时间还是个刚毕业不久的实习生,难免有时候会写出这种不规范的代码,但是希望以后能够越来越规范,多看看《阿里巴巴Java开发手册》,多看看源码,看看大牛们写的代码,相信不久之后自己也可以写出如此优雅的代码,让编程变成一种艺术。

  我是广州的java程序员小彬,刚毕业半年,一直致力于java的学习,对我的文章感兴趣可以关注我的微信公众号(J2彬彬),感谢支持!