看视频记笔记,https://www.bilibili.com/video/av32102436?p=3&t=464
加载配置类
传统 XML 方法中,您可使用 ClassPathXmlApplicationContext 类来加载外部 XML 上下文文件。 但在使用基于 Java 的配置时,有一个 AnnotationConfigApplicationContext 类。
@Conditional
@Conditional是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
- 作用:根据条件,决定类是否加载到Spring Ioc容器中,在SpringBoot中有大量的运用
- 应用场景:在一些需要条件满足才是实例化的类中,使用此注解,我曾经在项目中需要根据不同的场景使用不同的mq中间件的时候使用过,在mq的实例化bean上,加上此注解,根据配置文件的不同,来决定这个bean是否加载至ioc容器中。
https://blog.csdn.net/xcy1193068639/article/details/81491071,这篇笔记跟视频的一样
//满足LsCondition.class的条件就创建次bean,否则不创建
@Conditional(LsCondition.class)
@Bean("lisi")
public Person person1() {
return new Person("lisi", 18);
}
LsCondition得实现Condition接口的boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)方法。
标注在方法上
一个方法只能注入一个bean实例,所以@Conditional标注在方法上只能控制一个bean实例是否注入。
一个类中可以注入很多实例,@Conditional标注在类上就决定了一批bean是否注入。
@import
看看这篇可以 https://www.cnblogs.com/yichunguo/p/12122598.html
可以用来把bean快速加入IOC容器中,bean的名字是全类名
源码
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
可以看出支持三种方法进行导入
@Import的三种导入方法
方法一:直接填class数组方式
public class Color {
}
多个bean需要快速导入,加逗号分割开来
@Import(Color.class)
public class MainConfig {
}
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalRequiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
com.yhy.bean.Color
但是这种方式有一些问题,那就是只能使用类的无参构造方法来创建bean,对于有参数的构造方法且不提供无惨构造方法就无能为力了
将color类改成下面这样,在运行会报错
public class Color {
private String red;
public Color(String red) {
this.red = red;
}
}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.yhy.bean.Color':
Unsatisfied dependency expressed through constructor parameter 0;
nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate.
Dependency annotations: {}
方法二:实现ImportSelector
可以一次性返回许多需要导入的bean(组件)
1.定义两个需要导入的bean
public class ImportSelectorA {
}
public class ImportSelectorB {
}
2.实现此接口,返回上面两个类的全类名
AnnotationMetadata是@Import所在类的元数据信息,可以拿到类名,和所在类的所有注解......等等信息。
public class MySelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//返回需要导入的类的全类名
return new String[]{"com.yhy.bean.ImportSelectorA","com.yhy.bean.ImportSelectorB"};
}
}
3.在配置类上导入
@Configuration
@Import({ImportColor.class, MySelector.class})
public class MainConfig {
}
4.测试
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
com.yhy.bean.ImportColor
com.yhy.bean.ImportSelectorA
com.yhy.bean.ImportSelectorB
方法三:实现ImportBeanDefinitionRegistrar
Spring官方在动态注册bean时,大部分套路其实是使用ImportBeanDefinitionRegistrar接口。
1.定义一个需要注入的bean
public class ImportBeanDefinitionRegistrarA {
}
2.实现该接口的方法,进行动态注册bean
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//RootBeanDefiniiton保存了以下信息
//定义了id、别名与Bean的对应关系(BeanDefinitionHolder)
//Bean的注解(AnnotatedElement)
//具体的工厂方法(Class类型),包括工厂方法的返回类型,工厂方法的Method对象
//构造函数、构造函数形参类型
// Bean的class对象
RootBeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarA.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("ImportBeanDefinitionRegistrarA", beanDefinition);
}
}
3.在 配置类上的Import参数写上实现接口的类
@Configuration
@Import({ImportColor.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MainConfig {}
4.测试
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
ImportBeanDefinitionRegistrarA
FactoryBean
https://www.jianshu.com/p/d37737e823dc
https://www.cnblogs.com/tiancai/p/9604040.html
一个能生产对象的工厂Bean,可以用来代理一个对象,对该对象的所有方法做一个拦截。
在该接口中还定义了以下3个方法。
- T getObject():返回由FactoryBean创建的bean实例,如果isSingleton()返回true,则该实例会放到Spring容器中单实例缓存池中。
- boolean isSingleton():返回由FactoryBean创建的bean实例的作用域是singleton还是prototype。
- Class<T> getObjectType():返回FactoryBean创建的bean类型。
1.实现该接口
public class PersonFactoryBean implements FactoryBean<Person> {
//返回一个Person对象,这个对象会添加到容器中
@Override
public Person getObject() throws Exception {
System.out.println("PersonFactoryBean。。。。。。getObject()");
return new Person();
}
@Override
public Class<?> getObjectType() {
return Person.class;
}
//设置是否是单例
@Override
public boolean isSingleton() {
return true;
}
}
2.配置类中添加该bean
@Configuration
public class MainConfig {
@Bean
public PersonFactoryBean personFactoryBean(){
return new PersonFactoryBean();
}
}
3.测试
public class IOCTest {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
@Test
public void testFactoryBean() {
printBeans();
Object p1 = context.getBean("personFactoryBean");
Object p2 = context.getBean("personFactoryBean");
System.out.println("personFactoryBean类型:"+p1.getClass());
//单例的,所以是相同的
System.out.println(p1==p2);
}
@Test
public void printBeans() {
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
结果
personFactoryBean
PersonFactoryBean。。。。。。getObject()
personFactoryBean类型:class com.yhy.bean.Person
true
实现FactoryBean<T>接口的Bean,根据该Bean的ID从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。
Bean生命周期
初始化和销毁方法
1.@Bean指定initMethod,destroyMethod
1.声明一个类的初始化方法和销毁方法(但还没指定)
public class Car {
public Car(){
System.out.println("car constructor...");
}
public void init(){
System.out.println("car ... init...");
}
public void detory(){
System.out.println("car ... detory...");
}
}
2.在配置类中bean注解指定
@Configuration
public class MainConfigLifeCycle {
@Bean(initMethod = "init", destroyMethod = "detory")
public Car car() {
return new Car();
}
}
3.测试类
public class IOCTestCycle {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
@Test
public void test() {
Car car = (Car)context.getBean("car");
context.close();
}
}
三月 19, 2020 11:48:23 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5680a178: startup date [Thu Mar 19 11:48:23 CST 2020]; root of context hierarchy
car constructor...
car ... init...
三月 19, 2020 11:48:23 上午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5680a178: startup date [Thu Mar 19 11:48:23 CST 2020]; root of context hierarchy
car ... detory...
如果没close容器,是不会执行销毁方法的。
2.实现InitializingBean和DisposableBean接口
InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法,就是在bean属性被设置完后。
1.实现初始化和销毁接口
@Data
public class Cat implements InitializingBean, DisposableBean {
private String name;
@Override
public void destroy() throws Exception {
System.out.println("销毁Cat。。。。");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Cat属性已经初始化完毕。。。。");
System.out.println("name:"+name);
}
}
2.添加到配置类
@Configuration
public class MainConfigLifeCycle {
@Bean
public Cat cat() {
System.out.println("构造cat");
Cat cat = new Cat();
cat.setName("Tom");
return cat;
}
}
3.注册
public class IOCTestCycle {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
@Test
public void test() {
Cat cat = (Cat)context.getBean("cat");
context.close();
}
}
构造cat
Cat属性已经初始化完毕。。。。
name:Tom
销毁Cat。。。。
总结:
1、Spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中通过init-method指定,两种方式可以同时使用。
2、实现InitializingBean接口是直接调用afterPropertiesSet方法,比通过反射调用init-method指定的方法效率要高一点,但是init-method方式消除了对spring的依赖。
3、如果调用afterPropertiesSet方法时出错,则不调用init-method指定的方法。
3.@PostConstruct和@PreDestroy
javaee5规范的注解
在需要初始化的方法上标上注解,也是在对象创建并赋值之后调用
public class Dog {
@PreDestroy
public void destroy() {
System.out.println("销毁Dog。。。。");
}
@PostConstruct
public void init() {
System.out.println("Dog属性已经初始化完毕。。。。");
}
}
@Configuration
public class MainConfigLifeCycle {
@Bean
public Dog dog() {
System.out.println("构造Dog");
return new Dog();
}
}
public class IOCTestCycle {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
@Test
public void test() {
context.getBean("dog");
context.close();
}
}
三月 19, 2020 4:48:27 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5680a178: startup date [Thu Mar 19 16:48:27 CST 2020]; root of context hierarchy
构造Dog
Dog属性已经初始化完毕。。。。
三月 19, 2020 4:48:28 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5680a178: startup date [Thu Mar 19 16:48:27 CST 2020]; root of context hierarchy
销毁Dog。。。。
Bean初始化前后处理
BeanPostProcessor
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
所有bean都会执行实现此接口方法
- 后置处理器的postProcessorBeforeInitailization方法是在bean实例化,依赖注入之后及自定义初始化方法(例如:配置文件中bean标签添加init-method属性指定Java类中初始化方法、 @PostConstruct注解指定初始化方法,Java类实现InitailztingBean接口)之前调用
- 后置处理器的postProcessorAfterInitailization方法是在bean实例化、依赖注入及自定义初始化方法之后调用
1.配置类写好,记得扫描包,否则bean不会加到容器中
@ComponentScan("com.yhy.bean")
@Configuration
public class MainConfigLifeCycle {
}
2.bean
@Component
public class Dog{
public Dog() {
System.out.println("构造dog");
}
@PreDestroy
public void destroy() {
System.out.println("@PreDestroy:销毁Dog。。。。");
}
@PostConstruct
public void init() {
System.out.println("@PostConstruct:Dog属性已经初始化完毕。。。。");
}
}
3.加注解将后置处理器加入到容器中
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("前置处理器:"+bean+"="+beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("后置处理器:"+bean+"="+beanName);
return bean;
}
}
4.测试
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
System.out.println(context.getBean("dog"));
context.close();
}
前置处理器:org.springframework.context.event.EventListenerMethodProcessor@1e9e725a=org.springframework.context.event.internalEventListenerProcessor
后置处理器:org.springframework.context.event.EventListenerMethodProcessor@1e9e725a=org.springframework.context.event.internalEventListenerProcessor
前置处理器:org.springframework.context.event.DefaultEventListenerFactory@473b46c3=org.springframework.context.event.internalEventListenerFactory
后置处理器:org.springframework.context.event.DefaultEventListenerFactory@473b46c3=org.springframework.context.event.internalEventListenerFactory
前置处理器:com.yhy.config.MainConfigLifeCycle$$EnhancerBySpringCGLIB$$38866d28@516be40f=mainConfigLifeCycle
后置处理器:com.yhy.config.MainConfigLifeCycle$$EnhancerBySpringCGLIB$$38866d28@516be40f=mainConfigLifeCycle
构造dog
前置处理器:com.yhy.bean.Dog@62bd765=dog
@PostConstruct:Dog属性已经初始化完毕。。。。
后置处理器:com.yhy.bean.Dog@62bd765=dog
com.yhy.bean.Dog@62bd765
@PreDestroy:销毁Dog。。。。
可以看出,是在初始化方法前后对bean进行处理
ApplicationContextAware:获取Spring容器
实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来
看了下其他博客,基本是用来获取容器,封装成一个工具类
1.实现该接口,加入容器
@Component
public class SpringContextHolder implements ApplicationContextAware {
static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context=applicationContext;
}
public static ApplicationContext getContext() {
return context;
}
}
2.测试
@Test
public void test2() {
AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(MainConfigLifeCycle.class);
ApplicationContext context = SpringContextHolder.getContext();
System.out.println(annotationConfigApplicationContext);
System.out.println(context);
}
org.springframework.context.annotation.AnnotationConfigApplicationContext@48cf768c: startup date [Sat Mar 21 20:48:24 CST 2020]; root of context hierarchy
org.springframework.context.annotation.AnnotationConfigApplicationContext@48cf768c: startup date [Sat Mar 21 20:48:24 CST 2020]; root of context hierarchy
可以看出获取的是当前上下文。
AOP
切面使用
1.声明一个需要被代理(增强)的对象
@Component
public class MathCalculator {
//连接点
public int div(int i,int j){
System.out.println("MathCalculator...div...");
return i/j;
}
}
2.声明一个切面类
@AfterReturning returning 说明:
- returing属性所指定的形参名必须对应增强处理中的一个形参名,当目标方法执行返回
- 返回值作为相应的参数值传入增强处理方法中
- 虽然AfterReturning增强处理可以访问到目标方法的返回值,但它不可以改变目标方法的返回值。
/**
* @Aspect: 告诉Spring当前类是一个切面类
*/
@Aspect
@Component
public class LogAspects {
@Pointcut("execution( int com.yhy.aop.MathCalculator.*(..))")
public void pointCut(){
}
//@Before在目标方法之前切入;切入点表达式(指定在哪个方法切入)
@Before("pointCut()")
public void logStart(JoinPoint joinPoint){
//获取切入点的方法参数
Object[] args = joinPoint.getArgs();
//获取切入点的方法名
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName+"运行@Before,参数为"+ Arrays.asList(args));
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName+"运行@After,参数为"+Arrays.asList(args));
}
//JoinPoint一定要出现在参数表的第一位
@AfterReturning(value="pointCut()",returning="result")
public void logReturn(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName +"正常返回@AfterReturning:运行结果:{"+result+"}");
}
//throwing= " exception":告诉Spring哪个参数是用来接收异常,上面returning也是类似
@AfterThrowing(value="pointCut()",throwing="exception")
public void logException(JoinPoint joinPoint,Exception exception){
String methodName = joinPoint.getSignature().getName();
System.out.println(methodName+"异常信息@AfterThrowing:"+exception);
}
}
3.配置类,记得开启注解
@EnableAspectJAutoProxy表示开启AOP代理自动配置,如果配@EnableAspectJAutoProxy表示使用cglib进行代理对象的生成
@EnableAspectJAutoProxy
@Configuration
@ComponentScan("com.yhy.aop")
public class MainConfigAop {
}
4.测试
public class IOCTestAop {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigAop.class);
@Test
public void test1() {
MathCalculator mathCalculator = context.getBean(MathCalculator.class);
mathCalculator.div(5,1);
}
}
div运行@Before,参数为[5, 1]
MathCalculator...div...
div运行@After,参数为[5, 1]
div正常返回@AfterReturning:运行结果:{5}
事务管理器
1.记得开启事务注解
@EnableTransactionManagement
@ComponentScan("com.atguigu.tx")
@Configuration
public class TxConfig {
//数据源
@Bean
public DataSource dataSource() throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser("root");
dataSource.setPassword("123456");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
return dataSource;
}
//
@Bean
public JdbcTemplate jdbcTemplate() throws Exception{
//Spring对@Configuration类会特殊处理;给容器中加组件的方法,多次调用都只是从容器中找组件
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
return jdbcTemplate;
}
//注册事务管理器在容器中
@Bean
public PlatformTransactionManager transactionManager() throws Exception{
return new DataSourceTransactionManager(dataSource());
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional(rollbackFor = Exception.class)
public void insertUser(){
userDao.insert();
//otherDao.other();xxx
System.out.println("插入完成...");
int i = 10/0;
}
}
在项目中,@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚
Servlet3.0新增特性ServletContainerInitializer的使用
- Servlet容器启动会扫描当前应用里面每一个jar包的ServletContainerInitializer实现。
- 实现类必须绑定在META-INF/services/javax.servlet.ServletContainerInitializer的文件中,文件的内容就是实现类的全类名。
- 应用启动的时候,会运行实现ServletContainerInitializer里的onStartup方法;
- Set<Class<?>> c:感兴趣的类型的所有子类型;
- ServletContext ctx:代表当前Web应用的ServletContext;
- @HandlesTypes:容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递给ServletContainerInitializer实现类。
一个Web应用一个ServletContext;
- 使用ServletContext注册Web组件(Servlet、Filter、Listener)
- 使用编码的方式,在项目启动的时候给ServletContext里面添加组件,不需要在web.xml中配置,如果做框架就省去了配置web.xml的步骤,就是使用注解+java配置方式。
必须在项目启动的时候来添加;
- ServletContainerInitializer得到的ServletContext;
public interface AService {
}
public class AbstractAService implements AService {
}
public class AServiceImpl implements AService {
}
public interface ChildInterfaceAService extends AService {
}
@HandlesTypes(value = {AService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
for (Class<?> aClass : c) {
System.out.println(aClass);
}
}
}
interface service.ChildInterfaceAService
class service.AbstractAService
class service.AServiceImpl
使用java方式配置mvc:代替xml
springmvc源码解析之配置加载SpringServletContainerInitializer
1.service,controller类
@Service
public class HelloService {
public String sayHello(String name){
return "Hello "+name;
}
}
@Controller
public class HelloController {
@Autowired
HelloService helloService;
@ResponseBody
@RequestMapping("/hello")
public String hello(){
String hello = helloService.sayHello("tomcat..");
return hello;
}
@RequestMapping("/suc")
public String success(){
return "success";
}
}
2.配置类,只加载service,dao层的中间服务,就是后台处理的类
@Configuration
@ComponentScan(value = "com.yhy", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)})
public class RootConfig {
}
配置只加载前端控制器的类
@Configuration
@ComponentScan(value = "com.yhy", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)},useDefaultFilters=false)
public class ServletConfig {
}
3.实现web初始化类,它自动被加载,负责应用程序中 servlet 上下文中的 DispatcherServlet 和 Spring 其他上下文的配置。
/**
* web容器启动的时候创建对象;调用方法来初始化容器以前前端控制器
*/
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 获取根容器的配置类;(Spring的配置文件) 父容器;
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 获取web容器的配置类(SpringMVC配置文件) 子容器;
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{ServletConfig.class};
}
/**
* 获取DispatcherServlet的映射信息
* /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
* /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
访问:http://localhost:8080/hello
输出:Hello tomcat..
这样就完成了无xml配置,启动springmv。
- 上面讲到,servlet3.0以后,容器启动会在classpath 下自动寻找META-INF.services目录下的类,所以spring会利用java SPI 技术找到 ServletContainerInitializer的实现类并且将 @HandlesTypes 感兴趣的类型进行实例化注入,就是注册过滤器,监听器,servlet啥的;
- 发现SpringServletContainerInitializer是唯一的实现类,就开始执行感兴趣类的方法onStartup();
- 在MyServletContainerInitializer的onStartup()方法中找到所有的MyWebAppInitializer接口的实现类
- 然后调用MyWebAppInitializer接口实现类的loadOnStart()方法
- 这样,其他类只要实现了MyWebAppInitializer接口实现类的loadOnStart()方法,都会随着web容器启动被依次调用
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//形参webAppInitializerClasses,就是注解里WebApplicationInitializer实现,抽象,继承的类
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
//存放实现WebApplicationInitializer的类
List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
//循环感兴趣的类,不为空且是WebApplicationInitializer子类实现啊啥的就添加到集合
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer) waiClass.newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
//如果为空就打印信息
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
//对集合排序
AnnotationAwareOrderComparator.sort(initializers);
//遍历集合依次执行onStartup方法。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
异步请求
servlet3.0 使用方法
//1、开启支持异步处理asyncSupported=true
@WebServlet(value = "/async", asyncSupported = true)
public class AsyncFilter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("主线程开始。。。" + Thread.currentThread() + "==>" + LocalDateTime.now());
//2、开始异步请求
AsyncContext asyncContext = req.startAsync();
asyncContext.start(new Runnable() {
@Override
public void run() {
System.out.println("副线程开始。。。"+Thread.currentThread()+"==>"+LocalDateTime.now());
say();
//3.表示结束任务
asyncContext.complete();
System.out.println("副线程结束。。。"+Thread.currentThread()+"==>"+LocalDateTime.now());
}
});
System.out.println("主线程结束。。。"+Thread.currentThread()+"==>"+LocalDateTime.now());
}
private void say() {
System.out.println(Thread.currentThread() + " hello");
}
}
主线程开始。。。Thread[http-nio-8080-exec-9,5,main]==>2020-03-24T13:18:28.215
主线程结束。。。Thread[http-nio-8080-exec-9,5,main]==>2020-03-24T13:18:28.222
副线程开始。。。Thread[http-nio-8080-exec-10,5,main]==>2020-03-24T13:18:28.222
Thread[http-nio-8080-exec-10,5,main] hello
副线程结束。。。Thread[http-nio-8080-exec-10,5,main]==>2020-03-24T13:18:28.222
控制台输出,可以看出确实是新线程在做事情。
SpringMvc 使用方法
方式一:Callable
1.拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("拦截前。。。。。。。。。"+request.getRequestURL());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("拦截后。。。。。。。。。");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2.记得开启EnableWebMvc注解,才可以使用springmvc的功能 ,注册拦截器
@Configuration
@ComponentScan(value = "com.yhy", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)},useDefaultFilters=false)
@EnableWebMvc
public class ServletConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");
}
}
3.异步请求
@Controller
public class AsyncController {
@ResponseBody
@RequestMapping("/async")
public Callable<String> async() {
System.out.println("主线程开始..." + Thread.currentThread() + "==>" + LocalDateTime.now());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + LocalDateTime.now());
Thread.sleep(2000);
System.out.println("副线程开始..." + Thread.currentThread() + "==>" + LocalDateTime.now());
return "Callable<String> 异步请求结果返回成功";
}
};
System.out.println("主线程结束..." + Thread.currentThread() + "==>" + LocalDateTime.now());
return callable;
}
}
拦截前。。。。。。。。。http://localhost:8080/async
主线程开始...Thread[http-nio-8080-exec-5,5,main]==>2020-03-24T18:31:44.683
主线程结束...Thread[http-nio-8080-exec-5,5,main]==>2020-03-24T18:31:44.689
副线程开始...Thread[MvcAsync1,5,main]==>2020-03-24T18:31:44.713
副线程开始...Thread[MvcAsync1,5,main]==>2020-03-24T18:31:46.714
拦截前。。。。。。。。。http://localhost:8080/async
拦截后。。。。。。。。。
现有以下结论
- 控制器返回Callable
- Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
- DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
- Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
- 根据Callable返回的结果。SpringMVC继续进行视图渲染流程等(从收请求-视图渲染)。
所以结果可以看成三部分
1.第一次请求,进入到拦截器,在进入请求主线程方法
拦截前。。。。。。。。。http://localhost:8080/async
主线程开始...Thread[http-nio-8080-exec-5,5,main]==>2020-03-24T18:31:44.683
主线程结束...Thread[http-nio-8080-exec-5,5,main]==>2020-03-24T18:31:44.689
=========DispatcherServlet及所有的Filter退出线程============================
2.副线程执行任务
================等待Callable执行==========
副线程开始...Thread[MvcAsync1,5,main]==>2020-03-24T18:31:44.713
副线程开始...Thread[MvcAsync1,5,main]==>2020-03-24T18:31:46.714
================Callable执行完成==========
3.副线程执行任务完毕,再重新发送请求到容器
================再次收到之前重发过来的请求========
拦截前。。。。。。。。。http://localhost:8080/async
拦截后。。。。。。。。。
所以会拦截两次