• Spring AOP 编程原理和实现


    在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
    式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

     正如上图所示,UserDao和BookDao都需要开启和提交事务,而它们是必须的(通用的)且又和核心业务不相关,如何提高代码的复用性和开发效率,我们来看下图:

    我们将与业务代码不相关(或某功能的代码)从类中剥离出来,把它们定义在另一个类中,在使用是可以利用AOP对需要增强的代码进行织入,这个就是AOP的目的。

    AOP的实现是基于代理实现的,代理又有静态代理和动态代理,静态代理不能适应变化,动态代理利用反射机制可以适应变化。

    Spring默认采取的动态代理机制实现AOP,当动态代理不可用时(代理类无接口)会使用CGlib机制。

    Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
     

    静态代理:

    静态代理根据OCP原则是在代理对象和目标对象实现共同的接口,并且代理对象持有目标对象的引用。

    模拟实现:DAO接口:

    1. public interface IUserDao {
    2. void list();
    3. void add();
    4. }

    DAO实现类:

    1. @Repository
    2. public class UserDao implements IUserDao {
    3. @Override
    4. public void list() {
    5. System.out.println("user.....list");
    6. }
    7. @Override
    8. public void add() {
    9. System.out.println("user....add");
    10. }
    11. }

    增强功能类:

    1. public class LogInfo {
    2. public static void logInfo(String operation){
    3. System.out.println("执行了"+operation+"操作!");
    4. }
    5. }

    静态代理类:代理DAO实现并实现增强功能(添加日志功能)

    1. @Component(value = "userProxyDao")
    2. public class UserProxyDao implements IUserDao {
    3. @Resource
    4. private IUserDao userDao;
    5. @Override
    6. public void list() {
    7. LogInfo.logInfo("list");
    8. this.userDao.list();
    9. }
    10. @Override
    11. public void add() {
    12. LogInfo.logInfo("add");
    13. this.userDao.add();
    14. }
    15. }

    测试:

    1. @RunWith(SpringJUnit4ClassRunner.class)
    2. @ContextConfiguration("/beans.xml")
    3. public class UserDaoTest {
    4. @Resource(name = "userProxyDao")
    5. private IUserDao userDao;
    6. @Test
    7. public void test(){
    8. this.userDao.add();
    9. this.userDao.list();
    10. }
    11. }

    输出如下:

    以上的静态代理的问题时,当有多个业务类时,每个类都要创建一个代理类,显然这是不切实际的。我们可以通过Java的java.lang.reflect.InvocationHandler接口实现动态代理。

    动态代理:通过代理对象来创建业务对象,在代理对象中完成对业务的增强(业务)的处理。

    基于JDK的动态代理类:

    1. package edu.song.aop.util;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. import java.lang.reflect.Proxy;
    5. /**
    6. * 动态代理类
    7. */
    8. public class ProxyLog implements InvocationHandler {
    9. private ProxyLog(){}
    10. private Object target;
    11. /**
    12. * 生成代理对象的方法
    13. * @param object(要代理的对象)
    14. * @return (目标对象---》代理对象)
    15. */
    16. public static Object getInstance(Object object){
    17. ProxyLog proxyLog= new ProxyLog();
    18. proxyLog.target=object;
    19. /**
    20. * 创建代理实例:
    21. * 参数1:代理对象的类加载器
    22. * 参数2:代理对象的接口
    23. * 参数3:实现InvocationHandler的对象
    24. * 返回的就是代理对象
    25. */
    26. Object result= Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),proxyLog);
    27. return result;
    28. }
    29. @Override
    30. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    31. //要增加(强)的功能代码
    32. LogInfo.logInfo(method.getName()+"!");
    33. //回调
    34. Object invoke = method.invoke(target, args);
    35. return invoke;
    36. }
    37. }

    DAO:

    1. @Repository
    2. public class UserDao implements IUserDao {
    3. @Override
    4. public void list() {
    5. System.out.println("user...dao..list");
    6. }
    7. @Override
    8. public void add() {
    9. System.out.println("user..dao..add");
    10. }
    11. }

    Service:

    1. @Service
    2. public class UserService implements IUserService{
    3. @Resource//(name = "proxyLogDao")
    4. private IUserDao userDao;
    5. @Override
    6. public void list() {
    7. System.out.println("user service....list");
    8. this.userDao.list();
    9. }
    10. @Override
    11. public void add() {
    12. System.out.println("user service.....add");
    13. this.userDao.add();
    14. }
    15. }

      注意以上业务类代码的注释部分

    配置文件:

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:p="http://www.springframework.org/schema/p"
    4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    5. xmlns:context="http://www.springframework.org/schema/context"
    6. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    7. xsi:schemaLocation="http://www.springframework.org/schema/beans
    8. http://www.springframework.org/schema/beans/spring-beans.xsd
    9. http://www.springframework.org/schema/context
    10. http://www.springframework.org/schema/context/spring-context.xsd
    11. http://www.springframework.org/schema/aop
    12. http://www.springframework.org/schema/aop/spring-aop.xsd
    13. http://www.springframework.org/schema/tx
    14. http://www.springframework.org/schema/tx/spring-tx.xsd">
    15. <context:annotation-config/>
    16. <context:component-scan base-package="edu.song.aop"/>
    17. <bean id="proxyLogDao" class="edu.song.aop.util.ProxyLog" factory-method="getInstance">
    18. <constructor-arg ref="userDao"/>
    19. bean>
    20. beans>

    测试:

    1. @RunWith(SpringJUnit4ClassRunner.class)
    2. @ContextConfiguration("/beans.xml")
    3. public class UserDaoTest {
    4. @Resource
    5. private IUserService userService;
    6. @Test
    7. public void test(){
    8. userService.add();
    9. userService.list();
    10. }
    11. }

    输出如下:

     

     以上输出并没有使用代理类,我们将业务代码修改如下:

    1. @Service
    2. public class UserService implements IUserService{
    3. @Resource(name = "proxyLogDao")
    4. private IUserDao userDao;

     我们还可以在代理中设置条件等来完成特有功能,修改代理类如下:

    1. @Override
    2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    3. if(method.getName().equals("list")) {
    4. //只有list方法要增加(强)的功能代码
    5. LogInfo.logInfo(method.getName() + "!");
    6. }
    7. //回调
    8. Object invoke = method.invoke(target, args);
    9. return invoke;
    10. }

    spring框架的AOP实现提供了两种方式:分别是xml的配置和annotation的配置。接下来我们自定义一个注解:

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target(ElementType.METHOD)
    3. public @interface LogAnnotation {
    4. String value() default "";
    5. }

    接口上添加自定义注解:

    1. public interface IUserDao {
    2. @LogAnnotation("#### 列表 #####")
    3. void list();
    4. void add();
    5. }

    修改代理类,添加对注解的处理:

    1. @Override
    2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    3. if(method.isAnnotationPresent(LogAnnotation.class)){
    4. LogAnnotation annotation = method.getAnnotation(LogAnnotation.class);
    5. LogInfo.logInfo(annotation.value());
    6. }
    7. //回调
    8. Object invoke = method.invoke(target, args);
    9. return invoke;
    10. }

     

  • 相关阅读:
    Java实现Excel批量导入数据库
    安全防御-用户认证综合实验
    稳压二极管的应用及注意事项
    docker 内 使用arthas
    UE4 回合游戏项目 16- 控制玩家
    (实践)一文搞定synchronized锁升级过程
    【鸿蒙软件开发】ArkTS基础组件之DataPanel(数据面板)、DatePicker(日期选择)
    力扣:34,在排序数组这种查找元素的第一个和最后一个位置
    设计模式-Decorator模式(装饰者模式)
    时间复杂度与空间复杂度
  • 原文地址:https://blog.csdn.net/tonysong111073/article/details/126691218