thread


为什么使用多线程

​ 更多的处理器核心 更快的响应时间 更好的编程模型


线程的六种状态

状态 状态说明
NEW 初始状态,线程已经被构建,但是没有调用start()方法。
RUNNABLE 线程处于运行状态或者是可运行状态。
BLOCKED 阻塞状态,表示当前线程等待获取锁。
WAITING 等待状态,需要其他线程唤醒。
TIME_WAITING 超时等待状态,需要其他线程唤醒或者过了超时时间自动唤醒。
TERMINATED 终止状态,表示当前线程已经运行结束。

如何查看线程信息

​ jps:该命令可以获取进程id。

​ jstack:使用 jstack 进程id,可以查看线程的一些信息。


线程的初始化

private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
// 创建的这个线程的父线程就是当前的这个线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
g.checkAccess();
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
// 设置当前线程的线程组信息
this.group = g;
// 将daemon priority设置为父线程的相关属性
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();
}

如何优雅的终止线程

​ 使用volidate修饰的变量 中断线程interrupt()方法


在class所在目录下,使用javap -v HelloWorld.class指令可以查看该class的信息


管道输入输出流–>用于线程之间数据的传输,传输媒介为内存

​ PipedInputStream

​ PipedOutputStram

​ PipedReader

​ PipedWriter

springboot

springboot三大特性(2.1.6)


自动装配

springboot核心注解 @SpringBootApplication。该注解是由下面三个注解组合而成的。


@ComponentScan 默认会去扫描当前类所在包下面的所有类或者所有包,将这些类创建为spring所管理的bean。可以通过添加basePackages或者是basePackageClasses属性来指定扫描的包路径。


@EnableAutoConfiguration

关于META-INF/spring-autoconfigure-metadata.properties文件与META-INF/spring.factories文件

# spring-autoconfigure-metadata.properties

#Tue Jun 18 23:47:55 GMT 2019
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration.Configuration=
org.springframework.boot.autoconfigure.data.neo4j.Neo4jBookmarkManagementConfiguration.Configuration=
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration=
org.springframework.boot.autoconfigure.kafka.KafkaAnnotationDrivenConfiguration.Configuration=
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration.ConditionalOnClass=org.influxdb.InfluxDB
# spring.factories,类似于 java 或者是 dubbo 中的 spi

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

该注解的实现主要依赖于@Import注解引入的AutoConfigurationImportSelector类的selectImports方法。

// 注解定义-》关键点:AutoConfigurationImportSelector类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

Class<?>[] exclude() default {};

String[] excludeName() default {};
}

// 导入筛选实现类 AutoConfigurationImportSelector
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
....
// 若是 getAutoConfiguration 方法判断出用户关闭了自动装配,就返回该对象。
private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 1. 判断是否可以导入,若是不可以导入,直接返回一个空数组 {}
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
// 2. 使用自动装配元信息加载类(AutoConfigurationMetadataLoader)的 loadMetadata 方法加载自动装配元信息。返回的AutoConfigurationMetadata对象包含了一个Properties类型的属性。(具体分析见 AutoConfigurationMetadataLoader的loadMetadata方法。)
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 3. 根据步骤2返回的自动装配元信息(AutoConfigurationMetadata)和 注解元信息(AnnotationMetadata)做进一步处理。
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
// 4. 转化未String数组返回
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

// 判断是否可以导入的逻辑,若是用户在配置文件中配置了spring.boot.enableautoconfiguration属性未false,则不执行自动装配。默认执行自动装配。
protected boolean isEnabled(AnnotationMetadata metadata) {
if (getClass() == AutoConfigurationImportSelector.class) {
return getEnvironment().getProperty(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class, true);
}
return true;
}

// 获取自动装配的元素
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
// 1. 若是判断出不能自动装配,注解返回一个 AutoConfigurationEntry 空对象
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 2. 获取到 EnableAutoConfiguration 注解的属性值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 3. 读取候选装配组件-> EnableAutoConfiguration所对应的配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 4. 对这些配置类去重
configurations = removeDuplicates(configurations);
// 5. 获取用户配置的排除项(不自动装配)-> exclude,excludeName以及 spring.autoconfigure.exclude
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 6. 检查需要排除的项是否合法,非法的将抛出异常
checkExcludedClasses(configurations, exclusions);
// 7. 将这些排除向从配置中移除
configurations.removeAll(exclusions);
// 8. 执行过滤
configurations = filter(configurations, autoConfigurationMetadata);
// 9. 触发相关事件
fireAutoConfigurationImportEvents(configurations, exclusions);
// 10. 返回
return new AutoConfigurationEntry(configurations, exclusions);
}

// 读取给定注解元信息AnnotationMetadata的属性AnnotationAttributes
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) {
// 1. 获取到自动装配注解 EnableAutoConfiguration 的包名
// 如:org.springframework.boot.autoconfigure.EnableAutoConfiguration
String name = getAnnotationClass().getName();
// 2. 获取注解 @EnableAutoConfiguration 的属性值,该注解包含了两个属性 exclude 和 excludeName
AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true));
// 3. 返回注解属性 AnnotationAttributes
return attributes;
}
// 返回 @EnableAutoConfiguration 注解的 Class
protected Class<?> getAnnotationClass() {
return EnableAutoConfiguration.class;
}

// 根据注解元信息 AnnotationMetadata 和注解属性 AnnotationAttributes 获取候选组件
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 调用 SpringFactoriesLoader 类的 loadFactoryNames 方法加载
// 参数值分别为 EnableAutoConfiguration.class 和 ClassLoader,该方法返回的是
// EnableAutoConfiguration 所对应的所有配置类
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
...
// 返回该配置列表
return configurations;
}
// 返回EnableAutoConfiguration.class
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
// 返回ClassLoader
protected ClassLoader getBeanClassLoader() {
return this.beanClassLoader;
}

// List列表去重-> list转为LinkedHashSet,然后在转为ArrayList
protected final <T> List<T> removeDuplicates(List<T> list) {
return new ArrayList<>(new LinkedHashSet<>(list));
}

// 获取排除的自动装配项
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) {
Set<String> excluded = new LinkedHashSet<>();
// @EnableAutoConfiguration注解的exclude属性值
excluded.addAll(asList(attributes, "exclude"));
// @EnableAutoConfiguration注解的excludeName属性值
excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName")));
// 在 application.properties配置文件中配置的排除项
excluded.addAll(getExcludeAutoConfigurationsProperty());
return excluded;
}

protected final List<String> asList(AnnotationAttributes attributes, String name) {
String[] value = attributes.getStringArray(name);
return Arrays.asList((value != null) ? value : new String[0]);
}
// 获取配置文件中配置的 spring.autoconfigure.exclude 属性值
private List<String> getExcludeAutoConfigurationsProperty() {
if (getEnvironment() instanceof ConfigurableEnvironment) {
Binder binder = Binder.get(getEnvironment());
return binder.bind(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class).map(Arrays::asList)
.orElse(Collections.emptyList());
}
String[] excludes = getEnvironment().getProperty(PROPERTY_NAME_AUTOCONFIGURE_EXCLUDE, String[].class);
return (excludes != null) ? Arrays.asList(excludes) : Collections.emptyList();
}
// 检查配置的排除项是否合法,需要参数:自动装配列表,排除项列表
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) {
// 1. 创建一个无效的排除项列表 invalidExcludes
List<String> invalidExcludes = new ArrayList<>(exclusions.size());
// 2. 循环遍历这些排除项,将无效的放到 invalidExcludes列表中
for (String exclusion : exclusions) {
// 排除项无效的判断依据:是对应类加载器加载并且没有包含在 configurations列表中
if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) {
invalidExcludes.add(exclusion);
}
}
// 3.若是排除项为非空,处理这些排除项
if (!invalidExcludes.isEmpty()) {
handleInvalidExcludes(invalidExcludes);
}
}
// 封装非法的排除项,并抛出运行时异常
protected void handleInvalidExcludes(List<String> invalidExcludes) {
StringBuilder message = new StringBuilder();
for (String exclude : invalidExcludes) {
message.append("\t- ").append(exclude).append(String.format("%n"));
}
throw new IllegalStateException(String.format(
"The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s",
message));
}



/*************************执行fileter过滤*******************/
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
// 1. 获取开始的时间(纳秒)
long startTime = System.nanoTime();
// 2. 将候选组件转为数组
String[] candidates = StringUtils.toStringArray(configurations);
// 3. 创建一个boolean类型的数组,长度为candidates.length
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
// 4. 循环遍历这些过滤器
for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
// 设置相关的属性值
invokeAwareMethods(filter);
// 调用该过滤器的 match 方法,通过参数 candidates 和 autoConfigurationMetadata 来判断是否匹配该过滤器。
boolean[] match = filter.match(candidates, autoConfigurationMetadata);
// 遍历 match 列表
for (int i = 0; i < match.length; i++) {
// 若是第 i 项需要被过滤,将该项的 skip 设置为 true,并将该项设置为 null, 并将 skipped标识设置为 true
if (!match[i]) {
skip[i] = true;
candidates[i] = null;
skipped = true;
}
}
}
// 若是没有出现被过滤的,直接返回原始的
if (!skipped) {
return configurations;
}
// 若是有出现被过滤的,将被过滤的去掉,返回未被过滤的
List<String> result = new ArrayList<>(candidates.length);
for (int i = 0; i < candidates.length; i++) {
if (!skip[i]) {
result.add(candidates[i]);
}
}
...
return new ArrayList<>(result);
}
// 获取自动装配导入过滤器
protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
// 1. 通过SpringFactoriesLoader加载这些过滤器
// 该版本存在三个过滤器 OnBeanCondition OnClassCondition OnWebApplicationCondition
return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader);
}

private void invokeAwareMethods(Object instance) {
if (instance instanceof Aware) {
if (instance instanceof BeanClassLoaderAware) {
((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader);
}
if (instance instanceof BeanFactoryAware) {
((BeanFactoryAware) instance).setBeanFactory(this.beanFactory);
}
if (instance instanceof EnvironmentAware) {
((EnvironmentAware) instance).setEnvironment(this.environment);
}
if (instance instanceof ResourceLoaderAware) {
((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader);
}
}
}

/*************************触发相关事件*******************/
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
// 1. 获取到 AutoConfigurationImportListener 列表
List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners();
// 2. 若是列表非空,循环遍历 AutoConfigurationImportListener, 触发事件
if (!listeners.isEmpty()) {
// 创建一个 AutoConfigurationImportEvent 事件
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
for (AutoConfigurationImportListener listener : listeners) {
invokeAwareMethods(listener);
listener.onAutoConfigurationImportEvent(event);
}
}
}

...
}

/*********************AutoConfigurationMetadataLoader**********************/
/*
自动装配元信息加载类,该类在自动装配方面的作用就是加载了 PATH 路径下面的资源文件,并将其转换为 Properties 文件,该文件会做为 AutoConfigurationMetadata 对象的一个属性(properties)。
在自动装配注解 @EnableAutoConfiguration 的拦截装配类 AutoConfigurationImportSelector的 selectImports 方法的第二步,通过自动装配元信息加载类 AutoConfigurationMetadata 的 loadMetadata 方法来加载自动装配的一些元信息。
*/
final class AutoConfigurationMetadataLoader {

// 自动装配元信息的存储路径
protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties";

// 根据给定的 ClassLoader 获取到自动装配元信息
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, PATH);
}

// 根据给定的 ClassLoader 和 path 获取自动装配元信息
static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) {
try {
// 1. 读取到 PATH 路径下的资源文件,并将其以 key - value 的形式存储在 Properties文件中
Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path): ClassLoader.getSystemResources(path);
Properties properties = new Properties();
while (urls.hasMoreElements()) {
properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement())));
}
// 2. 返回 AutoConfigurationMetadata 对象,该对象持有 Properties:properties属性
return loadMetadata(properties);
} catch (IOException ex) {
...
}
}

// 通过给定的一个Properties对象,获取到一个 AutoConfigurationMetadata对象,该方法返回的是实现类为 PropertiesAutoConfigurationMetadata。
static AutoConfigurationMetadata loadMetadata(Properties properties) {
return new PropertiesAutoConfigurationMetadata(properties);
}

// AutoConfigurationMetadata 实现类 PropertiesAutoConfigurationMetadata 的定义,该类持有一个类型为 Properties 的属性 properties。
private static class PropertiesAutoConfigurationMetadata implements AutoConfigurationMetadata {

private final Properties properties;

PropertiesAutoConfigurationMetadata(Properties properties) {
this.properties = properties;
}
}
}


/***************************SpringFactoriesLoader*****************/
/*
SpringFactoriesLoader是Spring Framework工厂机制的加载器,该类的作用是加载 FACTORIES_RESOURCE_LOCATION 路径下的资源
*/
public final class SpringFactoriesLoader {
// 资源加载路径
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
// 缓存,存储每个ClassLoader下的资源
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

// 通过给定的 Class 和 ClassLoader加载在配置文件中配置的类
// 参数分别为EnableAutoConfiguration和ClassLoader
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 1. 首先获取给定参数 Class<?> factoryClass 的类完全限定名
// 如:org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryClassName = factoryClass.getName();
// 2. 返回参数 ClassLoader 对应 MultiValueMap 中键为 factoryClassName 的列表
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

// 根据给定的ClassLoader,返回一个 MultiValueMap 类型的资源
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 1. 首先从缓存中获取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
// 2.若是缓存中加载不到,就去 FACTORIES_RESOURCE_LOCATION 路径加载
try {
// 2.1 通过 ClassLoader和路径获取到 URL集合
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 2.2 遍历这些URL集合
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 2.3 将其转化为properties文件
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 2.4 将结果放入到缓存中去
cache.put(classLoader, result);
return result;
} catch (IOException ex) {}
}


public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
// 确定使用的ClassLoader
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
// 调用 loadFactoryNames 返回 factoryClass 所对应的配置
List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
/// 创建一个集合,存放这些 class 的对象
List<T> result = new ArrayList<>(factoryNames.size());
for (String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
// 排序后返回结果
AnnotationAwareOrderComparator.sort(result);
return result;
}
// 通过反射实例化
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
...
}
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (Throwable ex) {
...
}
}
}

关于 AutoConfigurationImportFilter

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
...
}

@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
...
}

@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnWebApplicationCondition extends FilteringSpringBootCondition {
...
}

abstract class FilteringSpringBootCondition extends SpringBootCondition
implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {

// match方法,自动装配时会被自动调用
@Override
public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionEvaluationReport report = ConditionEvaluationReport.find(this.beanFactory);
ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
boolean[] match = new boolean[outcomes.length];
for (int i = 0; i < outcomes.length; i++) {
match[i] = (outcomes[i] == null || outcomes[i].isMatch());
if (!match[i] && outcomes[i] != null) {
logOutcome(autoConfigurationClasses[i], outcomes[i]);
if (report != null) {
report.recordConditionEvaluation(autoConfigurationClasses[i], this, outcomes[i]);
}
}
}
return match;
}
}

关于 @AutoConfigurationPackage 注解

// 关于该注解定义
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

public abstract class AutoConfigurationPackages {

private static final String BEAN = AutoConfigurationPackages.class.getName();


static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}

@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}

}

public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}

private static String[] addBasePackages(ConstructorArgumentValues constructorArguments, String[] packageNames) {
String[] existing = (String[]) constructorArguments.getIndexedArgumentValue(0, String[].class).getValue();
Set<String> merged = new LinkedHashSet<>();
merged.addAll(Arrays.asList(existing));
merged.addAll(Arrays.asList(packageNames));
return StringUtils.toStringArray(merged);
}
}

@SpringBootConfiguration


核心事件

ApplicationEnvironmentPreparedEvent
ApplicationPreparedEvent
ApplicationStartingEvent
ApplicationReadyEvent
ApplicationFailedEvent

系统属性 System:getProperties


环境变量System:getEnv


嵌入式容器

为生产做准备

springboot踩坑记录
1. 当我们在springboot的配置文件中将启动端口配置为随机端口时 server.port=0,我们使用下面的方式无法获取到启动的真正端口,该port的值任然为0。

@Value("${server.port}")
private int port;

可以通过使用下面这种方式来获取真正的启动端口

private final Environment environment;
public OtherController(Environment environment) {
this.environment = environment;
}
private String getPort() {
return environment.getProperty("local.server.port");
}

bean的创建

spring在xml驱动的BeanFactory中创建bean的方式有很多种方式。

普通的bean创建

普通的bean创建方式一般是在xml文件中直接配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置一个简单的bean -->
<bean id="mycar" class="com.study.pack.xml.factorybean.Car"></bean>

<!-- 配置一个简单的bean,并为其加入name和price属性 -->
<bean id="mycarB" class="com.study.pack.xml.factorybean.Car">
<property name="name" value="宝马"/>
<property name="price" value="2000"/>
</bean>
</beans>
public class Test {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("factory-bean.xml"));
Car carA = (Car)factory.getBean("mycarA");
Car carB = (Car)factory.getBean("mycarB");
System.out.println(carA);
System.out.println(carB);
}
// 输出结果
// null--0.0
// 宝马--2000.0
}

@Data
public class Car {
private String name;
private double price;

@Override
public String toString() {
return this.name + "--" + this.price;
}
}
FactoryBean创建Bean

FactoryBean创建bean的不同之处在于增加了一个FactoryBean实现类,该类负责相关bean的创建细节,而在xml中配置的也是该FactoryBean的信息。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 通过指定的FactoryBean创建Bean -->
<bean id="car" class="com.study.pack.xml.factorybean.CarFactoryBean">
<property name="message" value="比亚迪,20000"/>
</bean>
</beans>
public class FactoryBeanTest {

public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("factory-bean.xml"));
Car car = (Car) factory.getBean("car");
// 加上&是只返回该FactoryBean实例
CarFactoryBean carFactoryBean = (CarFactoryBean) factory.getBean("&car");
System.out.println(car);
System.out.println(carFactoryBean);
}
// 输出信息
// 比亚迪--20000.0
// 比亚迪,20000
}

public class CarFactoryBean implements FactoryBean<Car> {
private String message;

@Override
public Car getObject() throws Exception {
Car car = new Car();
String[] messageArray = message.split(",");
car.setName(messageArray[0]);
car.setPrice(Double.parseDouble(messageArray[1]));
return car;
}

@Override
public Class<?> getObjectType() {
return Car.class;
}

@Override
public boolean isSingleton() {
return true;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

@Override
public String toString() {
return this.message;
}
}
其它方式创建Bean

除了以上两种创建方式之外,spring还可以通过工厂方法来创建实例,工厂方法创建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" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

<!-- 静态方法 -->
<bean id="secondCar" class="com.study.pack.xml.factorybean.SimpleCarFactory" factory-method="getCar"/>

<!-- 非静态方法 -->
<bean id="firstCar" factory-bean="simpleCarFactory" factory-method="createCar"/>
<bean id="simpleCarFactory" class="com.study.pack.xml.factorybean.SimpleCarFactory"/>

</beans>
public class Test {
public static void main(String[] args) {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("factory-bean.xml"));
Car firstCar = (Car)factory.getBean("firstCar");
Car secondCar = (Car)factory.getBean("secondCar");
System.out.println(firstCar);
System.out.println(secondCar);
}
// 输出
//吉利--200.0
//保时捷--150000.0
}

public class SimpleCarFactory {

// 静态方法
public static Car getCar() {
Car car = new Car();
car.setName("保时捷");
car.setPrice(150000);
return car;
}

// 非静态方法
public Car createCar() {
Car car = new Car();
car.setName("吉利");
car.setPrice(200);
return car;
}
}

基本操作

安装步骤
1.安装相关库gcc  
# yum install gcc
2.下载安装包
# wget http://download.redis.io/releases/redis-3.0.0.tar.gz
3.解压安装包
# tar zxvf redis-3.0.0.tar.gz
4.执行make命令
# cd redis-3.0.0/src
# make
5.将src目录下的相关文件拷贝到/usr/redis文件夹下
# mkdir /usr/redis
# cp redis-server /usr/redis
# cp redis-benchmark /usr/redis
# cp redis-cli /usr/redis
# cp redis.conf /usr/redis
6.为redis-server和redis-cli创建软链接
# ln -s /usr/redis/redis-server /usr/bin/redis-server
# ln -s /usr/redis/redis-cli /usr/bin/redis-cli
服务启动与关闭
# 服务启动
1.启动redis
# redis-server /usr/redis/redis.conf
2.若是需要在后台启动,可以修改redis.conf文件中的daemonize为yes

# 服务关闭
1. 强制结束程序。使用kill -9 进程的pid,强行终止Redis进程可能会导致redis持久化丢失。
2. 正确停止redis的方式是向redis发送shutdown命令(关闭默认端口)。 # redis-cli shutdown
cluster集群搭建
开启集群
修改配置文件redis.conf中的cluster-enabled yes
加入到集群
127.0.0.1:6379> cluster meet 192.168.25.150 6379
分配hash槽
[root@localhost redis]# redis-cli -h 192.168.25.147 -p 6379 cluster addslots {0..5000}
string基本操作
#单个操作   
set name value(设置某个key-value)
get name(获取某个key)
exists name(判断某个key是否存在)
del name(删除某个key)
expire name seconds(给某个key设置expire)
setex name seconds value(设置key的时候同时设置expire)
setnx name value(不存在存储,存在不存储)
#批量操作
mset name1 value1 name2 value2(批量插入)
mget name1 name2(批量获取)
#特殊操作
incr key(针对值为整数的情况,value加1)
incrby key 5(针对值为整数的情况,value加5)

# 使用场景
保存一些JSON序列化的用户对象信息,使用时通过JSON反序列化,一次性的获取一整个用户对象,消耗网络流量。优点是存储消耗低于hash。
list基本操作
#基本操作(普通操作)
rpush key value1 value2 value3(存储元素)
llen key(获取列表元素个数)
lpop key(从左边弹出,非阻塞)
rpop key(从右边弹出,非阻塞)
blpop key timeout(从左边弹出,阻塞,timeout单位为秒)
brpop key timeout(从右边弹出,阻塞,timeout单位为秒)
# 基本操作(慢操作)
lindex key index(获取某个位置上的value)
ltrim key start_index end_index(保留start_index到end_index区间内的值,index可为负数,-1表示最后一个)
lrange key start_index end_index(返回start_index到end_index区间内的值)

# 使用场景
异步队列(rpush lpop)
栈(rpush rpop)
hash基本操作
#基本操作
hset key filed value(给某个key的字典中存储一个key-value)
hgetall key(查看key-value,key和value间隔出现)
hlen books(查看key中存储的key-value个数)
hget key filed(获取某个key下filed的value值)
hmset key filed1 value1 filed2 value2(批量设置key-value)
hincrby key filed 1(给某个key的field字段的value加1)

#使用场景
存储用户信息->针对用户信息中的每个字段单独存储,获取用户信息时可以对部分字段单独获取,节省网络流量。但是缺点是存储消耗要高于字符串。
set基本操作
#基本操作
sadd key value(集合中添加一个元素)
sadd key value1 value2(集合中添加多个元素)
smembers key(列出key下面的value列表)
sismember key value(查询key下面是否存在value)
scard key(获取key的长度)
spop key(弹出一个元素)

#使用场景
可以用来存储中将用户的id,因为有去重功能,所以可以保证同一个用户不会中将两次。
zset基本操作
#基本操作
zadd key score value(添加一个value)
zrange key 0 -1(获取key元素列表,按照score排序列出)
zrevrange key 0 -1(获取key元素列表,按照score逆序列出)
zcard key(返回key中元素个数)
zscore key value(返回某个key的score)
zrangebyscore key score1 score2(根据分值区间遍历zset)
zrangebyscore key -inf score withscore (根据分值区间 (-∞, score] 遍历 zset,同时返回分值。inf 代表 infinite,无穷大的意思。)

#使用场景
可以用来存储粉丝列表,value值是粉丝的用户id,score是关注时间。可以对粉丝列表按照关注时间进行排序。
可以用来存储学生的成绩,value值是学生的id, score是考试成绩。可以对成绩按照分数进行排序,就可以获取到它的名次。
sort命令
# redis的sort命令可以对列表键 集合键或者有序集合键的值进行排序

############## 最简单的排序 sort #########
# 插入元素
redis> rpush numbers 5 3 1 4 2
(integer) 5

# 按照插入顺序排列的列表元素
redis>lrange numbers 0 -1
# 元素的输出顺序为 "5" "3" "1" "4" "2"

# 按值从小到大有序排列的列表元素(默认升序排列asc)
redis> sort numbers
# 元素的输出顺序为 "1" "2" "3" "4" "5"

# 按值从大到小有序排列的列表元素
redis> sort numbers desc
# 元素的输出顺序为 "5" "4" "3" "2" "1"

############ 使用 sort和alpha选项对字符串进行排序########
# 插入元素
reids> sadd alphabet a b c d e f g

# 乱序排列的集合元素
redis> smembers alphabet
# 输出顺序为 "d" "a" "c" "b" "g" "e" "f"

# 集合元素排序,使用alpha,asc升序排列
redis> sort alphabet alpha
# 输出顺序为 "a" "b" "c" "d" "e" "f" "g"

# 集合元素排序,使用alpha,desc降序排列
redis> sort alphabet alpha desc
# 输出顺序为 "g" "f" "e" "d" "c" "b" "a"

############ 使用sort和by选项对元素排序 ############
# 插入元素
redis> zadd test-result 3.0 jack 3.5 peter 4.0 tom
(integer) 3

# 普通输出,按照元素的分值排序
redis> zrange test-result 0 -1
# 输出顺序为 "jack" "peter" "tom"

# 为各个元素设置序号
redis> mset peter_number 1 tom_number 2 jack_number 3
ok

# 以序号为权重,对集合中的元素进行排序
redis> sort test-result by *_number
# 输出顺序为 "peter" "tom" "jack"
scan命令
// scan命令的使用
// scan命令提供了三个参数,第一个参数是cursor整数值,第二个是key的正则模式,第三个是遍历的limit hint。第一次遍历时,cursor值为0,然后将返回结果中第一个整数值作为下一次遍历的cursor。一直遍历到返回的cursor值为0时结束。

// 可以同过java的jedis客户端插入一些key
int count = 10000;
Jedis jedis = new Jedis("192.168.25.150", 6379);
for (int i = 0; i < count; i++) {
jedis.set("key" + i, i + "");
}

// 下面是redis-cli操作
127.0.0.1:6379> scan 0 match key99* count 1000
1) "10904"
2) 1) "key9987"
2) "key9924"
3) "key9992"
4) "key9941"
5) "key9901"
6) "key9945"
7) "key9984"
8) "key9903"
127.0.0.1:6379> scan 10904 match key99* count 1000
1) "14028"
2) 1) "key9989"
2) "key992"
3) "key9997"
4) "key9968"
5) "key996"
6) "key9958"

//下面是java客户端操作代码段
String cursor = "0";
ScanParams scanParams = new ScanParams();
scanParams.match("key99*");
scanParams.count(1000);
while (true) {
ScanResult<String> result = jedis.scan(cursor, scanParams);
List<String> resultList = result.getResult();
for (String resultStr : resultList) {
System.out.print(resultStr + "\t");
}
System.out.println("\n--------------");
String stringCursor = result.getStringCursor();
// 当游标为再次为0时,表示已经遍历完毕,直接跳出循环
if (stringCursor.equals("0")) {
break;
} else {
cursor = stringCursor;
}
}

HashMap


1.HashMap的数据结构 在jdk1.7中基于数组+链表,在jdk1.8中基于数组+链表+红黑树
2.HashMap的put方法的过程 a.首先判断当前的数组是否被初始化,若是没有被初始化,调用resize方法初始化 b.通过key的hash值和数组长度计算出该元素在数组中的位置 c1.若是数组上没有元素,构建Node节点,存储该元素 c21.若是该数组上有元素,且第一个节点的key与要存储的key相等,用变量保存该节点。 c22.若是该数组上有元素,且第一个节点的key与要存储的key不相等,需要判断该节点类型。 若是该节点属于红黑树,将元素插入到红黑树。 若是该节点属于链表,循环遍历链表,若是没有遇到key相同的,将key-value创建称为节点,插入到链表的尾部。判断是否需要转成红黑树,若是需要,将链表转成红黑树。 d.前面的操作中,若是找到与key相同的节点,根据条件判断是否需要覆盖,若是需要覆盖,直接修改原有节点的value。 f.将元素的个数size加1并判断是否需要扩容,若是需要扩容,调用resize方法扩容。
3.HashMap的resize方法 resize方法涉及到两个大的步骤,首先是确定新数组的大小已经下次的扩容时机,新数组大小为原有数组大小的两倍,扩容变量也扩大为原有的两倍。其次是将原有数组的元素迁移至新的数组中,其中数组元素只会在两个地方,一个在[原下标]的地方,另一个在[原下标+原容量]的位置。

jvm

jvm的运行时数据区


堆:java垃圾回收的主要区域,唯一存在的价值就是存放对象实例,几乎所有的对象实例都会在这里分配内存。从垃圾回收的角度分为新生代和老年代。在细分一点可以划分为Eden空间,From Survivor空间,To Survivor空间。若是在堆中没有完成实例分配,并且堆也无法再扩展时,就会抛出OutOfMemoryError异常。


方法区:用于存储已经被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。当方法区无法满足内存分配的需求时,将会抛出OutOfMemoryError异常。


虚拟机栈:描述的是java方法执行的内存模型,每个方法执行时都会创建一个栈帧,栈帧中用于存储局部变量表,操作数栈,动态链接,方法出口等信息。当线程申请的栈深度大于虚拟机所允许的栈深度,将抛出StackOverflowError异常。若是虚拟机动态扩展时无法申请到足够的内存,将会抛出OutOfMemoryError异常。


程序计数器:记录当前线程执行的字节码指令的行号,不会发生OutOfMemoryError。


本地方法栈:用于执行非java方法的内存模型,也会发生StackOverflowError和OutOfMemoryError异常。


jvm垃圾回收考虑的三个问题

  • 回收哪些对象
  • 什么时候回收这些对象
  • 怎么回收这些对象

jvm垃圾回收算法

  • 标记清除:会有内存碎片

  • 标记整理:没有内存碎片,适合新生代对象的回收

  • 复制算法:需要留出一部分的内存空间,利用率不高

  • 分代收集算法:新生代死亡对象多,采用复制算法。老年代死亡对象少,采用标记整理或标记清除。


jvm垃圾收集器

​ 新生代垃圾收集器

​ 老年代垃圾收集器

面试题

1.char(32)和varchar(32)的区别
(1):char和varchar都是数据库定义字符串类型的数据格式,char是一种定长度的类型,varchar是一种可变长度的类型。
(2):char(32)表示定义了当前字段所占用的存储空间为32个字符,不管字段长度是否达到32,占用的空间是不变的。而varchar(32)表示定义了当前字段所能够占用的最大存储空间是32个字符,实际占用空间是字段的大小。
(3):就存储效率而言,定长的char类型由于一开始就定义好了字段占用空间,不需要根据字段的长度在去申请空间,故效率相对较高,但是在占用空间上就没有varchar有优势。而varchar由于根据字段长度调整空间占用,故空间消耗较小,但是存储效率不高。

2.sql注入,如何避免sql注入
概念: 所谓sql注入, 就是攻击者将sql命令插入到web表单的输入域或者是页面请求的查询字符串,欺骗服务器执行恶意的sql命令。(某些表单中的输入命令被直接用来构造(或影响)sql命令,或者是构成存储过程的输入参数, 这类表单特别容易受到sql注入式攻击)。
如何避免:
(1): mybatis中多使用[#{param}],尽量避免使用[${param}],原因在于[${param}]会直接参与sql编译,容易造成sql攻击。

3.数据库的三大范式
第一范式: 每一列都是一个不可再分割的属性值,确保每一列的原子性(规范列)。
第二范式: 在满足第一范式的要求下,每一行数据只做一件事(规范行)。
第三范式: 在满足第二范式的要求下,确保数据表中的每一列数据都和主键直接相关,而不能够间接相关。