初级
包扫描
• 默认的包结构
• 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
• 无需以前的包扫描配置
• 想要改变扫描路径,@SpringBootApplication(scanBasePackages=“com.atguigu”)
• 或者@ComponentScan 指定扫描路径
@SpringBootApplication
等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(“com.boot”)
注册bean
- 方法一
MyConfig文件中
@Bean
public User user01(){
User user = new User("张三", 18,tomcatPet());
return user;
}
- 方法二@Component + @ConfigurationProperties
bean中
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}
application.properties文件中
mycar.brand=BYD
mycar.price=100000
Controlller中
@Autowired
Car car;
@RequestMapping("/car")
public Car car() {
return car;
}
- 方法三@EnableConfigurationProperties + @ConfigurationProperties
bean中
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
}
MyConfig文件中
@EnableConfigurationProperties(Car.class)
Controlller中
@Autowired
Car car;
@RequestMapping("/car")
public Car car() {
return car;
}
注解源码
SpringBootApplication注解的包含关系
- @SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
- @SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
- @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
/**
* Environment property that can be used to override when auto-configuration is
* enabled.
*/
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
/**
* Exclude specific auto-configuration classes such that they will never be applied.
* @return the classes to exclude
*/
Class<?>[] exclude() default {};
/**
* Exclude specific auto-configuration class names such that they will never be
* applied.
* @return the class names to exclude
* @since 1.3.0
*/
String[] excludeName() default {};
}
- @ComponentScan(" ")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default "**/*.class";
boolean useDefaultFilters() default true;
ComponentScan.Filter[] includeFilters() default {};
ComponentScan.Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
结论
@SpringBootApplication包含了
1.@SpringBootConfiguration
//@EnableAutoConfiguration(重要)内含
//@AutoConfigurationPackage
//@Import(AutoConfigurationImportSelector.class)
2.@EnableAutoConfiguration
3.@ComponentScan(" ")
@EnableAutoConfiguration中的@AutoConfigurationPackage
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//重点Registrar方法
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
- @Import(AutoConfigurationPackages.Registrar.class)下的Register批量注册(重点)
//利用Registrar给容器中导入一系列组件
//通过public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)中的metadata,即通过元数据获取包名,即导入的是MainApplication 所在包下。
public abstract class AutoConfigurationPackages {
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
}
@EnableAutoConfiguration中的@Import(AutoConfigurationImportSelector.class)
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories
1、所有场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration
2、按照条件装配规则(@Conditional),最终会按需配置。
总结
总结:
• SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
• 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
• 生效的配置类就会给容器中装配很多组件
• 只要容器中有这些组件,相当于这些功能就有了
• 定制化配置
• 用户直接自己@Bean替换底层的组件
• 用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration ---> 组件 ---> xxxxProperties里面拿值 ----> application.properties
入门
注入POJO的参数
已有类型
- 匹配参数解析器-HandlerMethodArgumentResolver
- 预先生成所需要注入参数的POJO类
- 通过Converter将HTTP报文中的String类型参数匹配注入到生成的POJO类中
自定义POJO
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Pet>() {
@Override
public Pet convert(String s) {
if (!ObjectUtils.isEmpty(s)){
Pet pet=new Pet();
String[] split=s.split(",");
pet.setName(split[0]);
pet.setAge(Integer.parseInt(split[1]));
return pet;
}
return null;
}
});
}
}
响应JSON
1.获取返回值类型
- 获取了返回值对象的类型
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
- 寻找返回值处理器
- private Boolean isAsyncReturnValue(){}方法中所有返回值处理器判断返回值是否是异步返回值,返回false
- private Boolean isAsyncReturnValue(){}方法中所有返回值处理器判断返回值是否是异步返回值,返回false
- HandlerMethodReturnValueHandle返回值处理器先通过supportsReturnType判断是否支持类型返回值
- 返回值处理器调用 handleReturnValue 进行,拿到返回值对象和返回值对象的类型,找到能够处理的返回值处理器
handleReturnValue 支持的类型有
ModelAndView
Model
View
ResponseEntity
ResponseBodyEmitter
StreamingResponseBody
HttpEntity
HttpHeaders
Callable
DeferredResult
ListenableFuture
CompletionStage
WebAsyncTask
有 @ModelAttribute 且为对象类型的
@ResponseBody 注解 ---> RequestResponseBodyMethodProcessor;
2.HttpMessageConverter
-
利用了@ResponseBody里的RequestResponseMethodProcessor返回值处理器,调用MessageConverter
-
RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
- 利用 MessageConverters 进行处理 (判断返回值类型)将数据写为json
1. 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型MeidaType)
2. 服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型MeidaType的数据
3. 1和2中的内容互相匹配,存到mediaTypesToUse链表中
4. SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理?-
得到MappingJackson2HttpMessageConverter中即可以将对象写为json
-
利用MappingJackson2HttpMessageConverter将对象转为json再写出去。即找到要转换的对象的supporter转换成json数据,并且可以将任何对象生成为json格式的数据。最终 MappingJackson2HttpMessageConverter 把对象转为JSON(利用底层的jackson的objectMapper转换的)
-
- 利用 MessageConverters 进行处理 (判断返回值类型)将数据写为json
响应xml
统计底层所有的MessageConverter,请求头Header中的Accept中描述了浏览器可以接收的类型和权重,找到最佳匹配,用支持将MediaType转成最佳匹配类型的Converter,通过底层将对象转换成xml
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
基于请求参数类型的内容协商模式
- contentNegotiationManager 内容协商管理器 默认使用基于请求头HeaderContentNegotiationStrategy的策略
- 基于参数的内容协商,在yml.application中开启ParameterContentNegotiationStrategy策略
spring:
contentnegotiation:
favor-parameter: true//开启类容协商模式
//发送请求
http://localhost:8080/test/person?format=json
http://localhost:8080/test/person?format=xml
- 内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型MeidaType),此时parameter中携带了参数且优先级最高
- 后面同之前
自定义MessageConverter
实现多协议数据兼容。json、xml、x-guigu
0、 @ResponseBody 响应数据出去 调用 RequestResponseBodyMethodProcessor 处理
1、Processor 处理方法返回值。通过 MessageConverter 处理
2、所有 MessageConverter 合起来可以支持各种媒体类型数据的操作(读、写)
3、内容协商找到最终的 messageConverter;
@Bean
public WebMvcConfigurer webMvcConfigurer(){
return new WebMvcConfigurer() {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
}
}
}
浏览器配置自定义参数访问
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
Map<String, MediaType> mediaTypeMap = new HashMap<>();
mediaTypeMap.put("gg",MediaType.parseMediaType("application/x-guigu"));
ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypeMap);
configurer.strategies(Arrays.asList(strategy));
}
}
浏览器发送请求
http://localhost:8080/test/person?format=gg
//如果spring版本太低可能会覆盖掉没配置的MediaType,包括参数方式和请求头方式,需额外添加
拦截器
拦截器配置
/**
* 1、编写一个拦截器实现HandlerInterceptor接口
* 2、拦截器注册到容器中(实现WebMvcConfigurer的addInterceptors)
* 3、指定拦截规则【如果是拦截所有,静态资源也会被拦截】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") //所有请求都被拦截包括静态资源
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**"); //放行的请求
}
}
拦截器设置
public class InterceptController implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String requestURI = request.getRequestURI();
log.info("preHandle拦截的请求路径是{}",requestURI);
//登录检查逻辑
HttpSession session = request.getSession();
Object loginUser = session.getAttribute("loginUser");
if(loginUser != null){
//放行
return true;
}
//拦截住。未登录。跳转到登录页
request.setAttribute("msg","请先登录");
// re.sendRedirect("/");
request.getRequestDispatcher("/").forward(request,response);
return false;
}
}
拦截器原理
1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有 拦截器】
2、先来顺序执行 所有拦截器的 preHandle方法
• 1、如果当前拦截器prehandler返回为true。则执行下一个拦截器的preHandle
• 2、如果当前拦截器返回为false。直接 倒序执行所有已经执行了的拦截器的 afterCompletion;
3、如果任何一个拦截器返回false。直接跳出不执行目标方法
4、所有拦截器都返回True。执行目标方法
5、倒序执行所有拦截器的postHandle方法。
6、前面的步骤有任何异常都会直接倒序触发 afterCompletion
7、页面成功渲染完成以后,也会倒序触发 afterCompletion
文件上传
- 表单
<form method="post" action="/upload" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
- 参数设置
@PostMapping("/upload")
public String upload(@RequestParam("email") String email,
@RequestParam("username") String username,
@RequestPart("headerImg") MultipartFile headerImg,
@RequestPart("photos") MultipartFile[] photos) throws IOException {
}
- 自动配置原理
文件上传自动配置类-MultipartAutoConfiguration-MultipartProperties
• 自动配置好了 StandardServletMultipartResolver 【文件上传解析器】
• 原理步骤
• 1、请求进来使用文件上传解析器判断(isMultipart)并封装(resolveMultipart,返回MultipartHttpServletRequest)文件上传请求
• 2、参数解析器来解析请求中的文件内容封装成MultipartFile
• 3、将request中文件信息封装为一个Map;MultiValueMap<String, MultipartFile>
FileCopyUtils。实现文件流的拷贝
异常处理
- 错误代码可以设置为5xx,4xx,模糊匹配,否则仅匹配响应错误代码的页面,其他页面仍然为默认的白页
使用Servlet API
原生方式
- @ServletComponentScan(basePackages = “com.hao.boot”)
:指定原生Servlet组件都放在那里
@ServletComponentScan(basePackages = "com.hao.boot")
@SpringBootApplication
public class Boot05WebAdminApplication {
public static void main(String[] args) {
SpringApplication.run(Boot05WebAdminApplication.class, args);
}
}
- 创建各类
- @WebServlet(urlPatterns = “/my”):效果:直接响应,没有经过Spring的拦截器?
@javax.servlet.annotation.WebServlet(urlPatterns = "/my")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().write("123");
}
}
- @WebFilter(urlPatterns={"/css/","/images/"})
@WebFilter(urlPatterns={"/css/*","/images/*"})
public class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
}
}
- @WebListener
@WebListener
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
}
}
推荐方式
@Configuration
public class MyRegisterConfig {
@Bean
public ServletRegistrationBean myServlet() {
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet, "/my", "/my2");
}
@Bean
public FilterRegistrationBean myFilter() {
MyFilter myFilter = new MyFilter();
//方法一拦截myServlet的路径
//return new FilterRegistrationBean(myFilter,myServlet());
//方法二
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(myFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/my", "/css/*"));
return filterRegistrationBean;
}
@Bean
public ServletListenerRegistrationBean myListener() {
MyListener myListener = new MyListener();
return new ServletListenerRegistrationBean(myListener);
}
}
Tomcat路径设置
- DispatcherServlet默认拦截http://localhost:8080/下
- Tomcat-Servlet默认访问
@Bean
public ServletRegistrationBean myServlet() {
MyServlet myServlet = new MyServlet();
return new ServletRegistrationBean(myServlet, "/my", "/my2");
}
此处设置的路径
多个Servlet都能处理到同一层路径,精确优选原则
//此时未经过DispatcherServlet可以跳过拦截器
A: /my/
//此时无法匹配,经过了DispatcherServlet,不在可以访问路径
B: /my/1
druid连接池
- 依赖
- 开启功能
@Configuration
public class MyDataSourceConfiguration {
//开启监控功能及防火墙
@Bean
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() throws SQLException {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setFilters("stat,wall");
return druidDataSource;
}
//设置访问路径及登录账号密码
@Bean
public ServletRegistrationBean statViewServlet() {
StatViewServlet statViewServlet = new StatViewServlet();
ServletRegistrationBean<StatViewServlet> statViewServletServletRegistrationBean = new ServletRegistrationBean<>(statViewServlet, "/druid/*");
statViewServletServletRegistrationBean.addInitParameter("loginUsername","admin");
statViewServletServletRegistrationBean.addInitParameter("loginPassword","admin");
return statViewServletServletRegistrationBean;
}
//设置防火墙
@Bean
public FilterRegistrationBean webStatFilter() {
WebStatFilter webStatFilter = new WebStatFilter();
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(webStatFilter);
filterRegistrationBean.setUrlPatterns(Arrays.asList("/*"));
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.css,/druid/*");
return filterRegistrationBean;
}
}
Druid注意
- 问题: yml方式无法生效,待解决
- 建议: 在yml中配置数据库基本功能,监控功能在config配置类中配置