结合动态代理技术学习SpringAop实现切面编程

结合一个例子利用动态代理技术和SpringAop实现需求

需求:为我的UserService类中的每一个方法加上一个计时器

最初的实现是为每一个类添加一段代码,这样看起来代码的冗余度特别大

静态代理实现

在使用JDK提供动态代理之前我们先利用静态代理技术实现这个需求

静态代理需要我们自己创建代理类具体代码如下:

创建UserService接口以及他的实现类及目标类UserServiceTarget

public interface UserService {

    public void insert();
    public void update();
    public void delete();
}

// 目标类
public class UserServiceTarget implements UserService {

    @Time
    public void insert() {
        System.out.println("插入用户");
    }

    public void update() {
        System.out.println("修改用户");
    }

    public void delete() {
        System.out.println("删除用户");
    }
}

创建TimeHandler类,将重复的计时器代码逻辑写入TimeHandler类中

public class TimeHandler {
    private UserServiceTarget userService = new UserServiceTarget();
    //需要加计时器的方法对应的对象 -- method
    public void invoke(Method method) {
        long start = System.nanoTime();
        // 反射调用: 方法.invoke(对象, 参数);
        try {
            method.invoke(userService);
        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.nanoTime();
        Time time = method.getAnnotation(Time.class);
        if(time != null) {
            System.out.println("花费了: " + (end - start));
        }
    }
}

最后一步就是自己实现代理类UserServiceProxy,自己实现代理类被称作静态代理

public class UserServiceProxy implements UserService {

    public void insert() {
        try {
            TimeHandler timeHandler = new TimeHandler();
            Method a = UserServiceTarget.class.getMethod("insert");
            timeHandler.invoke(a);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public void update() {
        try {
            TimeHandler timeHandler = new TimeHandler();
            Method b = UserServiceTarget.class.getMethod("update");
            timeHandler.invoke(b);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public void delete() {
        try {
            TimeHandler timeHandler = new TimeHandler();
            Method c = UserServiceTarget.class.getMethod("delete");
            timeHandler.invoke(c);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

这样在无需改变UserService类和其实现类的情况下增加了代码的扩展性,降低了代码间的耦合度。

动态代理实现

动态代理就是不需要我们自己创建代理类和代理对象,JDK会在程序运行中为我们自动生成代理对象

动态代理的三个步骤

1、生成代理类的字节码

2、执行类加载将字节码加载进入JVM

3、创建代理类的实例对象

方式一

自己写代码生成需要代理类的字节码

1、获取代理类的字节码

byte[] bytes = ProxyGenerator.generateProxyClass("UserServiceProxy", new Class[]{UserService.class});
//这里第一个参数是自己为代理类起的类名,第二个参数是需要创建代理类的字节码数组

2、执行类加载

 ClassLoader cl = new ClassLoader() {
            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                return defineClass(name, bytes, 0, bytes.length);
            }
        };
        Class c = cl.loadClass("UserServiceProxy"); // 进行类加载, 获得了 UserServiceProxy 类对象

3、 创建代理类实例对象--通过反射

 // 获取代理类的构造方法
        Constructor constructor = c.getConstructor(InvocationHandler.class);

        UserServiceTarget target = new UserServiceTarget();
        // 创建实例对象, 强制转换为它的接口类型
        UserService proxy = (UserService)constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long start = System.nanoTime();
                method.invoke(target, args);
                long end = System.nanoTime();
                System.out.println("花费了:" + (end - start));
                return null;
            }
        });

这里的InvocationHandler接口匿名实现类似于我们之前的TimeHandler类,只需要将重复代码逻辑写入其中在通过方法对象反射调用该方法即可实现动态代理。

//使用代理对象
proxy.insert();

方式二

利用Proxy类的newProxyInstance()方法实现动态代理,具体代码如下

 public static void main(String[] args) {
        // 直接创建代理类的实例
        // 1. 获取类加载器
        ClassLoader cl = UserService.class.getClassLoader();
        // 2. 规定代理类要实现的接口
        Class[] interfaces = new Class[] {UserService.class};
        // 3. 给一个 InvocationHandler 对象, 包含要执行的重复逻辑
        UserServiceTarget target = new UserServiceTarget();
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                long start = System.nanoTime();

                // 方法.invoke(目标, 参数);
                method.invoke(target, args);

                long end = System.nanoTime();
                System.out.println("花费了:" + (end - start));
                return null;
            }
        };
        UserService proxy = (UserService) Proxy.newProxyInstance(cl, interfaces, h);
        //4. 使用代理对象
        proxy.update();
    }
}

使用Spring框架AOP(面向切面编程)完成需求

Spring框架最最主要的两大特性就是IOC(控制反转)和AOP(面向切面编程)

IOC总结见我的博客SpringIOC总结

AOP (aspect oriented  programming ) 即面向切面编程

切面 aspect = 通知 adivce + 切点 pointcut 

通知:是一个方法,其中包含了重复的逻辑(例如我们今天需要实现的计时器需求,以及Spring事务管理的底层实现)
切点:是一种匹配条件, 与条件相符合的目标方法,才会应用通知方法,需要配合切点表达式

再来类比一下之前的图

图中的UserService就是SpringAOP技术中的代理,TimeHandler就是切面,UserServiceTarget在SpringAOP技术中被称作目标。

SpringAOP实现

首先需要添加maven依赖

<!--spring核心依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.22.RELEASE</version>
</dependency>
<!--切面相关依赖-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.13</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

第二步,编写切面类

@Component
//将切面交给spring容器管理
@Aspect
//@Aspect 注解表示该类是一个切面类
//切面 = 通知 + 切点
public class UserAspect {
    //配置切点 @Around注解和切点表达式
    @Around("within(service.impl.*)")

    //配置通知方法
    //ProceedingJoinPoint参数用来调用目标方法
    public Object time(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long start = System.nanoTime();
        Object proceed = proceedingJoinPoint.proceed();//调用目标方法返回结果
        long end = System.nanoTime();
        System.out.println("springaop  方法耗时" + (end - start) + "纳秒");
        return proceed;
    }
}

UserService和UserServiceImpl代码如下

public interface UserService {
    void insert();

    void update();

    void delete();
}

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void insert() {
        System.out.println("UserServiceImpl 增加用户信息");
}

    @Override
    public void update() {
        System.out.println("UserServiceImpl 修改用户信息");
    }

    @Override
    public void delete() {
        System.out.println("UserServiceImpl 删除用户信息");
    }
}

最后一步,配置Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"
>
    <!-- spring容器进行包扫描 配有@Componet @Service @Controller @Repository会交由spring容器管理-->
    <context:component-scan base-package="service,aspect"/>

    <!-- 启用切面编程的相关注解,例如: @Aspect, @Around, 还提供了自动产生代理类的功能-->
    <aop:aspectj-autoproxy/>
</beans>

编写测试类

public class TestSpringAopProgramming {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("spring.xml");
        UserService userService = context.getBean(UserService.class);
        userService.insert();
        userService.update();
        userService.delete();
    }
}

 彩蛋

这个时候上面的需求又发生了变法,不是给UserService中所有的方法加计时器,而是给指定方法加计时器,又该如何实现?

如果我们给需要加计时器的方法加上一个注解,当反射调用该方法的时候判断如果有该注解在通过动态代理的方式为其加计时器不就可以解决问题了。

自定义注解

自定义注解需要添加两个注解 @Target  @Retention

@Target 表示能够加在哪些位置
ElementType.TYPE 表示能够加在 类上
ElementType.METHOD 表示能够加在 方法上
ElementType.FIELD 表示能够加在 属性上

@Retention 表示注解的作用范围
Source 表示注解仅在 *.java 源码中有效
Class 表示注解在 *.java 源码 和 *.class 字节码中有效
Runtime 表示注解在 *.java 源码 和 *.class 字节码 和 运行期间都中有效

自定义注解类Time

@Target({ ElementType.METHOD } ) //该只需要加载方法上
@Retention(RetentionPolicy.RUNTIME)//需要在源码,字节码,以及运行中都有效
public @interface Time {
}

这个时候只需要在指定的方法上加@Time注解,然后在代理对象进行判断即可,示例代码如下:

public void insert() {
        try {
            TimeHandler timeHandler = new TimeHandler();
            Method a = UserServiceTarget.class.getMethod("insert");
            //通过getAnnotation()方法判断是否存在@Time注解
            if(method.getAnnotation(Time.class) !=null) {
                timeHandler.invoke(a);
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值