Spring注解版使用笔记

看视频记笔记,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都会执行实现此接口方法

  1. 后置处理器的postProcessorBeforeInitailization方法是在bean实例化,依赖注入之后及自定义初始化方法(例如:配置文件中bean标签添加init-method属性指定Java类中初始化方法、 @PostConstruct注解指定初始化方法,Java类实现InitailztingBean接口)之前调用                 
  2. 后置处理器的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 说明:

  1. returing属性所指定的形参名必须对应增强处理中的一个形参名,当目标方法执行返回
  2. 返回值作为相应的参数值传入增强处理方法中
  3. 虽然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

Spring 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。

  1. 上面讲到,servlet3.0以后,容器启动会在classpath 下自动寻找META-INF.services目录下的类,所以spring会利用java SPI 技术找到 ServletContainerInitializer的实现类并且将 @HandlesTypes 感兴趣的类型进行实例化注入,就是注册过滤器,监听器,servlet啥的;  
  2. 发现SpringServletContainerInitializer是唯一的实现类,就开始执行感兴趣类的方法onStartup();
  3. 在MyServletContainerInitializer的onStartup()方法中找到所有的MyWebAppInitializer接口的实现类
  4. 然后调用MyWebAppInitializer接口实现类的loadOnStart()方法
  5. 这样,其他类只要实现了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");
    }
}

访问http://localhost:8080/async

主线程开始。。。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
拦截后。。。。。。。。。

现有以下结论

  1. 控制器返回Callable
  2. Spring异步处理,将Callable 提交到 TaskExecutor 使用一个隔离的线程进行执行
  3. DispatcherServlet和所有的Filter退出web容器的线程,但是response 保持打开状态;
  4.  Callable返回结果,SpringMVC将请求重新派发给容器,恢复之前的处理;
  5. 根据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
   拦截后。。。。。。。。。
   

所以会拦截两次

方式二:DeferredResult

DeferredResult的使用场景及用法

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值