zoukankan      html  css  js  c++  java
  • 由浅入深,讲解 spring 实战详细使用——spring 源码篇

    首先,这篇文章不会详细的深入底层源码,只是基于注解和配置来说说我们的 spring 的使用,别小看基础,保证有你没用过的注解和配置,走起。

    我们先来建立一个 maven 项目,引入 spring 文件,不爱弄的在文章最下面有代码地址可以去下载。先看,后面自己下载代码自己去尝试。先给你们吧,边尝试边看吧。

    1. 基础 XML 注入 Bean

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="car" class="com.springIOC.bean.CarBean"></bean>
    </beans>
    
    package com.springIOC;
    
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MainTest {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("config.xml");
            Object car = cac.getBean("car");
        }
    }
    

    是不是超级简单的,我们由浅入深一点点来。

    2. 基于注解的方式来配置

    package com.springIOC2.config;
    
    import com.springIOC.bean.CarBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MainConfig {
        @Bean
            public CarBean car(){
            
            return new CarBean();
        }
    }
    
    package com.springIOC2;
    
    import com.springIOC2.config.MainConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainTest {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
            Object car = aca.getBean("car");
        }
    }
    
    

    我们通过方法名就可以直接得到我们的对象了,默认就是按照方法来装配。也可以通过 @Bean(value="newName") 来指定装配的名字。

    正是金九银十跳槽季,为大家收集了2019年最新的面试资料,有文档、有攻略、有视频。有需要的同学可以在公众号【Java知己】,发送【面试】领取最新面试资料攻略!暗号【1024】千万不要发,否则.....

    3. 按照包扫描的方式装配(重点),使用 @ComponentScan(basePackages={"包的全路径"})

    package com.springIOC3.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(basePackages = {"com.springIOC3"})
    public class MainConfig {
    }
    
    package com.springIOC3;
    
    import com.springIOC3.config.MainConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainTest {
        
        public static void main(String[] args) {
            AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
            String[] beanDefinitionNames = aca.getBeanDefinitionNames();
            for (String beanDefinitionName : beanDefinitionNames) {
                System.out.println("beanDefinitionName = " + beanDefinitionName);
            }
        }
    }
    

    这里在来说几个参数,excludeFilters 排除某一些对象,语法如下

    excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),  
    @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class}) 
    }
    
    
    

    FilterType 有五种,分别是 ANNOTATION(注解类),ASSIGNABLE_TYPE(类名),ASPECTJ(不常用,文档说 AspectJ 类型模式表达式匹配),REGEX(正则表达式匹配),CUSTOM(自定义),常用的三种我标记了红色。下面看一下具体写法

    package com.springIOC3b.config;
    
    import com.springIOC3b.repository.RepositoryBean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.stereotype.Controller;
    
    @Configuration
    @ComponentScan(basePackages = {"com.springIOC3b"},excludeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = {RepositoryBean.class})
    }
    )
    
    public class MainConfig {
    }
    

    刚才我们说到了自定义过滤,我们来看一下怎么写自定义的过滤,实现我们 TypeFilter 接口,重写我们的 match 即可,只关注返回的 true。下面是一个事例

    package com.springIOC3c.config;
    
    import org.springframework.core.io.Resource;
    import org.springframework.core.type.AnnotationMetadata;
    import org.springframework.core.type.ClassMetadata;
    import org.springframework.core.type.classreading.MetadataReader;
    import org.springframework.core.type.classreading.MetadataReaderFactory;
    import org.springframework.core.type.filter.TypeFilter;
    import java.io.IOException;
    
    public class CustomFilterType implements TypeFilter {
        @Override
            public Boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
            
            AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
            
            ClassMetadata classMetadata = metadataReader.getClassMetadata();
            
            Resource resource = metadataReader.getResource();
            if (classMetadata.getClassName().contains("RepositoryBean")) {
                
                return true;
            }
            
            
            
            return false;
        }
    }
    

    与包含相反的还有一个,只允许引入什么,也就是我们的 includeFilters,需要注意需要把 useDefaultFilters 属性设置为 false(true 表示扫描全部的)。语法和 excludeFilters 完全一致

    package com.springIOC3d.config;
    
    import com.springIOC3d.service.ServiceBean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.FilterType;
    import org.springframework.stereotype.Controller;
    
    @Configuration
    @ComponentScan(basePackages = {"com.springIOC3d"},includeFilters = {
            @ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class}),
            @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,value = ServiceBean.class)
    }
    ,useDefaultFilters = false)
    
    public class MainConfig {
    }
    

    4. 回过头来,我们看一下 Bean 的作用域。

    @Lazy 懒加载,使用才实例化,看下代码,我们在 Bean 里加入构造方法,更方便得出什么时候实例化的。

    package com.springIOC4.config;
    
    import com.springIOC4.bean.CarBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Lazy;
    
    @Configuration
    public class MainConfig {
        @Bean
            @Lazy
            public CarBean car(){
            return new CarBean();
        }
    }
    

    指定 @Scpoe 可以有四种作用域

    a) singleton 单实例的 (默认),单例的生命周期有 spring 容器来控制,非懒加载时在 spring 实例化以后就产生了对象,容器销毁则对象销毁

    package com.springIOC4b.config;
    
    import com.springIOC4b.bean.CarBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Lazy;
    import org.springframework.context.annotation.Scope;
    
    @Configuration
    public class MainConfig {
        @Bean
            @Scope(value = "singleton")
            public CarBean car(){
            return new CarBean();
        }
    }
    
    package com.springIOC4b;
    
    import com.springIOC4b.bean.CarBean;
    import com.springIOC4b.config.MainConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainTest {
        
        public static void main(String[] args) {
            AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
            CarBean car = (CarBean)aca.getBean("car");
            CarBean car2 = (CarBean)aca.getBean("car");
            System.out.println(car == car2);
            
        }
    }
    

    输出结果为 true,说明我们的对象是单例的,单例的对象,生命周期由 spring 来管理的。

    b) prototype 多实例的

    package com.springIOC4c.config;
    
    import com.springIOC4c.bean.CarBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Scope;
    
    @Configuration
    public class MainConfig {
        @Bean
            @Scope(value = "prototype")
            public CarBean car(){
            return new CarBean();
        }
    }
    
    package com.springIOC4c;
    
    import com.springIOC4c.bean.CarBean;
    import com.springIOC4c.config.MainConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainTest {
        
        public static void main(String[] args) {
            AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
            CarBean car = (CarBean)aca.getBean("car");
            CarBean car2 = (CarBean)aca.getBean("car");
            System.out.println(car == car2);
            
        }
    }
    

    多例的不受 ioc 容器来管理,销毁时是由 GC 来清理的,还有 request 同一次请求和 session 同一个会话级别的,这里就不一一演示了。

    5.@Configuration 注解,来判断是否注入 Bean 的。

    package com.springIOC5.config;
    
    import com.springIOC5.bean.CarBean;
    import com.springIOC5.bean.UserBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MainConfig {
        @Bean(value = "user")
            public UserBean userBean() {
            return new UserBean();
        }
        @Bean
            @Conditional(value = IOCConditional.class)
            public CarBean carBean() {
            return new CarBean();
        }
    }
    
    
    package com.springIOC5.config;
    
    import org.springframework.context.annotation.Condition;
    import org.springframework.context.annotation.ConditionContext;
    import org.springframework.core.type.AnnotatedTypeMetadata;
    
    public class IOCConditional implements Condition {
        @Override
            public Boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            if (context.getBeanFactory().containsBean("user")) {
                
                return true;
            }
            return false;
        }
    }
    

    上面的代码什么意思呢?就是我们是否需要注入 carBean,如果包含 user 这个对象,就注入我们的 carBean,不包含就不注入,这里有个意思的事,Configuration 配置里类的注入是有顺序的,我们必须把我们作为判断条件的 Bean 放在上面,否则 Conditional 会识别你没有那个判断条件的 Bean。

    6.@Import 引入方式注入 Bean

    package com.springIOC6.config;
    
    import com.springIOC6.bean.CarBean;
    import com.springIOC6.bean.UserBean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    @Configuration
    @Import({
        CarBean.class, UserBean.class
    }
    )
    public class MainConfig {
    }
    

    直接在注解内写入我们的要注入的类即可,也可以使用接口的方式来实现,我们来看换一下。

    package com.springIOC6b.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    @Configuration
    @Import({
        ImportSelector.class
    }
    )
    public class MainConfig {
    }
    
    package com.springIOC6b.config;
    
    import org.springframework.core.type.AnnotationMetadata;
    
    public class ImportSelector implements org.springframework.context.annotation.ImportSelector {
        @Override
            public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            return new String[]{
                "com.springIOC6b.bean.CarBean","com.springIOC6b.bean.UserBean"
            }
            ;
        }
    }
    

    实现 ImportSelector 类,然后返回类名全路径即可。自动装配就是基于 @Import 实现的。

    实现 ImportBeanDefinitionRegistrar,重写 registerBeanDefinitions 方法,也是可以的。

    package com.springIOC6c.config;
    
    import com.springIOC6c.bean.CarBean;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.RootBeanDefinition;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.type.AnnotationMetadata;
    
    public class ImportSelectorRegister implements ImportBeanDefinitionRegistrar {
        @Override
            public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(CarBean.class);
            registry.registerBeanDefinition("CarBean",rootBeanDefinition);
        }
    }
    

    7. 通过 FactoryBean 注入

    package com.springIOC7.config;
    
    import com.springIOC7.bean.UserBean;
    import org.springframework.beans.factory.FactoryBean;
    
    public class IOCFactoryBean implements FactoryBean<UserBean> {
        @Override
            public UserBean getObject() throws Exception {
            
            return new UserBean();
        }
        @Override
            public Class<?> getObjectType() {
            
            return UserBean.class;
        }
        @Override
            public Boolean isSingleton() {
            
            return true;
        }
    }
    
    package com.springIOC7.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MainConfig {
        @Bean
            public IOCFactoryBean iocFactoryBean(){
            return new IOCFactoryBean();
        }
    }
    
    package com.springIOC7;
    
    import com.springIOC7.config.MainConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainTest {
        
        public static void main(String[] args) {
            AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext(MainConfig.class);
            Object carBean = aca.getBean("iocFactoryBean");
            
            System.out.println(carBean);
            Object iocFactoryBean = aca.getBean("&iocFactoryBean");
            
            System.out.println(iocFactoryBean);
        }
    }
    

    说到这所有往 IOC 容器中添加组件的方式就全部说完了,简单总结一下:

    • @Bean 注入,可以指定四种作用域,单例,多例(生命周期不受 IOC 容器管理),一次请求和一次会话,也可以设置懒加载,
    • @ComponentScan 指定包扫描的方式来注入,配合 @Controller,@Repository,@Service,@Component 注解来使用。
    • @Import 方式注入,两种实现类 ImportSelector 和 ImportBeanDefinitionRegistrar 两种方式。
    • @FactoryBean,工程 Bean 的方式也可以注入。注意不带 & 是取得最终对象,带 & 是取得真实 Bean。三个方法,一个指定对象,一个指定类型,一个指定是否为单例。

    针对单实例 bean 的话,容器启动的时候,bean 的对象就创建了,而且容器销毁的时候,也会调用 Bean 的销毁方法。

    针对多实例 bean 的话, 容器启动的时候,bean 是不会被创建的而是在获取 bean 的时候被创建,而且 bean 的销毁不受 IOC 容器的管理。

    1. 我们先来看个最简单的方法,用 initMethod 和 destroyMethod 来指定我们的初始化方法和销毁方法

    package com.springlifeCycle1a.config;
    
    import com.springlifeCycle1a.bean.CarBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MainConfig {
        @Bean(initMethod = "init",destroyMethod = "destroy")
            public CarBean car() {
            return new CarBean();
        }
    }
    

    我们在指定了我们的 init 初始方法,销毁方法为 destroy 方法。调用顺序是,Car 的构造方法,Car 的 init 方法,Car 的 destroy 方法,也可以自己尝试使用 @Lazy 注解。码云代码里有可以自己去尝试。

    2. 通过 InitializingBean 和 DisposableBean 的两个接口实现 bean 的初始化以及销毁方法

    package com.springlifeCycle2a.bean;
    
    import org.springframework.beans.factory.DisposableBean;
    import org.springframework.beans.factory.InitializingBean;
    
    public class CarBean implements InitializingBean, DisposableBean {
        public CarBean() {
            System.out.println("我是Car");
        }
        public void afterPropertiesSet() {
            System.out.println("我是初始化init");
        }
        public void destroy() {
            System.out.println("我是销毁destroy");
        }
    }
    

    3. 通过 JSR250 规范 提供的注解 @PostConstruct 和 @ProDestory 标注的方法

    package com.springlifeCycle3.bean;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.PreDestroy;
    
    public class CarBean {
        public CarBean() {
            System.out.println("我是Car");
        }
        @PostConstruct
        public void init() {
            System.out.println("我是初始化init--PostConstruct");
        }
        @PreDestroy
        public void destory() {
            System.out.println("我是销毁destroy--PreDestroy");
        }    
    }
    

    4. 通过 Spring 的 BeanPostProcessor 的 bean 的后置处理器会拦截所有 bean 创建过程 (这个方法后面讲源码的时候会去讲内部实现,自己觉得有必要看这个的源码)

    package com.springlifeCycle4.bean;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.stereotype.Component;
    
    @Component
    public class LifeBeanPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("初始化方法" + beanName);
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("销毁方法" + beanName);
            return bean;
        }
    }
    

    这里也总结一下,我们来指定容器内对象的初始化方法和销毁方法的方式一共有四种

    • 用 @Bean 的 initMethod 和 destroyMethod 来给予初始化方法和销毁方法。
    • 通过 InitializingBean 和 DisposableBean 的二个接口实现 bean 的初始化以及销毁方法。
    • 通过 JSR250 规范 提供的注解 @PostConstruct 和 @ProDestory 标注的方法。
    • 通过 Spring 的 BeanPostProcessor 的 bean 的后置处理器会拦截所有 bean 创建过程。

    这里的东西不多,我就尽快说一下啦,赋值的方式有三种我们来看一下。

    package com.springValue.config;
    
    import com.springValue.bean.CarBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;
    
    @Configuration
    @PropertySource(value = "classpath:carBean.properties",encoding = "utf-8") //指定外部文件的位置
    public class MainConfig {
        @Bean
        public CarBean carBean() {
            return new CarBean();
        }
    }
    
    import org.springframework.beans.factory.annotation.Value;
    
    public class CarBean {
        @Value("宝马")
        private String name;
        @Value("#{5-2}")
        private int carNum;
        @Value("${carBean.realName}")
        private String realName;        
    }
    

    这里值得一提的就是导入文件最好设置一下 encoding = "utf-8",不然汉字会乱码。

    主动装配平时是我们最熟悉的,用的也是最多的,我们来复习一下。

    1.@Autowired 自动装配首先时按照类型进行装配,若在 IOC 容器中发现了多个相同类型的组件,那么就按照 属性名称来进行装配

    package com.springxAutowired1.config;
    
    import com.springxAutowired1.dao.AutowiredDao;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(value = "com.springxAutowired1.service")
    public class MainConfig {
        @Bean
        public AutowiredDao autowiredDao1(){
            return new AutowiredDao(1);
        }
        @Bean
        public AutowiredDao autowiredDao2(){
            return new AutowiredDao(2);
        }
    }
    
    package com.springxAutowired1.service;
    
    import com.springxAutowired1.dao.AutowiredDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ServiceBean {
        @Autowired
        private AutowiredDao autowiredDao2;
        public ServiceBean() {
            System.out.println("我是serviceBean");
        }
        @Override
        public String toString() {
            return "ServiceBean{" +
                    "AutowiredDao=" + autowiredDao2 +
                    '}';
        }
    }
    

    在这里我们设置了两个 AutowiredDao 的对象,一个标识为 1,一个标识为 2,ServiceBean 默认是按照名字来装配的。

    2. 我们也可以通过 @Qualifier 来指定装配名字。

    package com.springxAutowired1b.service;
    
    import com.springxAutowired1b.dao.AutowiredDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ServiceBean {
        @Autowired
        @Qualifier(value = "autowiredDao1")
        private AutowiredDao autowiredDao2;
        public ServiceBean() {
            System.out.println("我是serviceBean");
        }
        @Override
        public String toString() {
            return "ServiceBean{" +
                    "AutowiredDao=" + autowiredDao2 +
                    '}';
        }
    }
    

    3. 我们使用 Qualifier 是如果名字写错了,可能装配错误会报错,这时我们可以使用 required = false 来阻止异常的抛出

    package com.springxAutowired3.service;
    
    import com.springxAutowired3.dao.AutowiredDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    
    @Service
    public class ServiceBean {
        @Qualifier(value = "autowiredDaoError")
        @Autowired(required = false)
        private AutowiredDao autowiredDao2;
        public ServiceBean() {
            System.out.println("我是serviceBean");
        }
        @Override
        public String toString() {
            return "ServiceBean{" +
                    "AutowiredDao=" + autowiredDao2 +
                    '}';
        }
    }
    

    注意:这里我并没有说 @Resource 注解,这个注解其实不是 spring 里的,是 JSR250 规范的,但是不支持 @Primary 和 @Qualifier 的支持

    我们有时候需要通过不同的环境来切换我们的配置,我们通过 @Profile 注解,来根据环境来激活标识不同的 Bean,

    @Profile 标识在类上,那么只有当前环境匹配,整个配置类才会生效

    @Profile 标识在 Bean 上 ,那么只有当前环境的 Bean 才会被激活

    package com.springxAutowired4.config;
    
    import com.springxAutowired4.bean.Environment;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Profile;
    
    @Configuration
    public class MainConfig {
        @Bean
        @Profile(value = "test")
        public Environment environment_test() {
            return new Environment("test");
        }
        @Bean
        @Profile(value = "dev")
        public Environment environment_dev() {
            return new Environment("dev");
        }
        @Bean
        @Profile(value = "pro")
        public Environment environment_pro() {
            return new Environment("pro");
        }
    }
    

    激活切换环境的方法

    • 方法一: 通过运行时 jvm 参数来切换 -Dspring.profiles.active=test,dev,prod 多个参数表中间使用英文的逗号来分隔
    • 方法二: 通过代码的方式来激活
    package com.springxAutowired4;
    
    import com.springxAutowired4.config.MainConfig;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class MainTest {
        /**
         * 环境切换
         * @param args
         */
        public static void main(String[] args) {
            AnnotationConfigApplicationContext aca = new AnnotationConfigApplicationContext();
            aca.getEnvironment().setActiveProfiles("test","dev");//方便演示,我写了两个,一般都是一个的.
            aca.register(MainConfig.class);
            aca.refresh();
            for (String beanDefinitionName : aca.getBeanDefinitionNames()) {
                System.out.println(beanDefinitionName);
            }
        }
    }
    

    如果两个都写了,按照代码中的来实现,参数不再起作用


    “不积跬步,无以至千里”,希望未来的你能:有梦为马 随处可栖!加油,少年!

    关注公众号:「Java 知己」,每天更新Java知识哦,期待你的到来!

    • 发送「Group」,与 10 万程序员一起进步。
    • 发送「面试」,领取BATJ面试资料、面试视频攻略。
    • 发送「玩转算法」,领取《玩转算法》系列视频教程。
    • 千万不要发送「1024」...

  • 相关阅读:
    MAC使用小技巧(一)
    开发者:网站 & SDK
    编译 & 预处理
    归并排序 & 快速排序
    算法 & 分析 (收集)
    栈 & 堆 |--> 内存管理
    [ 单例、代理 & 通知 ]
    博客园:CSS & HTML
    比较、字符串
    Swift # 异常处理
  • 原文地址:https://www.cnblogs.com/java-friend/p/11582999.html
Copyright © 2011-2022 走看看