Proxy实现AOP切面编程案例
作者:三朵耳朵 发布时间:2023-07-23 06:44:52
通过JDK的Proxy代理实现对业务类做简单的AOP实现
接口:UserService 包含的方法为切入点,会被代理拦截
类:UserServiceImpl 实现UserService接口
类:UserServiceFactory 工厂模式生成 *
类:MyAspect 切面类,实现对切入点的操作
UserService
public interface UserService {
//切面: 需要被拦截的方法
public void addUser();
public void updateUser();
public int deleteUser(int id);
}
UserServiceImpl
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("UserServiceImpl.add()");
}
public void add(User user) {
System.out.println("UserServiceImpl.add(" + user + ")");
}
//下面继承自UserService接口的方法会被拦截
@Override
public void addUser() {
System.out.println("UserServiceImpl.addUser()");
}
@Override
public void updateUser() {
System.out.println("UserServiceImpl.updateUser()");
}
@Override
public int deleteUser(int id) {
System.out.println("UserServiceImpl.deleteUser(" + id + ")");
return 1;
}
}
UserServiceFactory
public class UserServiceFactory {
public static UserService createUserService() {
//1、创建目标对象target
final UserService userService = new UserServiceImpl();
//2、声明切面类对象
final MyAspect myAspect = new MyAspect();
//3、将切面类before()与after()方法应用到目标类
//3.1、创建JDK代理(返回一个接口)
/*
newProxyInstance(
ClassLoader loader, //类加载器,写当前类
Class<?>[] interfaces, //接口,接口中包含的方法执行时会被拦截
InvocationHandler h) //处理 调用切面类中的处理如:deforre()、after()
*/
UserService serviceProxy = (UserService) Proxy.newProxyInstance(
UserServiceFactory.class.getClassLoader(),
userService.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//开启事务
myAspect.before();
//返回值是调用的业务方法的返回值
Object obj = method.invoke(userService, args);
//提交事务
myAspect.after();
return obj;
}
});
return serviceProxy;
}
}
MyAspect :(就是一些具体操作,如记录日志等)
public class MyAspect {
public void before() {
System.out.println("MyAspect.before()开启事务...");
}
public void after() {
System.out.println("MyAspect.after()提交事务...");
}
}
单元测试:
@Test
public void aop_test() {
UserService userService = UserServiceFactory.createUserService();
userService.addUser();
userService.deleteUser(10);
userService.updateUser();
}
输出:
MyAspect.before()开启事务...
UserServiceImpl.addUser()
MyAspect.after()提交事务...
MyAspect.before()开启事务...
UserServiceImpl.deleteUser(10)
MyAspect.after()提交事务...
MyAspect.before()开启事务...
UserServiceImpl.updateUser()
MyAspect.after()提交事务...
补充知识:结合 * 技术学习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();
}
}
来源:https://blog.csdn.net/qq_37499840/article/details/90612012
猜你喜欢
- Spring Boot1.为什么要使用 Spring Boot因为Spring, SpringMVC 需要使用的大量的配置文件 (xml文件
- 构造方法以及参数:PageView可用于Widget的整屏滑动切换,如当代常用的短视频APP中的上下滑动切换的功能,也可用于横向页面的切换,
- NDK部分1、下载ndk这里就一笔带过了。2、解压ndk不要解压,文件权限会出错。执行之,会自动解压,然后mv到想放的地方。我放到了”/us
- 功能:解决web站点的登录,权限验证,授权等功能优点:在不影响站点业务代码,可以权限的授权与验证横切到业务中1、要添加的依赖<!--t
- 前言 短时间提升自己最快的手段就是背面试题,最近总结了Java常用的面试题,分享给大家,希望大家都能圆梦大厂,加油,我命由我不由天
- 概述:Flutter中常用的滑动布局 ScrollView 有 SingleChildScrollView、NestedScrollView
- 本文实例为大家分享了C语言实现两个矩阵相乘的具体代码,供大家参考,具体内容如下程序功能:实现两个矩阵相乘的C语言程序,并将其输出代码如下:#
- WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对
- 1.雪崩效应 雪崩效应如上图所示,假设我们有3个微服务A,B,C,A调用B,B调用C,如果C挂掉了,由于B是同步调用,不断等待,导致资源耗尽
- java数据结构的堆什么是堆堆指的是使用数组保存完全二叉树结构,以层次遍历的方式放入数组中。如图:注意:堆方式适合于完全二叉树,对于非完全二
- jdk中自带了很多工具可以用于性能分析,位于jdk的bin目录下,jvisualvm工具可以以图形化的方式更加直观的监控本地以及远程的jav
- 最近在搭建springmvc的框架,遇到的这样的问题:在地址栏访问登陆界面访问不了,http://localhost/XXXX/WEB-IN
- 前言HTML5 WebSocket实现了服务器与浏览器的双向通讯,双向通讯使服务器消息推送开发更加简单,最常见的就是即时通讯和对信息实时性要
- 概述从今天开始, 小白我将带大家开启 Jave 数据结构 & 算法的新篇章.循环队列循环队列 (Circular Queue) 是一
- 线程安全当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类
- java 实现MD5加密算法的简单实例实现代码:import java.security.NoSuchAlgorithmException;
- 实现方案:我们直接参考实例代码:private String pattern = "((http|ftp
- package com.jjinfo.common.util; import java.util.Arrays; import java.u
- 该项目主要实现mybatisplus、多数据源、lombok、druid的集成主要参考 https://mp.baomidou.com/gu
- 一、先看下项目结构CodeGenerator:生成器主类resources下的mapper.java.vm:一个模板类,用以在生成dao层时