一、Spring AOP
1、Spring AOP执行原理
1、连接点(JoinPoint)范围比较小
- 程序执行过程中的一个点,例如方法的调用,或抛出异常
- 就是 AOP 切面的插入点
2、切入点(Pointcut)范围比较大
- 选择一个或多个连接点的表达式,告诉AOP选择哪些切入位置,切入点包括连接点
- 0bean(userServiceImpl), 选择userServiceImpl Bean 全部方法
3、通知(Advice)
- 在选择的每个连接点执行的代码
- 通过注解配置在连接点执行代码的位置,例如@Around表示在方法前后都可以插入代码
4、切面(Aspect)
- 一个囊括了切入点和Advice的模块
- 是一个类,包含全部的切入点、通知等
5、织入(Weaving)(采用什么方式切进去)了解即可
- 将切面与主要代码进行结合的技术,Spring底层的代码,采用动态代理技术将Aspect嵌入目标的代码
6、目标对象(Target)
- 织入(通知) Advice 的目标对象
2、通知的类型
1、前置通知(@Before):在目标方法执行前触发的通知,在该通知中可以进行一些预处理逻辑。
2、后置通知(@After):在目标方法执行后触发的通知,不论是否有异常都执行。
3、返回通知(@AfterReturning):在目标方法执行并返回结果后触发的通知,该通知中可以访问目标方法的返回值。
4、异常通知(@AfterThrowing):在目标方法执行时出现异常触发的通知,该通知中可以访问目标方法抛出的异常对象。
5、环绕通知(@Around):在目标方法调用之前和之后,均可以执行自定义的代码,环绕通知需要使用ProceedingJoinPoint参数,来控制连接点执行。
/**
* 定义一个切面类
* 一个横切关注点
* 目的:统一处理日志功能
*/
@Component //交给Spring IOC容器管理
@Aspect //声明这是一个切面类
public class TimerAspect {
private static final Logger logger = LoggerFactory.getLogger(TimerAspect.class);
//前置通知
@Before("bean(userServiceImpl)")
public void before(JoinPoint point) {
//JoinPoint point获取目标方法的签名(方法名+参数列表)
logger.info("日志代码,执行{}操作前执行了日志:{}", point.getSignature().getName(), LocalDateTime.now());
}
//后置通知
@After("bean(userServiceImpl)")
public void after(JoinPoint point) {
//JoinPoint point获取目标方法的签名(方法名+参数列表)
logger.info("日志代码,执行{}操作后执行了日志:{}", point.getSignature().getName(), LocalDateTime.now());
}
/**
* 返回通知
*
* @param joinPoint 连接点信息
* @param object 执行目标方法的返回值
*/
@AfterReturning(value = "bean(userServiceImpl)", returning = "object")
public void afterReturning(JoinPoint joinPoint, Object object) {
logger.info("返回通知,执行了{}操作后执行了日志:{},返回值{}",
joinPoint.getSignature().getName(), LocalDateTime.now(), object);
}
/**
* 异常通知
* @param joinPoint
* @param e 执行目标方法发生的异常
*/
@AfterThrowing(value = "bean(userServiceImpl)",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e){
logger.info("异常通知,执行了{}操作后执行了日志:{},异常:{}",
joinPoint.getSignature().getName(), LocalDateTime.now(), e.getMessage());
}
//定义一个环绕通知 说明当前的方法在什么时候通知
//bean(userServiceImpl)表示一个切入点(在什么时候执行通知中的内容)
//执行userServiceImpl这个bean的任何方法,都会执行当前通知
@Around("bean(userServiceImpl)")
public Object log(ProceedingJoinPoint point) throws Throwable {
logger.info("执行" + point.getSignature().getName() + "操作");
//调用目标方法
Object result = point.proceed();
return result;
}
}
3、切点表达式
execution(方法修饰符 返回类型 类名.方法名(参数)):表示通过方法签名匹配目标方法,其中execution是固定的前缀,后面的括号里面是关于方法的限定信息。其中:
1、方法修饰符:可选项,表示访问修饰符;如public、private、protected、*(任意修饰符)等。
2、返回类型:可选项,表示方法的返回值类型;如void、int、*(匹配任意返回值类型)等。
3、类名:表示要拦截的类名,可以使用正则表达式来匹配;如com.example.service.UserService。
4、方法名:表示要拦截的方法名,也可以使用正则表达式来匹配;例:add、get*。
5、参数:可选项,用于匹配方法的参数。可以指定参数的类型,也可以使用“..”表示任意数量和类型的参数。
6、&&、||、!:用于组合切点表达式,与Java中的逻辑运算符相似。
@Around("execution(* cn.highedu.coolsharkhub.service.impl.*.find*(..))")
public Object exe(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("环绕通知开始,执行{}方法",joinPoint.getSignature().getName());
Object result = joinPoint.proceed(); //调用目标方法
logger.info("环绕通知结束,执行{}方法",joinPoint.getSignature().getName());
return result;
}
4、@Pointcut 统一管理切入点
@Pointcut是Spring框架中的一个注解,用于定义切点表达式,让通知方法能够准确地匹配到某个切入点。
二、Spring Boot
1、Spring 与 Spring Boot
Spring中不同的模块可能会出现版本不兼容的问题,整合框架时核心类要交给Spring去管理,Spring Boot简化了应用程序开发的框架。
Spring Boot框架的主要特点有依赖管理、自动化配置、微服务支持、生态系统完善。
2、依赖管理
在Spring Boot中有依赖管理,如Maven
dependencyManagement标签
用在父项目中,Maven的dependencyManagement标签是用来管理依赖项的一个特殊标签。它可以让开发者在一个pom.xml文件中集中管理多个子项目所共有的依赖项,并对其进行统一的版本和范围管理,避免不同子项目之间相同依赖包版本的冲突问题。
3、Spring Boot 中的组合注解
@SpringBootApplication注解
表示这个类是启动类
@SpringBootApplication注解是一个组合注解,主要包含了以下三个元注解。
1、@Configuration:用于定义配置类,Spring Boot项目的启动类现在也具有Spring框架配置类的功能。
在单独使用Spring框架时,一般会创建一个ContextConfig类,并添加@Configuration注解。ContextConfig类一般用于通过@Bean注解声明容器中使用的Bean。在Spring Boot项目中,不需要单独开发ContextConfig类,可以直接在启动类中可以使用@Bean注解定义Bean。
2、@ComponentScan:默认扫描启动类所在的包及其所有子包中的类前添加的注解,自动注册Bean。
3、@EnableAutoConfiguration:开启Spring Boot的自动配置功能,可以根据依赖自动配置Spring应用。
@SpringBootApplication
public class SctDay03Application {
public static void main(String[] args) {
SpringApplication.run(SctDay03Application.class, args);
}
}
@Conditional注解
可以用在类和方法上,当给定条件满足时,就会创建一个Bean或者执行一个配置类。这个注解可以和@Configuration,@Bean注解结合使用。
1、@ConditionalOnClass:在classpath下存在指定类名时生效,可以用来控制某些依赖包是否存在。
2、@ConditionalOnBean:在BeanFactory中存在指定的Bean时生效,用于确保使用的Spring Bean已经实例化。
3、@ConditionalOnMissingBean:在BeanFactory中不存在指定的Bean时生效,用于确保必要的Spring Bean不存在。
4、@ConditionalOnProperty:在指定的配置属性存在为true、false或不存在时生效的注解。
5、@ConditionalOnWebApplication:只在当前应用是Web应用时才生效。
6、@ConditionalOnResource:在classpath中有指定文件存在时生效。
注解示例:
创建工具接口:
public interface Tool {
}
创建电锯类:
@Component
@ConditionalOnBean(Worker.class) //如果worker这个对象存在,创建电锯对象
public class Saw implements Tool {
Logger logger = LoggerFactory.getLogger(Saw.class);
public Saw() {
logger.info("创建电锯");
}
@Override
public String toString() {
return "电锯";
}
}
创建斧头类:
@Component
@ConditionalOnMissingBean(name = "saw") //当电锯不存在 创建斧子对象
@ConditionalOnBean(Worker.class) //如果worker这个对象存在,创建斧子对象
public class Axe implements Tool {
Logger logger = LoggerFactory.getLogger(Axe.class);
public Axe() {
logger.info("创建斧子");
}
@Override
public String toString() {
return "斧子";
}
}
创建工人类:
@Component
public class Worker {
Logger logger = LoggerFactory.getLogger(Worker.class);
private String name = "光头强";
@Autowired
private Tool tool;
public Worker() {
logger.info("创建工人");
}
//工作方法
public void work() {
logger.info("{}使用{}砍树", name, tool);
}
@Override
public String toString() {
return "Worker{" +
"name='" + name + '\'' +
", tool=" + tool +
'}';
}
}
创建测试类:
@SpringBootTest
class WorkerTest {
Logger logger = LoggerFactory.getLogger(WorkerTest.class);
@Autowired
private Worker worker;
@Test
void work() {
worker.work();
}
}
测试结果:
: 创建电锯
: 创建工人
: Adding welcome page: class path resource [static/index.html]
: Started WorkerTest in 1.061 seconds (process running for 1.819)
: 光头强使用Saw{}砍树
4、Spring Boot自动配置
一些常见的Spring Boot自动配置如下:
1、Web开发中,Spring Boot可以自动配置Tomcat、Jetty、Undertow等嵌入式Web服务器,无需手动配置web.xml文件、Servlet API等内容。
2、在数据库操作中,Spring Boot可以自动配置JPA、Hibernate、MyBatis等ORM框架,无需手动配置数据源、连接池等内容。
3、安全认证中,Spring Boot可以自动配置Spring Security框架,提供多种身份认证、权限控制和资源保护的方案。
spring-boot-starter-jdbc自动配置
当开发者在Spring Boot项目中添加spring-boot-starter-jdbc 依赖时,Spring Boot的自动配置机制将会执行如下操作:
1、首先检查使用有自定义的DataSource对象,如果有DataSource类型的对象,就直接使用,如果没有会自动创建一个。
2、然后检查application.properties配置文件中是否有DataSource配置信息。如果有配置信息,就根据配置信息创建DataSource对象,连接到数据库,如果没有会自动创建一个。
3、接下来检查是否有Java内嵌的数据库(Derby、...),如果有内嵌数据库,就自动创建DataSource对象,自动连接到数据库。
4、如果上述检查都没有找到创建DataSource对象的信息,启动项目时会抛出异常:Error creating bean with name 'dataSource'。
如果检测到项目中有DataSource对象,还会自动创建JdbcTemplate对象,JdbcTemplate 是JDBC工具,速度比mybatis慢,不常用,可以简化JDBC开发。
三、Spring MVC
1、Spring MVC概述
可以在一个类里面处理多个业务,能处理响应、请求和类型转换等等内容,只需专注写业务代码。
2、Spring MVC核心概念**
1、DispatcherServlet:前端控制器,Spring MVC 是围绕前端控制器设计的,本质上是一个Servlet,相当于一个中转站,所有的访问都会走到这个Servlet中,再根据配置进行中转到相应的Handler中进行处理。
2、HandlerMapping:映射处理器,本质上是一段映射关系,将访问路径和对应的Handler存储为映射关系,在需要时供前端控制器查阅。主要的实现是RequestMappingHandlerMapping(支持@RequestMapping注解的方法)。
3、HandlerAdapter :处理器适配器,本质上是一个适配器,可以根据要求找到对应的Handler来运行。前端控制器通过处理器映射器找到对应的Handler信息之后,将请求响应和对应的Handler信息交由处理器适配器处理,处理器适配器找到真正Handler执行后,将结果即model和view返回给前端控制器。
4、Controller 控制器(需要编写):用于处理请求功能的程序,控制器中的方法是Handler实际执行的方法,控制器的处理结果是ModelAndView,或者最终被封装为ModelAndView。
- 如果返回 @ResponseBody 则使用MessageConverter处理响应结果
5、ModelAndView(不用):模型和视图,代表一个由控制器返回的模型和视图。视图是字符串视图名称。模型是一个Map,包含需要在视图上显示的数据。
6、ViewResolver:视图解析器,将控制器返回的视图名称解析为实际的视图,并将其渲染到响应。
7、View(基本现在也不用):视图,本质上就是将Handler处理器中返回的Model数据(现在都是返回json数据,通过ajax处理json数据)嵌入到视图解析器解析后得到的JSP/Thymeleaf页面中,向客户端做出响应。
MappingJackson2HttpMessageConverter
appingJackson2HttpMessageConverter是Spring MVC提供的HttpMessageConverter的一个实现。它将Java对象和JSON数据之间进行转换,并通过HTTP Response和Request进行传递。它底层使用Jackson库作为JSON处理库。
导入依赖
<!--操作以.xlsx为后缀的文件 EXCEL API-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
新建转换器类:
/**
* Excel转换器
*/
@Component
public class ExcelMessageConverter implements HttpMessageConverter {
//加载POI Excel主类
private Class excelObjectClass = XSSFWorkbook.class;
//请求媒体类型
private MediaType mediaType = new MediaType("application", "xlsx");
//设置响应类型
private String contentType = "application/vnd.ms-excel";
@Override
public boolean canRead(Class clazz, MediaType mediaType) {
return false;
}
@Override
public boolean canWrite(Class clazz, MediaType mediaType) {
if (clazz.equals(excelObjectClass)) {
return true;
}
return false;
}
@Override
public List<MediaType> getSupportedMediaTypes() {
List<MediaType> list = new ArrayList<>();
list.add(mediaType);
return list;
}
@Override
public Object read(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
return null;
}
@Override
public void write(Object o, MediaType mediaType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
//判断是不是excel请求
if (!(o instanceof XSSFWorkbook)) {
return;
}
//如果是excel请求,转换
XSSFWorkbook workbook = (XSSFWorkbook) o;
//写入文件
ByteArrayOutputStream buf = new ByteArrayOutputStream();
workbook.write(buf);
workbook.close();
buf.close();
byte[] bytes = buf.toByteArray();
//对文件进行设置
String fileName = "hello.xlsx"; //文件名
fileName = URLEncoder.encode(fileName, "UTF-8");
//定义一个响应
ServletServerHttpResponse response = (ServletServerHttpResponse) outputMessage;
//设置响应
response.getServletResponse().setCharacterEncoding("UTF-8");
//设置响应头
response.getServletResponse().setHeader("Content-Length",
String.valueOf(bytes.length));
response.getServletResponse().setHeader("Content-Type",
String.valueOf(contentType));
response.getServletResponse().setHeader("Content-Disposition",
"attachment; filename=" + fileName);
//写入文件
response.getBody().write(bytes);
}
@GetMapping("/excel")
@ResponseBody
public XSSFWorkbook excel() {
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet("Demo");
sheet.createRow(0).createCell(0).setCellValue("Hello World");
return workbook;
}
}