Spring图解核心,AOP切面编程,异步操作详解

Posted 宇宙磅礴而冷漠

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring图解核心,AOP切面编程,异步操作详解相关的知识,希望对你有一定的参考价值。

图解&解释

图解:
在这里插入图片描述
解释:
1.Spring Core
Spring Core模块是Spring的核心容器,它主要实现了IOC模式。此模块中包含的BeanFactory类是Spring的核心类,负责JavaBean的配置与管理。它采用工厂模式实现了IOC即依赖注入。

2.Spring Context
Spring Context模块继承BeanFactory(或者说Spring核心)类,ioc 容器通过应用上下文将配置加载到IOC容器。

3.Spring AOP
Spring集成了所有AOP功能。通过事务管理可以使任意Spring管理的对象AOP化,将程序的核心点和横切点分离。

4.Spring DAO
DAO是 Data Access Object的缩写,DAO模式思想是将业务逻辑代码与数据库交互代码分离,降低两者耦合。通过DAO模式可以使结构变得更为清晰,代码更为简洁。DAO模块提供了JDBC的抽象层,提供了对声明式事务和编程式事务的支持。

5.Spring ORM
Spring ORM (对象关系映射)模块提供了对现有ORM框架的支持,Spring没有必要开发新的ORM工具,Spring提供各类的接口支持(support),目前比较流行的下层数据库封闭映射框架,如 mybatis, Hibernate等。

6.Spring Web
此模块建立在Spring Context 基础之上,主要提供了表现层(spring mvc)到业务层(spring),它天生就有MVC所带来的一系列优点,如:结构层次分明,高可重用性,增加了程序的健壮性和可伸缩性。

7.Spring WebMVC
Spring WebMVC模块建立在Spring核心功能之上,这使它能拥有Spring框架的所有特性,依赖添加的Web框架,对表现层提供支持。

注入方式

接口注
setter方法注入
构造方法注入
直接注入

AOP

场景介绍

实际项目中通常会将系统分为两大部分,一部分是核心业务,一部分是非核业务。在编程实现时我们首先要完成的是核心业务的实现,非核心业务一般是通过特定方式切入到系统中,这种特定方式一般就是借助 AOP 进行实现。在不改变原有系统核心业务代码的基础上动态添加一些扩展功能并可以"控制"对象的执行。

原理图解

Spring AOP 底层基于代理机制(动态方式)实现功能扩展:

  1. 假如目标对象(被代理对象)实现接口,则底层可以采用 JDK 动态代理机制为目标
    对象创建代理对象(目标类和代理类会实现共同接口)。
  2. 假如目标对象(被代理对象)没有实现接口,则底层可以采用 CGLIB 代理机制为目
    标对象创建代理对象(默认创建的代理类会继承目标对象类型)。

图解:
在这里插入图片描述

术语

切面(aspect): 横切面对象,一般为一个具体类对象(可以借助@Aspect 声明)。
通 知 (Advice): 在 切面 的 某 个 特 定 连 接 点 上 执 行 的 动 作 ( 扩 展 功 能 ) ,例 如around,before,after 等。
连接点(joinpoint): 程序执行过程中某个特定的点,一般指向被拦截到的目标方法。
切入点(pointcut): 对多个连接点(Joinpoint)一种定义,一般可以理解为多个连接点的集 合

白话解释:切入点相当于机场多个安检点(每个安检点是一个连接点),安检过程是通知

通知类型(Advice)5种

@Before。(目标方法执行之前执行)
@AfterReturning。(目标方法成功结束时执行)
@AfterThrowing。(目标方法异常结束时执行)
@After。(目标方法结束时执行)
@Around。(目标方法执行前后都可以做业务拓展)(优先级最高)

切入点表达式9种之常用4种

bean

bean(userServiceImpl)//指定一个 userServiceImpl 类中所有方法。
bean(*ServiceImpl)//指定所有后缀为 ServiceImpl 的类中所有方法。
//使用示例
@Pointcut("bean(jsonServiceImpl)")
public void p(){}
@Around("p()")
//或
@Around("bean(jsonServiceImpl)")

within

within(xx.service.UserServiceImpl)//指定当前包中这个类内部的所有方法。
within(xx.service.*) //指定当前目录下的所有类的所有方法。
within(xx.service..*) //指定当前目录以及子目录中类的所有方法。

execution
语法:execution(返回值类型 包名.类名.方法名(参数列表))。

execution(void xx.service.UserServiceImpl.addUser())//匹配 addUser 方法。
execution(void xx.service.PersonServiceImpl.addUser(String)) //方法参数必须为String 的 addUser 方法。
execution(* xx.service..*.*(..)) //万能配置。
execution(public * *(..)) //拦截任意公共方法
execution(* get*(..)) //拦截以get开头的任意方法
execution(* xx.service.UserService.*(..)) //拦截类或者接口中的方法

@annotation

@annotation(xxx.xxx.MyAnnotation) //匹配有此注解描述的方法。

切面优先级设置

切面的优先级需要借助@Order 注解进行描述,数字越小优先级越高,默认优先级比较低。

@Order(1)
@Aspect
@Component
public class MyAspect {}

功能模块举例

异步日志处理

@Async 注解默认会基于 ThreadPoolTaskExecutor 对象获取工作线程,然后调用由@Async 描述的方法,让方法运行于一个工作线程,以实现异步操作。

@EnableAsync //spring 容器启动时会创建线程池
@SpringBootApplication
public class Application {
	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}
//在需要异步执行的业务方法上,使用@Async 方法进行异步声明
//线程池yml配置
spring:
 task:
 	execution:
 		pool:
 			queue-capacity: 128
 			core-size: 5
 			max-size: 128
 			keep-alive: 60s
 		thread-name-prefix: db-service-taskkkkkkk-
 		
参数解释:
queue-capacity: 队列长度
core-size: 核心线程数
max-size: 128 最大线程数
keep-alive: 60s 默认允许空闲时间60秒
thread-name-prefix: 线程前缀名称,名字取得较长便于测试识别
过程解释:
1.如果当前线程池的线程数还没有达到核心线程数大小,即 poolSize < corePoolSize ,无论是否有空闲的线程,系统回新增一个线程来处理新提交的任务;
2.如果当前线程池的线程数大于或者等于核心线程数大小,即 poolSize >= corePoolSize,且任务队列未满时,将提交的任务提交到阻塞队列中,等待处理
3.如果当前线程池的线程数大于或者等于核心线程数大小,即 poolSize >= corePoolSize,且任务队列已满时,分以下两种情况:
   1.poolSize < maximumPoolSize ,新增线程来处理任务;
   2.poolSize = maximuxPoolSize ,线程池的处理能力已达极限,此时会拒绝增加新任务,如何拒绝取决于RejectedExecutionHandler
@Slf4j
@Aspect
@Component
public class SysLogAspect {
    /**
     *  return目标方法的返回值 @Around描述方法的返回值必须为Object类型
     */
    //精确到指定注解的方法
    @Pointcut("@annotation(com.cy.pj.common.annotation.RequireLog)")
    public void doLog(){
        //不用写任何代码,只是用于承载注解@Pointcut
    }
    @Around("doLog()")
    public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable{
        long t1=System.currentTimeMillis();
        log.info("start {}",t1 );
        try {
            Object result=joinPoint.proceed();//执行目标方法
            long t2=System.currentTimeMillis();
            log.info("end {}",t2 );
            //在当前类@Async方法无效,需要在其他类定义方法在这里使用,需要传入joinPoint
            saveUserLog(joinPoint,t2-t1);//记录用户操作日志,调用下面的方法
            return result;
        }catch (Throwable e){
            log.error("error {}",e.getMessage() );
            throw e;
        }
    }
    //部分异步
    @Autowired
    private SysLogService sysLogService;
    private void saveUserLog(ProceedingJoinPoint joinPoint,long time) throws Throwable{
        SysLog sysLog=new SysLog();
//       当前示例通过反射获取数据,在下一个redis与数据库示例用更高级写法
        Class<?> targetClass=joinPoint.getTarget().getClass();
        MethodSignature ms=(MethodSignature) joinPoint.getSignature();
       // Method m=ms.getMethod();//jdk代理获取的是接口方法,CGlib能获取
        Method targetMethod=targetClass.getDeclaredMethod(ms.getName(),ms.getParameterTypes());
        RequireLog requireLog=targetClass.getAnnotation(RequireLog.class);
        String operation=requireLog.operation();
        String method=targetClass.getName()+"."+targetMethod.getName();
        String params=new ObjectMapper().writeValueAsString(joinPoint.getArgs());
        String ip=IPUtils.getIpAddr();

        sysLog.setParams(params);
        sysLog.setMethod(method);
        sysLog.setOperation(operation);
        sysLog.setIp(ip);
        sysLog.setTime(time);
        //以下方法采用异步,saveObject()被@Async标识,上面的代码也可以异步,传入joinPoint就可以了
        sysLogService.saveObject(sysLog);
    }
}

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;

public class IPUtils {
    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
    public static String getIpAddr() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ip = null;
        try {
            ip = request.getHeader("x-forwarded-for");
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (StringUtils.isEmpty(ip) || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        return ip;
    }
}

redis集群与数据库操作

//需要先自定义一个注解CacheFind
@Component
@Aspect
public class RedisAop {
//    @Lazy //要求对象必须有,@Autowired可以没有
    @Autowired(required = false)//类实例化的时候注入,required = false调用才注入,暂时不注入
    private JedisCluster jedis;
    @Pointcut("@annotation(com.xx.annotataion.CacheFind)")
    public void pointcut(){}
    

    /**
     * @param joinPoint
     * @return
     * 获取注解CacheFind的key前缀+动态拼接
     * 向redis中获取数据
     * 1.没数据:查询数据库,之后保存到缓存
     * 2.有数据:有数据,直接将缓存数据返回
     */
     //@Around("pointcut()")//以下可直接写成另一种方式,可省却pointcut
    @Around("@annotation(com.xx.annotataion.cacheFind)")//高级写法cacheFind,注意是小写cacheFind
    public Object doAround(ProceedingJoinPoint joinPoint, CacheFind cacheFind)throws Throwable{
        String prekey=cacheFind.key();
        System.out.println("注解属性key  "+ prekey);
        String args= Arrays.toString(joinPoint.getArgs());
        String key= prekey+"::"+args;// ...::[0]
        Object obj=null;
        if(jedis.exists(key)){
            MethodSignature signature=(MethodSignature) joinPoint.getSignature();
            Class c=signature.getReturnType();
            //以下c用object.class代替的话,object只能转化简单对象,不能转化嵌套对象
            obj= ObjectMapperUtil.toObject(jedis.get(key), c);
            System.out.println("查询redis缓存");
        }else {
            obj=joinPoint.proceed();
            System.out.println("aop查询数据库");
            if(cacheFind.second()>0){
                jedis.setex(key,cacheFind.second(),ObjectMapperUtil.toJSON(obj));

            }else {
                jedis.set(key,ObjectMapperUtil.toJSON(obj) );
            }
        }
        //jedis.close();//pool就关
        return obj;
    }
}

以上是关于Spring图解核心,AOP切面编程,异步操作详解的主要内容,如果未能解决你的问题,请参考以下文章

Spring框架——AOP(面向切面编程)详解

Spring AOP用法详解

Spring中切面详解(AOP)

spring框架 AOP核心详解

Spring详解------面向切面编程

Spring-AOP面向切面编程