引用:
spring注解之 @Target 和 @Retention
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");
}
}
-
核心启动:作为Spring Boot应用的入口
-
事务管理:通过
@EnableTransactionManagement
启用事务 -
日志记录:通过
@Slf4j
提供日志能力 -
缓存支持:通过
@EnableCaching
启用缓存 -
定时任务:通过
@EnableScheduling
启用定时任务 -
自动配置:通过
@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信息
@Retention
Retention(保留) 注解说明,这种类型的注解会被保留到那个阶段.有三个值:
RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
范围级别: source < class < runtime
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
中定义的操作类型
joinPoint.getSignature()
获取被拦截方法的签名信息(方法名、参数类型等)
返回类型为
Signature
接口,实际是MethodSignature
实现类强制转型
(MethodSignature)
因为Spring AOP拦截的都是方法,所以可安全转为
MethodSignature
转型后能获取更丰富的方法元数据
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);
signature.getMethod()
获取被拦截的
java.lang.reflect.Method
对象示例:若拦截的是
UserMapper.insert()
,则返回该方法对象从方法上获取
.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();
}
}