springaop实现相关功能(事务、异常处理、记录日志)

springaop的底层实现(代理模式)

  • 原理设计

    • 代理模式:
      Spring AOP使用了代理模式,为目标对象创建一个代理对象。当调用目标对象的方法时,实际上是调用代理对象的方法。代理对象在调用目标方法前后,可以插入额外的代码(即切面的通知),从而实现AOP的功能。
    • 字节码操作:
      Spring AOP使用字节码操作技术来创建代理对象。具体地,它会为目标对象生成一个子类(使用CGLIB代理)或接口的实现类(使用JDK动态代理),并在该类中重写目标方法,以插入额外的代码。
  • JDK动态代理

    • 如果目标对象实现了至少一个接口,Spring AOP会使用JDK动态代理。JDK动态代理通过反射机制为目标接口创建一个代理类,并调用InvocationHandler接口的invoke方法来执行目标方法。
    • JDK动态代理主要基于Java的反射机制,它要求被代理的对象必须实现至少一个接口。代理类在运行时动态生成,并实现了与被代理对象相同的接口。当调用代理对象的方法时,实际上是通过反射机制调用被代理对象的相应方法。
      • JDK动态代理优点:

        • 简单易用:JDK动态代理使用Java标准库中的Proxy类和InvocationHandler接口,无需额外引入第三方库,使用相对简单。
        • 灵活性:能够动态地创建代理类,并可以灵活地添加额外的功能,如日志记录、性能监控等。
        • 通用性:由于基于接口代理,因此可以很方便地对不同的接口进行代理,实现通用的代理逻辑。
      • JDK动态代理缺点:

        • 只能代理接口:JDK动态代理要求目标对象必须实现至少一个接口,如果目标类没有实现任何接口,则无法使用JDK动态代理进行代理。

        • 性能开销:由于JDK动态代理基于反射机制实现,因此在调用代理方法时可能存在一定的性能开销。

        • 举例,假设我们有一个接口MyInterface和一个实现了该接口的类MyClass:

          public interface MyInterface {  
              void doSomething();  
          }  
            
          public class MyClass implements MyInterface {  
              @Override  
              public void doSomething() {  
                  System.out.println("Doing something in MyClass");  
              }  
          }
          
        • 我们可以使用JDK动态代理为MyClass创建一个代理对象:

          import java.lang.reflect.InvocationHandler;  
          import java.lang.reflect.Method;  
          import java.lang.reflect.Proxy;  
            
          public class MyInvocationHandler implements InvocationHandler {  
              private Object target;  
            
              public MyInvocationHandler(Object target) {  
                  this.target = target;  
              }  
            
              @Override  
              public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
                  System.out.println("Before method invocation");  
                  Object result = method.invoke(target, args);  
                  System.out.println("After method invocation");  
                  return result;  
              }  
            
              public static void main(String[] args) {  
                  MyInterface myClass = new MyClass();  
                  MyInterface proxy = (MyInterface) Proxy.newProxyInstance(  
                          MyClass.class.getClassLoader(),  
                          myClass.getClass().getInterfaces(),  
                          new MyInvocationHandler(myClass)  
                  );  
                  proxy.doSomething();  // 输出:Before method invocation, Doing something in MyClass, After method invocation  
              }  
          }
          
        • 在这个例子中,我们创建了一个实现了InvocationHandler接口的类MyInvocationHandler,并在其中添加了额外的逻辑(在方法调用前后打印消息)。然后,我们使用Proxy.newProxyInstance方法创建了一个代理对象,该对象实现了与MyClass相同的接口。当我们调用代理对象的doSomething方法时,实际上会先调用MyInvocationHandlerinvoke方法,然后再调用MyClassdoSomething方法。

  • CGLIB代理

    • 如果目标对象没有实现任何接口,Spring AOP会使用CGLIB代理。CGLIB是一个强大的高性能的代码生成库,它可以在运行时扩展Java类与实现Java接口。CGLIB通过继承目标类来创建代理类,并重写目标方法。

    • 与JDK动态代理不同,CGLIB代理是通过继承被代理类来创建代理对象的。因此,它不需要被代理对象实现任何接口。CGLIB代理在运行时动态生成被代理类的子类,并重写其中的方法。当调用代理对象的方法时,实际上是调用CGLIB生成的子类中的相应方法。

      • CGLIB代理优点:
        • 可以代理任意类:CGLIB代理通过继承目标类来创建代理对象,因此无需目标类实现接口,可以代理任意类。
        • 性能较好:CGLIB代理通过直接操作字节码生成新的类,避免了使用反射的开销,因此在性能方面通常优于JDK动态代理。
        • 提供更多控制:CGLIB代理提供了更多的控制选项,如方法拦截、方法回调等,可以实现更复杂的代理逻辑。
      • CGLIB代理缺点:
        • 无法代理final类和方法:由于CGLIB代理是通过继承目标类实现的,因此无法代理被标记为final的类和方法。

        • 使用相对复杂:CGLIB代理需要引入第三方库,并且其实现逻辑相对复杂,需要一定的编程经验和技能。

        • 性能开销:虽然CGLIB代理在性能方面通常优于JDK动态代理,但由于它涉及到字节码操作,因此在创建代理类的过程中可能会存在一定的性能开销。

        • 使用CGLIB代理,需要添加CGLIB库的依赖。然后,可以创建一个实现了MethodInterceptor接口的类来定义代理逻辑:

          import net.sf.cglib.proxy.MethodInterceptor;  
          import net.sf.cglib.proxy.MethodProxy;  
          import java.lang.reflect.Method;  
          import java.lang.reflect.Object;  
            
          public class MyMethodInterceptor implements MethodInterceptor {  
              @Override  
              public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {  
                  System.out.println("Before method invocation");  
                  Object result = proxy.invokeSuper(obj, args);  // 调用父类(即被代理类)的方法  
                  System.out.println("After method invocation");  
                  return result;  
              }  
          }
          

          接下来,我们可以使用CGLIB来创建MyClass的代理对象:

          import net.sf.cglib.proxy.Enhancer;  
            
          public class Main {  
              public static void main(String[] args) {  
                  Enhancer enhancer = new Enhancer();  
                  enhancer.setSuperclass(MyClass.class);  // 设置被代理类  
                  enhancer.setCallback(new MyMethodInterceptor());  // 设置回调(即代理逻辑)  
                  MyClass proxy = (MyClass) enhancer.create();  // 创建代理对象  
                  proxy.doSomething();  // 输出:Before method invocation, Doing something in MyClass, After method invocation  
              }  
          }
          

          在这个例子中,创建了一个实现了MethodInterceptor接口的类MyMethodInterceptor,并在其中添加了额外的逻辑。然后,我们使用CGLIB的Enhancer类来创建MyClass的代理对象,并设置回调为MyMethodInterceptor的实例。当我们调用代理对象的doSomething方法时,实际上会先调用MyMethodInterceptorintercept方法,然后再调用MyClass的方法

    1. 代理类的生成:
      Spring AOP使用AspectJ的weaver组件来生成代理类的字节码。weaver组件负责解析切面定义、切点表达式,并生成相应的代理类。
    2. 通知的执行:
      当代理对象的方法被调用时,Spring AOP会根据切点表达式匹配相应的通知。然后,按照通知的类型(前置、后置、环绕等)执行相应的代码。
    3. 事务管理:
      Spring AOP的事务管理是一个重要的应用场景。通过配置事务管理器、事务属性(如传播行为、隔离级别等),Spring AOP可以在方法执行前后自动开启和提交/回滚事务。

异常处理

  • 定义一个切面来捕获和处理方法执行过程中抛出的异常:
    1. 准备:AOP 的依赖,如果你使用 Maven,可以在 pom.xml 文件中添加以下依赖:

      <dependency>  
          <groupId>org.springframework.boot</groupId>  
          <artifactId>spring-boot-starter-aop</artifactId>  
      </dependency>
      
    2. 创建一个异常处理切面 ExceptionHandlingAspect,使用 @AfterThrowing 注解来定义在方法抛出异常后执行的通知:

      import org.aspectj.lang.JoinPoint;  
      import org.aspectj.lang.annotation.AfterThrowing;  
      import org.aspectj.lang.annotation.Aspect;  
      import org.springframework.stereotype.Component;  
        
      @Aspect  
      @Component  
      public class ExceptionHandlingAspect {  
        
          @AfterThrowing(pointcut = "execution(* com.example.myapp.service.*.*(..))", throwing = "exception")  
          public void handleException(JoinPoint joinPoint, Throwable exception) {  
              // 处理异常逻辑  
              String methodName = joinPoint.getSignature().getName();  
              String className = joinPoint.getTarget().getClass().getName();  
              System.err.println("Exception occurred in method " + methodName + " of class " + className);  
              exception.printStackTrace();  
                
              // 你可以在这里记录异常到日志、发送邮件通知、执行回滚操作等  
          }  
      }
      
    3. 如上代码@Aspect 注解标记这个类为一个切面

      • @Component 注解使得 Spring 能够自动发现并注册这个 bean。
      • @AfterThrowing 注解定义了一个异常通知,它会在匹配 pointcut 表达式的方法抛出异常后执行。
      • throwing = "exception" 指定了通知方法的参数名,用于接收抛出的异常。
    4. 在 Spring 配置中启用 AOP 支持,Spring Boot,可以通过添加 @EnableAspectJAutoProxy 注解来启用 AOP:

      import org.springframework.boot.SpringApplication;  
      import org.springframework.boot.autoconfigure.SpringBootApplication;  
      import org.springframework.context.annotation.EnableAspectJAutoProxy;  
        
      @SpringBootApplication  
      @EnableAspectJAutoProxy  
      public class MyApplication {  
          public static void main(String[] args) {  
              SpringApplication.run(MyApplication.class, args);  
          }  
      }
      
    5. 当你的应用程序中的 com.example.myapp.service 包下的任意方法抛出异常时,ExceptionHandlingAspect 切面中的 handleException 方法将被自动调用,处理异常逻辑。

    6. 注意,还可以使用 @Around 注解来在方法执行前后进行更精细的控制,包括在异常发生时执行特定的逻辑。

      import org.aspectj.lang.ProceedingJoinPoint;  
      import org.aspectj.lang.annotation.Around;  
      import org.aspectj.lang.annotation.Aspect;  
      import org.springframework.stereotype.Component;  
        
      @Aspect  
      @Component  
      public class ExceptionHandlingAspect {  
        
          @Around("execution(* com.example.myapp.service.*.*(..))")  
          public Object handleExceptions(ProceedingJoinPoint joinPoint) throws Throwable {  
              try {  
                  // 在目标方法执行之前执行一些操作(可选)  
                  System.out.println("Before method execution: " + joinPoint.getSignature());  
        
                  // 继续执行目标方法  
                  Object result = joinPoint.proceed();  
        
                  // 在目标方法执行之后执行一些操作(可选)  
                  System.out.println("After method execution: " + joinPoint.getSignature());  
        
                  return result;  
              } catch (Exception ex) {  
                  // 处理异常  
                  System.out.println("Exception occurred: " + ex.getMessage());  
                  // 这里可以记录日志、发送通知等  
        
                  // 可以选择抛出异常或者返回一个默认值、错误响应等  
                  throw new RuntimeException("An error occurred during method execution", ex);  
              }  
          }  
      }
      

springmvc拦截器实现异常处理

  • Spring MVC中使用拦截器和@ControllerAdvice与@ExceptionHandler来处理异常。

    1. 创建一个简单的拦截器来记录请求信息:
    import javax.servlet.http.HttpServletRequest;  
    import javax.servlet.http.HttpServletResponse;  
      
    import org.springframework.web.servlet.HandlerInterceptor;  
    import org.springframework.web.servlet.ModelAndView;  
      
    public class LoggingInterceptor implements HandlerInterceptor {  
      
        @Override  
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)  
                throws Exception {  
            // 在请求处理之前记录日志  
            System.out.println("Pre-Handle: " + request.getRequestURI());  
            return true; // 继续处理请求  
        }  
      
        @Override  
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,  
                               ModelAndView modelAndView) throws Exception {  
            // 在请求处理之后但在视图渲染之前记录日志  
            System.out.println("Post-Handle: " + request.getRequestURI());  
        }  
      
        @Override  
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)  
                throws Exception {  
            // 在请求处理完成之后(包括视图渲染)记录日志  
            System.out.println("After-Completion: " + request.getRequestURI());  
              
            // 注意:这里虽然可以访问到异常,但不建议在这里处理异常  
            // 因为此时请求已经处理完成,并且可能已经将异常信息发送给了客户端  
            if (ex != null) {  
                System.out.println("Exception occurred: " + ex.getMessage());  
            }  
        }  
    }
    
    1. 配置拦截器:
    import org.springframework.beans.factory.annotation.Autowired;  
    import org.springframework.context.annotation.Configuration;  
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;  
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  
      
    @Configuration  
    public class WebConfig implements WebMvcConfigurer {  
      
        @Autowired  
        private LoggingInterceptor loggingInterceptor;  
      
        @Override  
        public void addInterceptors(InterceptorRegistry registry) {  
            registry.addInterceptor(loggingInterceptor);  
        }  
    }
    
    1. 创建一个@ControllerAdvice类来处理异常:
    import org.springframework.http.HttpStatus;  
    import org.springframework.http.ResponseEntity;  
    import org.springframework.web.bind.annotation.ControllerAdvice;  
    import org.springframework.web.bind.annotation.ExceptionHandler;  
      
    @ControllerAdvice  
    public class GlobalExceptionHandler {  
      
        @ExceptionHandler(value = Exception.class)  
        public ResponseEntity<Object> handleException(Exception e) {  
            // 这里处理异常,比如记录日志、返回错误信息等  
            ErrorDto errorDto = new ErrorDto("Error", e.getMessage());  
            return new ResponseEntity<>(errorDto, HttpStatus.INTERNAL_SERVER_ERROR);  
        }  
      
        // ErrorDto 是一个简单的类,用于封装错误信息  
        // ...  
    }
    
    1. 测试在控制器中,抛出一个异常来测试异常处理是否生效:
    import org.springframework.web.bind.annotation.GetMapping;  
    import org.springframework.web.bind.annotation.RestController;  
      
    @RestController  
    public class MyController {  
      
        @GetMapping("/testException")  
        public String testException() {  
            throw new RuntimeException("Test Exception");  
        }  
    }
    
  • 访问/testException端点时,会触发异常,并且GlobalExceptionHandler中的handleException方法会被调用,返回一个包含错误信息的ResponseEntity对象。同时,拦截器的preHandlepostHandleafterCompletion方法也会被调用,你可以在afterCompletion方法中看到异常信息(如果有的话)。

  • 注意:不要在afterCompletion方法中处理异常,因为它通常用于清理资源等后续操作。

springaop实现日志

import org.aspectj.lang.ProceedingJoinPoint;  
import org.aspectj.lang.annotation.Around;  
import org.aspectj.lang.annotation.Aspect;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
import org.springframework.stereotype.Component;  
  
@Aspect  
@Component  
public class LoggingAspect {  
  
    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);  
  
    @Around("execution(* com.example.myapp.service.*.*(..))")  
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {  
        long startTime = System.currentTimeMillis();  
  
        // 记录方法执行前的日志  
        logger.info("Entering method: " + joinPoint.getSignature());  
  
        Object result = joinPoint.proceed(); // 继续执行目标方法  
  
        // 记录方法执行后的日志  
        long endTime = System.currentTimeMillis();  
        logger.info("Exiting method: " + joinPoint.getSignature() + " took " + (endTime - startTime) + "ms");  
  
        return result;  
    }  
}

springaop实现事务

  • 关于事务管理,Spring AOP通过@Transactional注解和PlatformTransactionManager接口来实现。以下是如何使用Spring AOP实现事务管理的步骤:

    1. 配置事务管理器:

      • 首先,你需要在Spring配置文件中配置一个事务管理器。事务管理器的类型取决于你使用的数据库和连接池。如果你使用的是MyBatis和JDBC,你可能会使用DataSourceTransactionManager

        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
            <property name="dataSource" ref="dataSource"/>  
        </bean>
        
    2. 启用事务注解:

      • 在Spring配置中启用对@Transactional注解的支持。这可以通过<tx:annotation-driven/>标签来完成。
      <tx:annotation-driven transaction-manager="transactionManager"/>
      
      • Java配置:
      @Configuration  
      @EnableTransactionManagement  
      public class AppConfig {  
          // ... 其他bean配置 ...  
      }
      
    3. 使用@Transactional注解:
      使用@Transactional注解来声明这些方法需要在事务上下文中执行。Spring会自动为你管理这些事务。

      @Service  
      public class MyService {  
            
          @Autowired  
          private MyRepository myRepository;  
            
          @Transactional  
          public void doSomethingInTransaction() {  
              // 这里执行一些数据库操作,如果发生异常,所有操作都会被回滚  
              myRepository.save(new MyEntity());  
          }  
      }
      
      • 事务属性:
        @Transactional注解还允许你指定事务的属性,比如传播行为、隔离级别、超时和只读标志等。例如:

        @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)  
        public void doSomethingWithCustomTransactionAttributes() {  
            // ...  
        }
        
    4. 异常处理:
      默认情况下,Spring会在运行时异常(RuntimeException及其子类)发生时回滚事务。如果你希望在其他类型的异常(如检查型异常)发生时也回滚事务,你可以使用rollbackFor属性来指定。

springaop实现权限认证

  • 步骤:

    1. 定义切面:创建一个切面类,用于拦截需要进行权限认证的方法。
    2. 定义切入点:在切面类中,使用AspectJ的切入点表达式定义哪些方法需要被拦截。
    3. 实现通知方法:在切面类中,实现一个或多个通知方法(如@Before、@Around等),在这些方法中实现权限认证逻辑。
  • 示例

  1. 定义一些需要权限认证的服务接口和实现类:

    // 服务接口  
    public interface MyService {  
        void secureMethod(String userId);  
    }  
      
    // 服务实现类  
    @Service  
    public class MyServiceImpl implements MyService {  
        @Override  
        public void secureMethod(String userId) {  
            // 业务逻辑  
            System.out.println("Executing secure method for user: " + userId);  
        }  
    }
    
  2. 创建一个切面类用于权限认证:

    @Aspect  
    @Component  
    public class AuthorizationAspect {  
      
        // 假设有一个权限检查服务  
        @Autowired  
        private PermissionCheckService permissionCheckService;  
      
        // 切入点表达式,拦截MyService接口的所有方法  
        @Before("execution(* com.example.myapp.service.MyService.*(..))")  
        public void checkPermission(JoinPoint joinPoint) {  
            // 获取方法参数  
            Object[] args = joinPoint.getArgs();  
            if (args.length > 0 && args[0] instanceof String) {  
                String userId = (String) args[0];  
                // 检查用户是否有权限执行该方法  
                if (!permissionCheckService.hasPermission(userId, joinPoint.getSignature().getName())) {  
                    throw new AccessDeniedException("Access denied for user: " + userId);  
                }  
            }  
        }  
    }
    
  3. 接下来,定义一个简单的PermissionCheckService接口和实现类,用于模拟权限检查逻辑:

    public interface PermissionCheckService {  
        boolean hasPermission(String userId, String methodName);  
    }  
      
    @Service  
    public class PermissionCheckServiceImpl implements PermissionCheckService {  
        @Override  
        public boolean hasPermission(String userId, String methodName) {  
            // 这里只是示例,实际应用中需要根据业务逻辑和权限配置来判断  
            return "admin".equals(userId);  
        }  
    }
    
  4. 在Spring Boot项目中启用AOP支持,可以通过在启动类上添加@EnableAspectJAutoProxy注解来实现:

    @SpringBootApplication  
    @EnableAspectJAutoProxy  
    public class MyApplication {  
        public static void main(String[] args) {  
            SpringApplication.run(MyApplication.class, args);  
        }  
    }
    
  • 总述:当调用MyServicesecureMethod方法时,AuthorizationAspect切面会自动拦截该方法,并执行权限认证逻辑。如果用户没有权限,则会抛出异常。

springMVC使用拦截器实现权限认证&日志记录

  • 创建一个拦截器类,实现 HandlerInterceptor 接口。
  • 在 preHandle 方法中,检查当前用户是否有权限访问目标资源。
    • 如果用户没有权限,则设置响应状态码或重定向到错误页面。
    • 如果用户有权限,则继续处理请求。
public class AuthenticationInterceptor implements HandlerInterceptor {  
  
    @Autowired  
    private AuthenticationService authenticationService; // 假设的权限服务  
  
    @Override  
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {  
     	 // 在Controller处理之前调用  
        // 可以在这里执行权限检查、记录日志等  
        // 如果返回false,则请求处理到此为止,不会调用Controller  
        // 获取当前用户(这里假设可以从请求中获取)  
        User user = ...;  
  
        // 检查用户是否有权限访问目标资源(这里假设 handler 是 HandlerMethod)  
        if (handler instanceof HandlerMethod) {  
            HandlerMethod handlerMethod = (HandlerMethod) handler;  
            if (!authenticationService.isAuthenticated(user, handlerMethod.getBeanType(), handlerMethod.getMethod().getName())) {  
                response.sendError(HttpServletResponse.SC_FORBIDDEN); // 发送 403 Forbidden 响应  
                return false; // 阻止后续处理  
            }  
        }  
  
        // 如果有权限,则继续处理请求  
        return true;  
    }  
    @Override  
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {  
        // 在Controller处理请求完成之后调用,但在视图渲染之前  
        // 可以在这里进行模型属性的修改等  
        System.out.println("MyInterceptor - postHandle");  
    }  
  
    @Override  
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {  
        // 在整个请求结束之后调用,即在视图渲染之后  
        // 通常用于清理资源、记录执行时间等  
        System.out.println("MyInterceptor - afterCompletion");  
    }  

}
  • 拦截器注册
java
import org.springframework.context.annotation.Configuration;  
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;  
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;  
  
@Configuration  
public class WebConfig implements WebMvcConfigurer {  
  
    @Override  
    public void addInterceptors(InterceptorRegistry registry) {  
        registry.addInterceptor(new MyInterceptor());  
        // 还可以添加多个拦截器,并为它们指定路径模式  
        /** registry.addInterceptor(anotherInterceptor())
		        .addPathPatterns("/somePath/**")
		        .excludePathPatterns("/static/**");;  


    }  
}

相关推荐

  1. Android-实现记录异常闪退“

    2024-04-30 15:26:03       27 阅读
  2. aop实现统一处理

    2024-04-30 15:26:03       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-30 15:26:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-30 15:26:03       101 阅读
  3. 在Django里面运行非项目文件

    2024-04-30 15:26:03       82 阅读
  4. Python语言-面向对象

    2024-04-30 15:26:03       91 阅读

热门阅读

  1. 【软测学习笔记】Linux入门Day01

    2024-04-30 15:26:03       28 阅读
  2. 点云和去噪

    2024-04-30 15:26:03       38 阅读
  3. K8S集群安装

    2024-04-30 15:26:03       28 阅读
  4. Android APP转成launcher

    2024-04-30 15:26:03       35 阅读
  5. Linux第六章

    2024-04-30 15:26:03       35 阅读
  6. 内存溢出如何实现自动化重启

    2024-04-30 15:26:03       36 阅读