苍穹外卖服务层解读(一)

引用:

Java注解 @Target

spring注解之 @Target 和 @Retention

SpringBoot简单使用切面类(@aspect注解)

spring AOP注解@Aspect的使用以及spring注解失效的场景

目录

SkyApplication 

package com.sky;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement //开启注解方式的事务管理
@Slf4j
@EnableCaching //开启缓存注解
@EnableScheduling //开启任务调度注解
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}
  1. 核心启动:作为Spring Boot应用的入口

  2. 事务管理:通过@EnableTransactionManagement启用事务

  3. 日志记录:通过@Slf4j提供日志能力

  4. 缓存支持:通过@EnableCaching启用缓存

  5. 定时任务:通过@EnableScheduling启用定时任务

  6. 自动配置:通过@SpringBootApplication实现自动配置和组件扫描

注解

@SpringBootApplication 


 核心注解:标记为Spring Boot应用入口

组合了三个注解:

@SpringBootConfiguration:标记为配置类

@EnableAutoConfiguration:启用自动配置

@ComponentScan:自动扫描当前包及其子包下的组件

@EnableTransactionManagement
 

开启注解方式的事务管理

启用Spring的声明式事务支持

允许在方法/类上使用@Transactional注解管理事务

@Transactional 是 Spring 框架中用于声明式事务管理的核心注解,它简化了数据库事务的操作。

@Slf4j 

​​​​​Lombok注解:自动生成日志对象log

相当于隐式声明:

private static final Logger log = LoggerFactory.getLogger(SkyApplication.class);

@EnableCaching


开启Spring的缓存注解功能

启用缓存支持

允许使用@Cacheable@CacheEvict等注解管理缓存

@EnableScheduling

开启Spring的任务调度功能

启用定时任务支持

允许使用@Scheduled注解创建定时任务

 

annotation的AutoFill.java

package com.sky.annotation;

import com.sky.enumeration.OperationType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,用于标识某一个方法需要进行功能字段自动填充
 */

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}

 @AutoFill 注解是一个简洁而强大的自定义注解,专门用于标识需要自动填充公共字段的方法。

@Target(ElementType.METHOD) // 限定注解只能用于方法上

@RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.

 

@Target(ElementType.METHOD)

// 限定注解只能用于方法上
@Target ,用来表示注解作用范围,超过此范围,编译会报错。

@Target:注解的作用目标:

        @Target(ElementType.TYPE)——接口、类、枚举、注解

        @Target(ElementType.FIELD)——字段、枚举的常量        

        @Target(ElementType.METHOD)——方法

        @Target(ElementType.PARAMETER)——方法参数

        @Target(ElementType.CONSTRUCTOR) ——构造函数

        @Target(ElementType.LOCAL_VARIABLE)——局部变量

        @Target(ElementType.ANNOTATION_TYPE)——注解

      @Target(ElementType.PACKAGE)——包,用于记录java文件的package信息

参考:Java注解 @Target

@Retention


Retention(保留) 注解说明,这种类型的注解会被保留到那个阶段.

有三个值:

RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
范围级别: source < class < runtime

参考:spring注解之 @Target 和 @Retention

OperationType value()

OperationType value():声明一个名为 value 的属性类型为

 OperationType(自定义枚举类型)

作为注解的唯一属性,使用时可以直接赋值无需指定属性名

@interface

注解@interface不是接口是注解类,在jdk1.5之后加入的功能,使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。

@interface 关键字用于定义自定义注解(Annotation)。注解是一种特殊的接口类型,用于为 Java 代码提供元数据(metadata),这些元数据可以被编译器、开发工具或运行时框架读取和使用。

aspect的AutoFillAspect.java

package com.sky.aspect;


import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */

    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
        OperationType operationType = autoFill.value();

        //获取到当前被拦截的方法的参数--实体对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }

        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            //为4个公共字段赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME,LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER,Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            }catch (Exception e){
                e.printStackTrace();
            }
        }else if(operationType == OperationType.UPDATE){
            try {
                //为2个公共字段赋值
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME,LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER,Long.class);

                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}

@Aspect

标注该类是一个切面类

AOP思想:

AOP(Aspect Oriented Programming)是一种面向切面的编程思想。面向切面编程是将程序抽象成各个切面,即解剖对象的内部,将那些影响了多个类的公共行为抽取到一个可重用模块里,减少系统的重复代码,降低模块间的耦合度,增强代码的可操作性和可维护性。AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理、增强处理。

AOP的使用场景:

权限认证、日志、事务处理、增强处理

@Aspect的使用以及基本概念:

1.切面类 @Aspect: 定义切面类,加上@Aspect、@Component注解

2.切点 @Pointcut

3.Advice,在切入点上执行的增强处理,主要有五个注解:

    @Before  在切点方法之前执行

       @After  在切点方法之后执行

       @AfterReturning 切点方法返回后执行

     @AfterThrowing 切点方法抛异常执行

     @Around 属于环绕增强,能控制切点执行前,执行后

4.JoinPoint :方法中的参数JoinPoint为连接点对象,它可以获取当前切入的方法的参数、代理类等信息,因此可以记录一些信息,验证一些信息等;

5.使用&&、||、!、三种运算符来组合切点表达式,表示与或非的关系;

6.@annotation(annotationType) 匹配指定注解为切入点的方法;

使用切面的好处


代码解耦
通过将横切关注点(如日志、事务等)与业务逻辑分离,使得代码更加模块化,便于维护和扩展。

减少重复代码
AOP可以将通用的功能抽取到一个切面中,然后在需要的地方进行引用,避免了重复编写相同的代码。

提高代码的可重用性
AOP可以将通用的功能封装成一个切面,然后在多个类或方法中进行引用,提高了代码的可重用性。

增强代码的灵活性
AOP可以在运行时动态地改变程序的行为,增强了代码的灵活性。

易于测试
AOP可以将测试代码与业务逻辑分离,使得测试更加简单和方便。

@Pointcut

@Pointcut("execution(* com.sky.mapper.*.*(..))

execution:切入点指示符,匹配方法执行

*:匹配任意返回类型

com.sky.mapper.*:匹配 com.sky.mapper 包下的所有类

.*:匹配这些类的所有方法

(..):匹配任意参数列表

匹配范围com.sky.mapper 包中所有类的所有方法

@annotation(com.sky.annotation.AutoFill)")

@annotation:匹配带有指定注解的方法

com.sky.annotation.AutoFill:自定义的 @AutoFill 注解全限定名

匹配要求:方法必须标注 @AutoFill 注解

@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)"

双重匹配条件

方法位于 com.sky.mapper 包中

方法上标注了 @AutoFill 注解

 @Before

@Before("autoFillPointCut()") 是 Spring AOP(面向切面编程)中的一个注解,用于在特定切点(Pointcut)之前执行通知(Advice)。该注解表示在 autoFillPointCut() 方法定义的切点之前运行通知逻辑。

MethodSignature signature = (MethodSignature) joinPoint.getSignature();

从被拦截的方法上提取自定义注解@AutoFill中定义的操作类型

  1. joinPoint.getSignature()

    • 获取被拦截方法的签名信息(方法名、参数类型等)

    • 返回类型为Signature接口,实际是MethodSignature实现类

  2. 强制转型(MethodSignature)

    • 因为Spring AOP拦截的都是方法,所以可安全转为MethodSignature

    • 转型后能获取更丰富的方法元数据

AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);

  1. signature.getMethod()

    • 获取被拦截的java.lang.reflect.Method对象

    • 示例:若拦截的是UserMapper.insert(),则返回该方法对象

  2. .getAnnotation(AutoFill.class)

    从方法上获取@AutoFill自定义注解

autoFill.value()

调用注解的value()方法获取其枚举值

相当于读取注解中定义的操作类型

confing

package com.sky.config;

import com.sky.properties.AliOssProperties;
import com.sky.utils.AliOssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class OssConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public AliOssUtil aliOssUtil(AliOssProperties aliOssProperties){
        log.info("开始创建阿里云文件上传工具对象:{}",aliOssProperties);
        return new AliOssUtil(
                aliOssProperties.getEndpoint(),
                aliOssProperties.getAccessKeyId(),
                aliOssProperties.getAccessKeySecret(),
                aliOssProperties.getBucketName()
        );
    }
}

 

package com.sky.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@Slf4j
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){

        log.info("开始创建redis模板...");
        RedisTemplate redisTemplate = new RedisTemplate();

        //设置redis的连接工厂对象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //设置redis key 的序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());

        //设置redis value 的序列化器
//        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
package com.sky.config;

import com.sky.interceptor.JwtTokenAdminInterceptor;
import com.sky.interceptor.JwtTokenUserInterceptor;
import com.sky.json.JacksonObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;

import java.util.List;

/**
 * 配置类,注册web层相关组件
 */
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {

    @Autowired
    private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;

    @Autowired
    private JwtTokenUserInterceptor jwtTokenUserInterceptor;

    /**
     * 注册自定义拦截器
     *
     * @param registry
     */
    protected void addInterceptors(InterceptorRegistry registry) {
        log.info("开始注册自定义拦截器...");
        registry.addInterceptor(jwtTokenAdminInterceptor)
                .addPathPatterns("/admin/**")
                .excludePathPatterns("/admin/employee/login");

        registry.addInterceptor(jwtTokenUserInterceptor)
                .addPathPatterns("/user/**")
                .excludePathPatterns("/user/user/login")
                .excludePathPatterns("/user/shop/status");


    }

    /**
     * 通过knife4j生成接口文档
     * @return
     */
    @Bean
    public Docket docket1() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("管理端接口文档")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.admin"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    @Bean
    public Docket docket2() {
        ApiInfo apiInfo = new ApiInfoBuilder()
                .title("苍穹外卖项目接口文档")
                .version("2.0")
                .description("苍穹外卖项目接口文档")
                .build();
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .groupName("用户端接口文档")
                .apiInfo(apiInfo)
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.sky.controller.user"))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }

    /**
     * 设置静态资源映射
     * @param registry
     */
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        log.info("开启静态资源映射");
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * 扩展Spring MVC框架的消息转化器
     * @Parm converters
     */
    protected void extendMessageConverters(List<HttpMessageConverter<?>> converters){

        log.info("消息转换器...");
        //创建一个消息转换器对象
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        //需要为消息转换器设置一个对象转换器,对象转换器可以将Java对象序列化为json数据
        converter.setObjectMapper(new JacksonObjectMapper());
        //将自己的信息装换器加入容器
        converters.add(0,converter);
    }

}
package com.sky.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * WebSocket配置类,用于注册WebSocket的Bean
 */
@Configuration
public class WebSocketConfiguration {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JBM.下北泽の大天使

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值