可以看到,我们课上讲的,都是 BeanFactory 提供的基本功能,ApplicationContext 中的扩展功能都没有用到。
com.itheima.a01 包
通过这个示例结合 debug 查看 ApplicationContext 对象的内部结构,学到:
建议练习:完成用户注册与发送短信之间的解耦,用事件方式、和 AOP 方式分别实现
注意
public class TestMessageSource {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("messageSource", MessageSource.class, () -> {
ResourceBundleMessageSource ms = new ResourceBundleMessageSource();
ms.setDefaultEncoding("utf-8");
ms.setBasename("messages");
return ms;
});
context.refresh();
System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
System.out.println(context.getMessage("hi", null, Locale.CHINESE));
System.out.println(context.getMessage("hi", null, Locale.JAPANESE));
}
}
国际化文件均在 src/resources 目录下
messages.properties(空)
messages_en.properties
hi=Hello
messages_ja.properties
hi=こんにちは
messages_zh.properties
hi=你好
注意
Spring 的发展历史较为悠久,因此很多资料还在讲解它较旧的实现,这里出于怀旧的原因,把它们都列出来,供大家参考
另外要注意的是,后面这些带有 ApplicationContext 的类都是 ApplicationContext 接口的实现,但它们是组合了 DefaultListableBeanFactory 的功能,并非继承而来
com.itheima.a02.TestBeanFactory
com.itheima.a02.A02
一个受 Spring 管理的 bean,生命周期主要阶段有
一些资料会提到,生命周期中还有一类 bean 后处理器:BeanPostProcessor,会在 bean 的初始化的前后,提供一些扩展逻辑。但这种说法是不完整的,见下面的演示1
com.itheima.a03 包
graph LR
创建 --> 依赖注入
依赖注入 --> 初始化
初始化 --> 可用
可用 --> 销毁
创建前后的增强
依赖注入前的增强
初始化前后的增强
销毁之前的增强
public class TestMethodTemplate {
public static void main(String[] args) {
MyBeanFactory beanFactory = new MyBeanFactory();
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Autowired"));
beanFactory.addBeanPostProcessor(bean -> System.out.println("解析 @Resource"));
beanFactory.getBean();
}
// 模板方法 Template Method Pattern
static class MyBeanFactory {
public Object getBean() {
Object bean = new Object();
System.out.println("构造 " + bean);
System.out.println("依赖注入 " + bean); // @Autowired, @Resource
for (BeanPostProcessor processor : processors) {
processor.inject(bean);
}
System.out.println("初始化 " + bean);
return bean;
}
private List<BeanPostProcessor> processors = new ArrayList<>();
public void addBeanPostProcessor(BeanPostProcessor processor) {
processors.add(processor);
}
}
static interface BeanPostProcessor {
public void inject(Object bean); // 对依赖注入阶段的扩展
}
}
com.itheima.a03.TestProcessOrder
com.itheima.a04 包
com.itheima.a04.DigInAutowired
com.itheima.a05 包
com.itheima.a05.ComponentScanPostProcessor
com.itheima.a05.AtBeanPostProcessor
com.itheima.a05.MapperPostProcessor
com.itheima.a06 包
Java 配置类不包含 BeanFactoryPostProcessor 的情况
sequenceDiagram
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor
ac ->> +config : 3. 创建和初始化
bpp ->> config : 3.1 依赖注入扩展(如 @Value 和 @Autowired)
bpp ->> config : 3.2 初始化扩展(如 @PostConstruct)
ac ->> config : 3.3 执行 Aware 及 InitializingBean
config -->> -ac : 3.4 创建成功
Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效
sequenceDiagram
participant ac as ApplicationContext
participant bfpp as BeanFactoryPostProcessor
participant bpp as BeanPostProcessor
participant config as Java配置类
ac ->> +config : 3. 创建和初始化
ac ->> config : 3.1 执行 Aware 及 InitializingBean
config -->> -ac : 3.2 创建成功
ac ->> bfpp : 1. 执行 BeanFactoryPostProcessor
ac ->> bpp : 2. 注册 BeanPostProcessor
对应代码
@Configuration
public class MyConfig1 {
private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
log.debug("注入 ApplicationContext");
}
@PostConstruct
public void init() {
log.debug("初始化");
}
@Bean // ⬅️ 注释或添加 beanFactory 后处理器对应上方两种情况
public BeanFactoryPostProcessor processor1() {
return beanFactory -> {
log.debug("执行 processor1");
};
}
}
注意 解决方法:
com.itheima.a07 包
Spring 提供了多种初始化手段,除了课堂上讲的 @PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是
与初始化类似,Spring 也提供了多种销毁手段,执行顺序为
在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃
但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有
com.itheima.a08 包
以单例注入多例为例
有一个单例对象 E
@Component
public class E {
private static final Logger log = LoggerFactory.getLogger(E.class);
private F f;
public E() {
log.info("E()");
}
@Autowired
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
public F getF() {
return f;
}
}
要注入的对象 F 期望是多例
@Component
@Scope("prototype")
public class F {
private static final Logger log = LoggerFactory.getLogger(F.class);
public F() {
log.info("F()");
}
}
测试
E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);
输出
com.itheima.demo.cycle.F@6622fc65
com.itheima.demo.cycle.F@6622fc65
发现它们是同一个对象,而不是期望的多例对象
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
graph LR
e1(e 创建)
e2(e set 注入 f)
f1(f 创建)
e1-->f1-->e2
解决
graph LR
e1(e 创建)
e2(e set 注入 f代理)
f1(f 创建)
f2(f 创建)
f3(f 创建)
e1-->e2
e2--使用f方法-->f1
e2--使用f方法-->f2
e2--使用f方法-->f3
@Component
public class E {
@Autowired
@Lazy
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
// ...
}
注意
输出
E: setF(F f) class com.itheima.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.itheima.demo.cycle.F@3a6f2de3
F: F()
com.itheima.demo.cycle.F@56303b57
从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型
com.itheima.a08.sub 包
AOP 底层实现方式之一是代理,由代理结合通知和目标,提供增强功能
除此以外,aspectj 提供了两种另外的 AOP 底层实现:
简单比较的话:
代码参考项目 demo6_advanced_aspectj_01
注意
代码参考项目 demo6_advanced_aspectj_02
public class JdkProxyDemo {
interface Foo {
void foo();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Foo proxy = (Foo) Proxy.newProxyInstance(
Target.class.getClassLoader(), new Class[]{Foo.class},
(p, method, args) -> {
System.out.println("proxy before...");
Object result = method.invoke(target, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
运行结果
proxy before...
target foo
proxy after...
public class CglibProxyDemo {
static class Target {
public void foo() {
System.out.println("target foo");
}
}
public static void main(String[] param) {
// 目标对象
Target target = new Target();
// 代理对象
Target proxy = (Target) Enhancer.create(Target.class,
(MethodInterceptor) (p, method, args, methodProxy) -> {
System.out.println("proxy before...");
Object result = methodProxy.invoke(target, args);
// 另一种调用方法,不需要目标对象实例
// Object result = methodProxy.invokeSuper(p, args);
System.out.println("proxy after...");
return result;
});
// 调用代理
proxy.foo();
}
}
运行结果与 jdk 动态代理相同
public class A12 {
interface Foo {
void foo();
int bar();
}
static class Target implements Foo {
public void foo() {
System.out.println("target foo");
}
public int bar() {
System.out.println("target bar");
return 100;
}
}
public static void main(String[] param) {
// ⬇️1. 创建代理,这时传入 InvocationHandler
Foo proxy = new $Proxy0(new InvocationHandler() {
// ⬇️5. 进入 InvocationHandler
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
// ⬇️6. 功能增强
System.out.println("before...");
// ⬇️7. 反射调用目标方法
return method.invoke(new Target(), args);
}
});
// ⬇️2. 调用代理方法
proxy.foo();
proxy.bar();
}
}
模拟代理实现
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
// ⬇️这就是 jdk 代理类的源码, 秘密都在里面
public class $Proxy0 extends Proxy implements A12.Foo {
public $Proxy0(InvocationHandler h) {
super(h);
}
// ⬇️3. 进入代理方法
public void foo() {
try {
// ⬇️4. 回调 InvocationHandler
h.invoke(this, foo, new Object[0]);
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
@Override
public int bar() {
try {
Object result = h.invoke(this, bar, new Object[0]);
return (int) result;
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
static Method foo;
static Method bar;
static {
try {
foo = A12.Foo.class.getMethod("foo");
bar = A12.Foo.class.getMethod("bar");
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}
}
代理一点都不难,无非就是利用了多态、反射的知识
com.itheima.a12.TestMethodInvoke
注意 运行时请添加 –add-opens java.base/java.lang.reflect=ALL-UNNAMED –add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
com.itheima.a13 包
和 jdk 动态代理原理查不多
public class A14Application {
public static void main(String[] args) throws InvocationTargetException {
Target target = new Target();
Proxy proxy = new Proxy();
proxy.setCallbacks(new Callback[]{(MethodInterceptor) (p, m, a, mp) -> {
System.out.println("proxy before..." + mp.getSignature());
// ⬇️调用目标方法(三种)
// Object result = m.invoke(target, a); // ⬅️反射调用
// Object result = mp.invoke(target, a); // ⬅️非反射调用, 结合目标用
Object result = mp.invokeSuper(p, a); // ⬅️非反射调用, 结合代理用
System.out.println("proxy after..." + mp.getSignature());
return result;
}});
// ⬇️调用代理方法
proxy.save();
}
}
注意
com.itheima.a13.ProxyFastClass,com.itheima.a13.TargetFastClass
Spring 中对切点、通知、切面的抽象如下
classDiagram
class Advice
class MethodInterceptor
class Advisor
class PointcutAdvisor
Pointcut <|-- AspectJExpressionPointcut
Advice <|-- MethodInterceptor
Advisor <|-- PointcutAdvisor
PointcutAdvisor o-- "一" Pointcut
PointcutAdvisor o-- "一" Advice
<<interface>> Advice
<<interface>> MethodInterceptor
<<interface>> Pointcut
<<interface>> Advisor
<<interface>> PointcutAdvisor
代理相关类图
classDiagram
Advised <|-- ProxyFactory
ProxyFactory o-- Target
ProxyFactory o-- "多" Advisor
ProxyFactory --> AopProxyFactory : 使用
AopProxyFactory --> AopProxy
Advised <|-- 基于CGLIB的Proxy
基于CGLIB的Proxy <-- ObjenesisCglibAopProxy : 创建
AopProxy <|-- ObjenesisCglibAopProxy
AopProxy <|-- JdkDynamicAopProxy
基于JDK的Proxy <-- JdkDynamicAopProxy : 创建
Advised <|-- 基于JDK的Proxy
class AopProxy {
+getProxy() Object
}
class ProxyFactory {
proxyTargetClass : boolean
}
class ObjenesisCglibAopProxy {
advised : ProxyFactory
}
class JdkDynamicAopProxy {
advised : ProxyFactory
}
<<interface>> Advised
<<interface>> AopProxyFactory
<<interface>> AopProxy
com.itheima.a15.A15
注意
com.itheima.a16.A16
org.springframework.aop.framework.autoproxy 包
org.springframework.aop.framework.autoproxy.A17_1
org.springframework.aop.framework.autoproxy.A17_2
代理对象调用流程如下(以 JDK 动态代理实现为例)
图中不同颜色对应一次环绕通知或目标的调用起始至终结
sequenceDiagram
participant Proxy
participant ih as InvocationHandler
participant mi as MethodInvocation
participant Factory as ProxyFactory
participant mi1 as MethodInterceptor1
participant mi2 as MethodInterceptor2
participant Target
Proxy ->> +ih : invoke()
ih ->> +Factory : 获得 Target
Factory -->> -ih :
ih ->> +Factory : 获得 MethodInterceptor 链
Factory -->> -ih :
ih ->> +mi : 创建 mi
mi -->> -ih :
rect rgb(200, 223, 255)
ih ->> +mi : mi.proceed()
mi ->> +mi1 : invoke(mi)
mi1 ->> mi1 : 前增强
rect rgb(200, 190, 255)
mi1 ->> mi : mi.proceed()
mi ->> +mi2 : invoke(mi)
mi2 ->> mi2 : 前增强
rect rgb(150, 190, 155)
mi2 ->> mi : mi.proceed()
mi ->> +Target : mi.invokeJoinPoint()
Target ->> Target :
Target -->> -mi2 : 结果
end
mi2 ->> mi2 : 后增强
mi2 -->> -mi1 : 结果
end
mi1 ->> mi1 : 后增强
mi1 -->> -mi : 结果
mi -->> -ih :
end
ih -->> -Proxy :
org.springframework.aop.framework.A18
代理方法执行时会做如下工作
org.springframework.aop.framework.A18_1
MethodInvocation 的编程技巧在实现拦截器、过滤器时能用上
org.springframework.aop.framework.autoproxy.A19
RequestMappingHandlerMapping 与 RequestMappingHandlerAdapter 俩是一对,分别用来
com.itheima.a20 包
com.itheima.a20.TokenArgumentResolver ,com.itheima.a20.YmlReturnValueHandler
com.itheima.a21 包
com.itheima.a22.A22
classDiagram
Formatter --|> Printer
Formatter --|> Parser
class Converters {
Set~GenericConverter~
}
class Converter
class ConversionService
class FormattingConversionService
ConversionService <|-- FormattingConversionService
FormattingConversionService o-- Converters
Printer --> Adapter1
Adapter1 --> Converters
Parser --> Adapter2
Adapter2 --> Converters
Converter --> Adapter3
Adapter3 --> Converters
<<interface>> Formatter
<<interface>> Printer
<<interface>> Parser
<<interface>> Converter
<<interface>> ConversionService
classDiagram
PropertyEditorRegistry o-- "多" PropertyEditor
<<interface>> PropertyEditorRegistry
<<interface>> PropertyEditor
classDiagram
TypeConverter <|-- SimpleTypeConverter
TypeConverter <|-- BeanWrapperImpl
TypeConverter <|-- DirectFieldAccessor
TypeConverter <|-- ServletRequestDataBinder
SimpleTypeConverter --> TypeConverterDelegate
BeanWrapperImpl --> TypeConverterDelegate
DirectFieldAccessor --> TypeConverterDelegate
ServletRequestDataBinder --> TypeConverterDelegate
TypeConverterDelegate --> ConversionService
TypeConverterDelegate --> PropertyEditorRegistry
<<interface>> TypeConverter
<<interface>> ConversionService
<<interface>> PropertyEditorRegistry
com.itheima.a23 包
基本的类型转换与数据绑定用法
com.itheima.a23.TestServletDataBinderFactory
ServletRequestDataBinderFactory 的用法和扩展点
com.itheima.a23.sub 包
准备 @InitBinder 在整个 HandlerAdapter 调用过程中所处的位置
sequenceDiagram
participant adapter as HandlerAdapter
participant bf as WebDataBinderFactory
participant mf as ModelFactory
participant ihm as ServletInvocableHandlerMethod
participant ar as ArgumentResolvers
participant rh as ReturnValueHandlers
participant container as ModelAndViewContainer
rect rgb(200, 150, 255)
adapter ->> +bf: 准备 @InitBinder
bf -->> -adapter:
end
adapter ->> +mf: 准备 @ModelAttribute
mf ->> +container: 添加Model数据
container -->> -mf:
mf -->> -adapter:
adapter ->> +ihm: invokeAndHandle
ihm ->> +ar: 获取 args
ar ->> ar: 有的解析器涉及 RequestBodyAdvice
ar ->> container: 有的解析器涉及数据绑定生成Model数据
ar -->> -ihm: args
ihm ->> ihm: method.invoke(bean,args) 得到 returnValue
ihm ->> +rh: 处理 returnValue
rh ->> rh: 有的处理器涉及 ResponseBodyAdvice
rh ->> +container: 添加Model数据,处理视图名,是否渲染等
container -->> -rh:
rh -->> -ihm:
ihm -->> -adapter:
adapter ->> +container: 获取 ModelAndView
container -->> -adapter:
classDiagram
class ServletInvocableHandlerMethod {
+invokeAndHandle(ServletWebRequest,ModelAndViewContainer)
}
HandlerMethod <|-- ServletInvocableHandlerMethod
HandlerMethod o-- bean
HandlerMethod o-- method
ServletInvocableHandlerMethod o-- WebDataBinderFactory
ServletInvocableHandlerMethod o-- ParameterNameDiscoverer
ServletInvocableHandlerMethod o-- HandlerMethodArgumentResolverComposite
ServletInvocableHandlerMethod o-- HandlerMethodReturnValueHandlerComposite
HandlerMethod 需要
ServletInvocableHandlerMethod 需要
sequenceDiagram
participant adapter as RequestMappingHandlerAdapter
participant bf as WebDataBinderFactory
participant mf as ModelFactory
participant container as ModelAndViewContainer
adapter ->> +bf: 准备 @InitBinder
bf -->> -adapter:
adapter ->> +mf: 准备 @ModelAttribute
mf ->> +container: 添加Model数据
container -->> -mf:
mf -->> -adapter:
sequenceDiagram
participant adapter as RequestMappingHandlerAdapter
participant ihm as ServletInvocableHandlerMethod
participant ar as ArgumentResolvers
participant rh as ReturnValueHandlers
participant container as ModelAndViewContainer
adapter ->> +ihm: invokeAndHandle
ihm ->> +ar: 获取 args
ar ->> ar: 有的解析器涉及 RequestBodyAdvice
ar ->> container: 有的解析器涉及数据绑定生成模型数据
container -->> ar:
ar -->> -ihm: args
ihm ->> ihm: method.invoke(bean,args) 得到 returnValue
ihm ->> +rh: 处理 returnValue
rh ->> rh: 有的处理器涉及 ResponseBodyAdvice
rh ->> +container: 添加Model数据,处理视图名,是否渲染等
container -->> -rh:
rh -->> -ihm:
ihm -->> -adapter:
adapter ->> +container: 获取 ModelAndView
container -->> -adapter:
com.itheima.a26 包
准备 @ModelAttribute 在整个 HandlerAdapter 调用过程中所处的位置
sequenceDiagram
participant adapter as HandlerAdapter
participant bf as WebDataBinderFactory
participant mf as ModelFactory
participant ihm as ServletInvocableHandlerMethod
participant ar as ArgumentResolvers
participant rh as ReturnValueHandlers
participant container as ModelAndViewContainer
adapter ->> +bf: 准备 @InitBinder
bf -->> -adapter:
rect rgb(200, 150, 255)
adapter ->> +mf: 准备 @ModelAttribute
mf ->> +container: 添加Model数据
container -->> -mf:
mf -->> -adapter:
end
adapter ->> +ihm: invokeAndHandle
ihm ->> +ar: 获取 args
ar ->> ar: 有的解析器涉及 RequestBodyAdvice
ar ->> container: 有的解析器涉及数据绑定生成Model数据
ar -->> -ihm: args
ihm ->> ihm: method.invoke(bean,args) 得到 returnValue
ihm ->> +rh: 处理 returnValue
rh ->> rh: 有的处理器涉及 ResponseBodyAdvice
rh ->> +container: 添加Model数据,处理视图名,是否渲染等
container -->> -rh:
rh -->> -ihm:
ihm -->> -adapter:
adapter ->> +container: 获取 ModelAndView
container -->> -adapter:
com.itheima.a27 包
com.itheima.a28.A28
com.itheima.a29 包
ResponseBodyAdvice 增强 在整个 HandlerAdapter 调用过程中所处的位置
sequenceDiagram
participant adapter as HandlerAdapter
participant bf as WebDataBinderFactory
participant mf as ModelFactory
participant ihm as ServletInvocableHandlerMethod
participant ar as ArgumentResolvers
participant rh as ReturnValueHandlers
participant container as ModelAndViewContainer
adapter ->> +bf: 准备 @InitBinder
bf -->> -adapter:
adapter ->> +mf: 准备 @ModelAttribute
mf ->> +container: 添加Model数据
container -->> -mf:
mf -->> -adapter:
adapter ->> +ihm: invokeAndHandle
ihm ->> +ar: 获取 args
ar ->> ar: 有的解析器涉及 RequestBodyAdvice
ar ->> container: 有的解析器涉及数据绑定生成Model数据
ar -->> -ihm: args
ihm ->> ihm: method.invoke(bean,args) 得到 returnValue
ihm ->> +rh: 处理 returnValue
rect rgb(200, 150, 255)
rh ->> rh: 有的处理器涉及 ResponseBodyAdvice
end
rh ->> +container: 添加Model数据,处理视图名,是否渲染等
container -->> -rh:
rh -->> -ihm:
ihm -->> -adapter:
adapter ->> +container: 获取 ModelAndView
container -->> -adapter:
com.itheima.a30.A30
com.itheima.a31 包
/error
也可以通过 ${server.error.path}
进行配置/error
这个地址/error
,所以处理异常的职责就又回到了 Spring评价
@Bean // ⬅️修改了 Tomcat 服务器默认错误地址, 出错时使用请求转发方式跳转
public ErrorPageRegistrar errorPageRegistrar() {
return webServerFactory -> webServerFactory.addErrorPages(new ErrorPage("/error"));
}
@Bean // ⬅️TomcatServletWebServerFactory 初始化前用它增强, 注册所有 ErrorPageRegistrar
public ErrorPageRegistrarBeanPostProcessor errorPageRegistrarBeanPostProcessor() {
return new ErrorPageRegistrarBeanPostProcessor();
}
@Bean // ⬅️ErrorProperties 封装环境键值, ErrorAttributes 控制有哪些错误信息
public BasicErrorController basicErrorController() {
ErrorProperties errorProperties = new ErrorProperties();
errorProperties.setIncludeException(true);
return new BasicErrorController(new DefaultErrorAttributes(), errorProperties);
}
@Bean // ⬅️名称为 error 的视图, 作为 BasicErrorController 的 text/html 响应结果
public View error() {
return new View() {
@Override
public void render(
Map<String, ?> model,
HttpServletRequest request,
HttpServletResponse response
) throws Exception {
System.out.println(model);
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("""
<h3>服务器内部错误</h3>
""");
}
};
}
@Bean // ⬅️收集容器中所有 View 对象, bean 的名字作为视图名
public ViewResolver viewResolver() {
return new BeanNameViewResolver();
}
@Bean
public BeanNameUrlHandlerMapping beanNameUrlHandlerMapping() {
return new BeanNameUrlHandlerMapping();
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
@Bean("/c3")
public Controller controller3() {
return (request, response) -> {
response.getWriter().print("this is c3");
return null;
};
}
@Bean
public RouterFunctionMapping routerFunctionMapping() {
return new RouterFunctionMapping();
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
return new HandlerFunctionAdapter();
}
@Bean
public RouterFunction<ServerResponse> r1() {
// ⬇️映射条件 ⬇️handler
return route(GET("/r1"), request -> ok().body("this is r1"));
}
org.springframework.boot.autoconfigure.web.servlet.A35
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ApplicationContext context) {
SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
Map<String, ResourceHttpRequestHandler> map
= context.getBeansOfType(ResourceHttpRequestHandler.class);
handlerMapping.setUrlMap(map);
return handlerMapping;
}
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("static/")));
return handler;
}
@Bean("/img/**")
public ResourceHttpRequestHandler handler2() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("images/")));
return handler;
}
@Bean("/**")
public ResourceHttpRequestHandler handler1() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
handler.setLocations(List.of(new ClassPathResource("static/")));
handler.setResourceResolvers(List.of(
// ⬇️缓存优化
new CachingResourceResolver(new ConcurrentMapCache("cache1")),
// ⬇️压缩优化
new EncodedResourceResolver(),
// ⬇️原始资源解析
new PathResourceResolver()
));
return handler;
}
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext context) {
Resource resource = context.getResource("classpath:static/index.html");
return new WelcomePageHandlerMapping(null, context, resource, "/**");
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
当浏览器发送一个请求 http://localhost:8080/hello
后,请求到达服务器,其处理流程是:
/
,即会匹配到所有请求 URL,可作为请求的统一入口,也被称之为前控制器如果是 linux 环境,用以下命令即可获取 spring boot 的骨架 pom.xml
curl -G https://start.spring.io/pom.xml -d dependencies=web,mysql,mybatis -o pom.xml
也可以使用 Postman 等工具实现
若想获取更多用法,请参考
curl https://start.spring.io
步骤1:创建模块,区别在于打包方式选择 war
接下来勾选 Spring Web 支持
步骤2:编写控制器
@Controller
public class MyController {
@RequestMapping("/hello")
public String abc() {
System.out.println("进入了控制器");
return "hello";
}
}
步骤3:编写 jsp 视图,新建 webapp 目录和一个 hello.jsp 文件,注意文件名与控制器方法返回的视图逻辑名一致
src
|- main
|- java
|- resources
|- webapp
|- hello.jsp
步骤4:配置视图路径,打开 application.properties 文件
spring.mvc.view.prefix=/
spring.mvc.view.suffix=.jsp
将来 prefix + 控制器方法返回值 + suffix 即为视图完整路径
如果用 mvn 插件 mvn spring-boot:run
或 main 方法测试
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
也可以使用 Idea 配置 tomcat 来测试,此时用的是外置 tomcat
对于 jar 项目,若要支持 jsp,也可以在加入 jasper 依赖的前提下,把 jsp 文件置入 META-INF/resources
阶段一:SpringApplication 构造
阶段二:执行 run 方法
带 * 的有独立的示例
com.itheima.a39.A39_1 对应 SpringApplication 构造
com.itheima.a39.A39_2 对应第1步,并演示 7 个事件
com.itheima.a39.A39_3 对应第2、8到12步
org.springframework.boot.Step3
org.springframework.boot.Step4
org.springframework.boot.Step5
org.springframework.boot.Step6
org.springframework.boot.Step7
Tomcat 基本结构
Server
└───Service
├───Connector (协议, 端口)
└───Engine
└───Host(虚拟主机 localhost)
├───Context1 (应用1, 可以设置虚拟路径, / 即 url 起始路径; 项目磁盘路径, 即 docBase )
│ │ index.html
│ └───WEB-INF
│ │ web.xml (servlet, filter, listener) 3.0
│ ├───classes (servlet, controller, service ...)
│ ├───jsp
│ └───lib (第三方 jar 包)
└───Context2 (应用2)
│ index.html
└───WEB-INF
web.xml
public static void main(String[] args) throws LifecycleException, IOException {
// 1.创建 Tomcat 对象
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("tomcat");
// 2.创建项目文件夹, 即 docBase 文件夹
File docBase = Files.createTempDirectory("boot.").toFile();
docBase.deleteOnExit();
// 3.创建 Tomcat 项目, 在 Tomcat 中称为 Context
Context context = tomcat.addContext("", docBase.getAbsolutePath());
// 4.编程添加 Servlet
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
HelloServlet helloServlet = new HelloServlet();
ctx.addServlet("aaa", helloServlet).addMapping("/hello");
}
}, Collections.emptySet());
// 5.启动 Tomcat
tomcat.start();
// 6.创建连接器, 设置监听端口
Connector connector = new Connector(new Http11Nio2Protocol());
connector.setPort(8080);
tomcat.setConnector(connector);
}
WebApplicationContext springContext = getApplicationContext();
// 4.编程添加 Servlet
context.addServletContainerInitializer(new ServletContainerInitializer() {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
// ⬇️通过 ServletRegistrationBean 添加 DispatcherServlet 等
for (ServletRegistrationBean registrationBean :
springContext.getBeansOfType(ServletRegistrationBean.class).values()) {
registrationBean.onStartup(ctx);
}
}
}, Collections.emptySet());
Spring Boot 是利用了自动配置类来简化了 aop 相关配置
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
spring.aop.auto=false
禁用 aop 自动配置@EnableAspectJAutoProxy
来开启了自动代理,如果在引导类上自己添加了 @EnableAspectJAutoProxy
那么以自己添加的为准@EnableAspectJAutoProxy
的本质是向容器中添加了 AnnotationAwareAspectJAutoProxyCreator
这个 bean 后处理器,它能够找到容器中所有切面,并为匹配切点的目标类创建代理,创建代理的工作一般是在 bean 的初始化阶段完成的简单说明一下,Spring Boot 支持两大类数据源:
PooledDataSource 又支持如下数据源
如果知道数据源的实现类类型,即指定了 spring.datasource.type
,理论上可以支持所有数据源,但这样做的一个最大问题是无法订制每种数据源的详细配置(如最大、最小连接数等)
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
mybatis.
前缀的配置项进行定制配置@MapperScan 注解的作用与 MybatisAutoConfiguration 类似,会注册 MapperScannerConfigurer 有如下区别
这里有同学有疑问,之前介绍的都是将具体类交给 Spring 管理,怎么到了 MyBatis 这儿,接口就可以被管理呢?
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
假设已有第三方的两个自动配置类
@Configuration // ⬅️第三方的配置类
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}
@Configuration // ⬅️第三方的配置类
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
提供一个配置文件 META-INF/spring.factories,key 为导入器类名,值为多个自动配置类名,用逗号分隔
MyImportSelector=\
AutoConfiguration1,\
AutoConfiguration2
注意
引入自动配置
@Configuration // ⬅️本项目的配置类
@Import(MyImportSelector.class)
static class Config { }
static class MyImportSelector implements DeferredImportSelector {
// ⬇️该方法从 META-INF/spring.factories 读取自动配置类名,返回的 String[] 即为要导入的配置类
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return SpringFactoriesLoader
.loadFactoryNames(MyImportSelector.class, null).toArray(new String[0]);
}
}
条件装配的底层是本质上是 @Conditional 与 Condition,这两个注解。引入自动配置类时,期望满足一定条件才能被 Spring 管理,不满足则不管理,怎么做呢?
比如条件是【类路径下必须有 dataSource】这个 bean ,怎么做呢?
首先编写条件判断类,它实现 Condition 接口,编写条件判断逻辑
static class MyCondition1 implements Condition {
// ⬇️如果存在 Druid 依赖,条件成立
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ClassUtils.isPresent("com.alibaba.druid.pool.DruidDataSource", null);
}
}
其次,在要导入的自动配置类上添加 @Conditional(MyCondition1.class)
,将来此类被导入时就会做条件检查
@Configuration // 第三方的配置类
@Conditional(MyCondition1.class) // ⬅️加入条件
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}
分别测试加入和去除 druid 依赖,观察 bean1 是否存在于容器
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
com.itheima.a43 包
真实项目中,只需要加入以下依赖即可
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<optional>true</optional>
</dependency>
com.itheima.a44 包
com.itheima.a45 包
com.itheima.a46 包
com.itheima.a47 包
com.itheima.a48 包
事件监听器的两种方式
com.itheima.a49 包
事件发布器模拟实现