高阶面试-spring的部分

spring的诞生

为什么需要spring?spring之前人们是怎么开发的,用的主流框架是什么,spring解决了什么痛点?

Enterprise JavaBeans (EJB),企业级开发框架,里面就提出bean的概念了,为啥不用呢?配置复杂、重量级、灵活性差。

给了spring机会。

EJB只适合大型企业,时代变了,互联网时代,讲究敏捷、快速,spring应运而生。

spring的优势

为什么spring这么厉害呢?了解下他的设计思想。
初学Java的都知道java是面向对象编程,那spring其实就是再往前一步,面向bean编程。
bean跟我们普通的java对象有什么区别?

  1. 依赖注入的方式解耦bean之间的依赖,将依赖管理交由容器管理
  2. 托管bean的全生命周期,从beanDefinition到实例化、属性填充、初始化到最后的销毁,一站式服务
  3. AOP,面向切面编程,不修改bean代码的情况下,添加额外的功能,如事务管理、日志记录

更简单、易用的框架,采用IOC和AOP等,简化开发流程。开发人员只需要专注业务逻辑。周边生态

BeanFactory和ApplicationContext的区别

都是管理Bean的容器接口,ApplicationContext是BeanFactory的子接口(ApplicationContext – ListableBeanFactory – BeanFactory),功能更多,区别:

  • applicationContext立即初始化,容器启动创建所有单例bean,beanFactory延迟初始化
  • ApplicationContext实现MessageResource,支持国际化
  • ApplicationContext实现ApplicationEventPublisher,提供事件机制
  • ApplicationContext内置AOP的支持,BeanPostProcessor处理Bean后期初始化工作
  • ApplicationContext实现自动装配,@ComponentScan注解或<context:component-scan> 元素,spring扫描指定的包及其子包的类,将带有特定注解如 @Component@Service@Repository@Controller)的类注册为容器的bean

依赖注入(DI)和控制反转(IoC)有什么区别?

控制反转(IoC)

控制反转 是一种设计原则,它描述了程序结构的控制权从应用程序代码转移到框架或容器的过程。传统的编程模式中,应用程序代码控制着依赖对象的创建和管理。在 IoC 模式中,这种控制权被反转,交给了 IoC 容器或框架。

  • 目的:降低代码的耦合性,提高模块的可重用性和可测试性。
  • 实现方式:通过将对象的创建和依赖管理交给 IoC 容器或框架。

依赖注入(DI)

依赖注入 是实现控制反转的一种具体方式。它通过将对象的依赖关系注入到对象中,而不是由对象自己去创建或查找依赖。DI 可以有多种形式,包括构造函数注入、Setter 方法注入和接口注入。

  • 目的:将依赖关系的管理职责从对象自身转移出去,使得对象更加专注于自身的功能,提高代码的可测试性和可维护性。
  • 实现方式:通过构造函数、Setter 方法或接口将依赖注入到对象中。

Spring支持哪些类型的依赖注入?

主要的依赖注入方式包括构造函数注入、Setter 方法注入和字段注入

构造函数注入

构造函数注入是通过类的构造函数将依赖项传递给类的实例。这种方式在类的实例化时就完成了依赖注入,因此可以确保依赖项在对象创建时就被正确地初始化。

@Component
public class ServiceA {
    private final ServiceB serviceB;

    @Autowired
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void performTask() {
        serviceB.doSomething();
    }
}

@Component
public class ServiceB {
    public void doSomething() {
        System.out.println("ServiceB is doing something");
    }
}

Setter 方法注入

Setter 方法注入是通过类的 Setter 方法将依赖项传递给类的实例。这种方式允许在对象创建之后设置或更改依赖项。

@Component
public class ServiceA {
    private ServiceB serviceB;

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }

    public void performTask() {
        serviceB.doSomething();
    }
}

@Component
public class ServiceB {
    public void doSomething() {
        System.out.println("ServiceB is doing something");
    }
}

字段注入

字段注入是通过直接在类的字段上使用 @Autowired 注解进行依赖注入。这种方式简洁,但在测试和重构时可能不如构造函数注入和 Setter 方法注入灵活。

@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;

    public void performTask() {
        serviceB.doSomething();
    }
}

@Component
public class ServiceB {
    public void doSomething() {
        System.out.println("ServiceB is doing something");
    }
}

5. 注解配置

Spring 提供了 @Autowired 注解来标识需要注入的依赖项,@Qualifier 注解来解决多个相同类型 Bean 的注入冲突,以及 @Resource 注解用于 JSR-250 标准的依赖注入。

使用 @Qualifier

当有多个相同类型的 Bean 时,可以使用 @Qualifier 指定注入的 Bean

IOC容器

IoC 容器是一个管理 Bean 的工厂,它根据配置元数据(如 XML 文件、注解或 Java 配置类)创建、配置和管理 Bean。Spring 提供了两种主要的 IoC 容器实现:

  1. BeanFactory:基本的 IoC 容器,提供基础的 DI 功能。
  2. ApplicationContext:比 BeanFactory 更高级的容器,提供了更多的企业级特性,如事件发布、国际化、AOP 集成等。

beanDefinition

BeanDefinition(Bean 定义)是描述 Spring 容器中的 Bean 实例的元数据对象。它定义了 Bean 的各种属性,如类名、作用域、构造方法参数、属性值、初始化方法、销毁方法等信息。通过 BeanDefinition,Spring 容器可以了解如何创建、配置和管理 Bean 实例。

bean生命周期的整个过程

在这里插入图片描述

  • 实例化(Instantiation): 在 Spring 容器中,Bean 的生命周期始于实例化。这意味着 Spring 根据 Bean 的定义(如配置文件、注解等)创建 Bean 的实例。

  • 依赖注入(Dependency Injection): 一旦 Bean 实例化完成,Spring 将会注入所需的依赖。这包括构造函数注入、Setter 方法注入以及字段注入(通过反射或者代理等方式)。

  • 初始化(Initialization): 初始化阶段是 Bean 生命周期中的一个重要部分。在初始化阶段,Spring 容器会调用特定的初始化回调方法(如果有的话),例如通过 init-method 属性指定的初始化方法,或者使用 @PostConstruct 注解标注的方法。

  • 使用(In Use): 一旦 Bean 初始化完成并且依赖注入完成,Bean 就可以被 Spring 容器管理和使用了。在这个阶段,Bean 处于活跃状态,可以被其他 Bean 或者应用程序代码引用和调用。

  • 销毁(Destruction): 当不再需要 Bean 时,Spring 容器会释放它。这个阶段涉及到 Bean 的销毁工作,例如调用销毁回调方法(通过 destroy-method 属性指定的方法或者使用 @PreDestroy 注解标注的方法)

BeanDefinition->实例化

  • BeanFactoryPostProcessor#postProcessBeanFactory bean创建前修改bean的定义信息
  • BeanPostProcessor#postProcessBeforeInstantiation 实例化前,返回对象不为null,替换原本的bean

实例化->依赖注入DI

  • BeanPostProcessor#postProcessAfterInstantiation 实例化后,在 Bean 的实例化之后对其进行进一步处理或检查,false跳过DI
  • BeanPostProcessor#postProcessProperties DI前如@Autowired @Value @Resource
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.stereotype.Component;

@Component
public class CustomBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

    @Override
    public PropertyValues postProcessProperties(
            PropertyValues pvs, Object bean, String beanName) throws BeansException {
        // 对 bean 进行自定义处理
        if (bean instanceof MyBean) {
            // 获取并修改属性值
            MutablePropertyValues mutablePvs = (MutablePropertyValues) pvs;
            mutablePvs.add("propertyName", "newValue");
        }
        return pvs;
    }
}

依赖注入DI->初始化

  • 各种Aware接口 BeanNameAware#setBeanName() BeanClassLoaderAware#setBeanClassLoader() BeanFactoryAware#setBeanFactory()
  • BeanPostProcessor#beforeInitialization后置处理器的前置过程 初始化前,如@PostConstruct @ConfigurationProperties

初始化->完整对象

  • InitializingBean#afterPropertiesSet() `的init-method属性的初始化方法
  • BeanPostProcessor#afterInitialization后置处理器的后置过程 初始化后,返回对象会替换原本的bean,如AOP

redisTemplate的afterPropertiesSet使用:
在这里插入图片描述

三级缓存解决循环依赖

https://www.51cto.com/article/747437.html

循环依赖:A的属性是B,B的属性是A

首先是加一层缓存,key为beanName,value是对象实例,由于实例化和初始化分开的,A实例化后会放入缓存,A polulateBean() 依赖B,然后B实例化、填充属性的时候就从缓存中获取到A,解决了循环依赖。

但是有个问题,bean有可能创建完成、有可能正在创建还没注入依赖,参杂到一起,因此需要搞个二级缓存。

  • 新增二级缓存,存放刚实例化的bean
  • 当bean完成初始化,放入一级缓存,删掉二级缓存,只保留完整的bean即可

但是java有代理,怎么区分代理对象和普通对象?如果存在代理对象的循环依赖怎么处理?
按二级缓存的,最终在一级缓存中的对象A是一个proxy_A,但是对象B依赖的对象A却是一个普通A

需要三级缓存。
在这里插入图片描述

与二级缓存的不同之处:

  • 在获取到A的时候创建proxy_A。同时将其加入二级缓存,并返回给B,B就依赖了proxy_A
  • A初始化过程会创建代理对象,查二级缓存有没得proxy_A,有的话选择二级缓存的proxy_A存入一级缓存并返回

spring的更屌,第三级缓存不是存入了实例化的对象,而是存入了一个匿名类ObjectFactory,用于延迟初始化的工厂,它知道如何创建和返回最终的 Bean 实例。按需完成初始化,更加灵活,也减少了内存占用。

getEarlyBeanReference函数的实现中会调用BeanPostProcessor执行用户自定义的逻辑。


boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
    if (logger.isDebugEnabled()) {
        logger.debug("Eagerly caching bean '" + beanName +
                     "' to allow for resolving potential circular references");
    }
    // addSingletonFactory方法是将bean加入三级缓存中
    // 三级缓存会被ObjectFactory包装
    // getEarlyBeanReference方法会执行Bean的后置处理器
    addSingletonFactory(beanName, new ObjectFactory<Object>() {
        @Override
        public Object getObject() throws BeansException {
            return getEarlyBeanReference(beanName, mbd, bean);
        }
    });
}
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
				exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
			}
		}
		return exposedObject;
	}

AOP

Spring AOP(Aspect-Oriented Programming,面向切面编程)是一种用于实现横切关注点(cross-cutting concerns)的技术,如日志记录、事务管理、安全检查等。Spring AOP 允许在不改变业务逻辑代码的情况下,将这些横切关注点分离出来,提升代码的可维护性和复用性。

Spring AOP 的原理

Spring AOP 的实现基于代理模式,主要使用了两种代理机制:

  1. JDK 动态代理:用于代理实现了接口的类。
  2. CGLIB 代理:用于代理没有实现接口的类,通过生成子类来实现代理。

核心概念

  • 切面(Aspect):切面是横切关注点的模块化。
  • 连接点(JoinPoint):程序执行过程中的某个点,如方法调用。
  • 通知(Advice):切面在连接点执行的动作(如前置通知、后置通知、异常通知等)。
  • 切入点(Pointcut):定义了横切关注点应该应用到哪些连接点上。
  • 引入(Introduction):在不修改现有类的前提下,向类中添加新的方法或属性。
  • 织入(Weaving):将切面应用到目标对象,创建代理对象的过程。

实现机制

Spring AOP 主要通过 AopProxy 接口及其实现类来创建代理对象。

JDK 动态代理

当目标类实现了接口时,Spring 使用 JDK 动态代理来创建代理对象。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkDynamicProxy implements InvocationHandler {
    private final Object target;

    public JdkDynamicProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method execution");
        Object result = method.invoke(target, args);
        System.out.println("After method execution");
        return result;
    }

    public static void main(String[] args) {
        TargetInterface target = new TargetClass();
        TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new JdkDynamicProxy(target)
        );

        proxy.targetMethod();
    }
}

interface TargetInterface {
    void targetMethod();
}

class TargetClass implements TargetInterface {
    @Override
    public void targetMethod() {
        System.out.println("Target method execution");
    }
}

CGLIB 代理

当目标类没有实现接口时,Spring 使用 CGLIB 来生成目标类的子类作为代理对象。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    private final Object target;

    public CglibProxy(Object target) {
        this.target = target;
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method execution");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method execution");
        return result;
    }

    public static void main(String[] args) {
        TargetClass target = new TargetClass();
        TargetClass proxy = (TargetClass) Enhancer.create(
                target.getClass(),
                new CglibProxy(target)
        );

        proxy.targetMethod();
    }
}

class TargetClass {
    public void targetMethod() {
        System.out.println("Target method execution");
    }
}

Spring AOP 配置

Spring AOP 可以通过 XML 配置和注解配置两种方式来定义切面和通知。

XML 配置
<!-- Spring AOP 配置 -->
<aop:config>
    <aop:aspect id="myAspect" ref="myAspectBean">
        <aop:pointcut id="myPointcut" expression="execution(* com.example.service.*.*(..))"/>
        <aop:before pointcut-ref="myPointcut" method="beforeAdvice"/>
        <aop:after pointcut-ref="myPointcut" method="afterAdvice"/>
    </aop:aspect>
</aop:config>

<bean id="myAspectBean" class="com.example.aspect.MyAspect"/>

注解配置
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;

@Aspect
public class MyAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }

    @After("execution(* com.example.service.*.*(..))")
    public void afterAdvice() {
        System.out.println("After method execution");
    }
}

@Configuration
@EnableAspectJAutoProxy
public class AppConfig {

    @Bean
    public MyAspect myAspect() {
        return new MyAspect();
    }

    @Bean
    public MyService myService() {
        return new MyService();
    }
}

AOP 代理的创建时机

  1. BeanDefinition 解析阶段:Spring 在解析 Bean 定义时,根据切面配置(如 @Aspect 或 XML 配置)生成相关的 BeanDefinition
  2. Bean 实例化阶段:在 Bean 实例化时,Spring 判断是否需要为该 Bean 创建代理对象。如果需要,Spring 使用 AopProxy 创建代理对象并替换原始 Bean。
  3. 应用通知(Advice):代理对象调用目标方法前后,执行相应的通知(如前置通知、后置通知)。

spring的事务管理

1. 声明式事务管理

声明式事务管理是通过配置或注解来管理事务,而无需在代码中显式地进行事务管理操作。Spring 提供了 @Transactional 注解来实现这一点。

@Service
public class MyService {

    @Autowired
    private MyRepository myRepository;

    @Transactional
    public void performTransactionalOperation() {
        myRepository.save(new Entity());
        // other database operations
    }
}

在上述代码中,performTransactionalOperation 方法被标记为事务性操作。Spring 将确保这个方法在事务上下文中执行,任何异常都会导致事务回滚。

2. 事务传播行为

Spring 支持多种事务传播行为,这些行为定义了事务方法如何与现有的事务关联。主要的传播行为包括:

  • REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • REQUIRES_NEW:总是创建一个新的事务。如果当前存在事务,则将当前事务挂起。
  • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • NESTED:如果当前存在事务,则在嵌套事务中执行。如果当前没有事务,则创建一个新的事务。
  • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  • NOT_SUPPORTED:总是以非事务方式执行。如果当前存在事务,则将当前事务挂起。
  • NEVER:总是以非事务方式执行。如果当前存在事务,则抛出异常。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // transactional code
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    // transactional code
}

3. 编程式事务管理

编程式事务管理允许开发者在代码中显式地控制事务。Spring 提供了 TransactionTemplatePlatformTransactionManager 来实现编程式事务管理。

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void performTransactionalOperation() {
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                // transactional code
            }
        });
    }
}

4. 事务回滚规则

默认情况下,Spring 只在遇到未检查(运行时)异常时回滚事务。可以通过 @Transactional 注解的 rollbackFornoRollbackFor 属性来指定其他回滚规则。

@Transactional(rollbackFor = Exception.class)
public void performTransactionalOperation() throws Exception {
    // transactional code
}

5. 事务管理器

Spring 支持多种事务管理器,以满足不同的数据访问技术需求。常用的事务管理器包括:

  • DataSourceTransactionManager:用于管理 JDBC 事务。
  • JpaTransactionManager:用于管理 JPA 事务。
  • HibernateTransactionManager:用于管理 Hibernate 事务。
  • JtaTransactionManager:用于分布式事务管理。
@Configuration
@EnableTransactionManagement
public class AppConfig {

    @Bean
    public DataSource dataSource() {
        // configure and return DataSource
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

6. 事务拦截器

Spring 使用 AOP(Aspect-Oriented Programming)来实现声明式事务管理。@Transactional 注解的处理实际上是通过事务拦截器实现的。事务拦截器会在方法调用前后应用事务逻辑,确保事务的正确开始和结束。

Spring MVC中的DispatcherServlet的作用是什么

![[Pasted image 20240624073531.png]]

DispatcherServlet 是核心组件,负责将 HTTP 请求分发给合适的处理器(Controller)。它是整个 Spring MVC 框架的前端控制器(Front Controller),起着中央调度器的作用。

DispatcherServlet 的工作流程

DispatcherServlet 的工作流程可以分为以下几个步骤:

  1. 接收请求

    • DispatcherServlet 接收客户端发送的 HTTP 请求。
  2. 处理器映射

    • DispatcherServlet 根据请求的 URL,利用 HandlerMapping 来找到对应的处理器(Controller)。
    • HandlerMapping 通过配置的映射规则来匹配请求和处理器。
  3. 调用处理器

    • 找到合适的处理器(Controller)后,DispatcherServlet 调用处理器的方法来处理请求。
    • 处理器返回一个 ModelAndView 对象,其中包含了模型数据和视图名称。
  4. 视图解析

    • DispatcherServlet 利用 ViewResolver 解析 ModelAndView 中的视图名称,找到对应的视图对象。
    • 常用的视图解析器包括 InternalResourceViewResolver(解析 JSP)、ThymeleafViewResolver(解析 Thymeleaf 模板)等。
  5. 视图渲染

    • 将模型数据传递给视图对象,并将视图渲染成 HTML 响应。
    • DispatcherServlet 将生成的 HTML 响应返回给客户端。

DispatcherServlet 的配置

在 Spring MVC 中,DispatcherServlet 通常在 web.xml 文件中配置,但在 Spring Boot 中,可以通过注解和配置类来自动配置。

<web-app>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Spring Boot 自动配置 DispatcherServlet,因此通常不需要手动配置。只需在应用程序主类上使用 @SpringBootApplication 注解,并确保 spring-boot-starter-web 依赖已包含在项目中

Spring框架中的@Value注解是如何注入配置值的?

在 Spring 框架中,@Value 注解用于将外部配置文件中的值或者其他 Spring 管理的 Bean 的属性值注入到当前的 Bean 的字段、方法参数或构造函数参数中。通过 @Value 注解,可以方便地将配置值注入到 Spring 管理的组件中,例如 @Component@Service@Controller 等。

如果配置文件中的值是一个逗号分隔的字符串(如 myapp.names=John,Doe),Spring 将会自动将其转换为数组或集合类型,并注入到相应的字段中

@Component
public class MyComponent {

    @Value("${myapp.names}")
    private String[] names;

    @Value("${myapp.emails}")
    private List<String> emails;

    // Getter and Setter
}

myapp.username=admin
myapp.password=secret
myapp.names=John,Doe
myapp.emails=example1@example.com,example2@example.com

Spring框架中的Spring Security是如何提供安全性的?

Spring Security 是 Spring 框架中专门用于提供身份认证(Authentication)和授权(Authorization)功能的安全性框架。它能够有效地保护应用程序的安全性,防止恶意攻击和非法访问。以下是 Spring Security 提供安全性的主要特点和功能:

主要功能和特点:

  1. 身份认证(Authentication)

    • Spring Security 提供了多种身份认证方式,包括基于表单登录、HTTP Basic 认证、HTTP Digest 认证、LDAP 认证等。
    • 可以通过配置简单的用户名密码认证,也可以扩展到多种复杂的认证机制,例如 OAuth2、OpenID Connect 等。
  2. 授权(Authorization)

    • 通过角色(Roles)或权限(Permissions)控制对应用程序资源的访问权限。
    • 可以基于注解、XML 配置或者编程方式实现细粒度的权限控制,如 @Secured@PreAuthorize@PostAuthorize 等。
  3. 安全配置

    • 可以通过 Java 配置或者 XML 配置对安全规则进行灵活配置,定义哪些 URL 需要保护、哪些请求需要认证、如何处理登录和注销等行为。
  4. Session 管理

    • Spring Security 提供了对 Session 的管理,包括限制并发登录数、Session 超时处理、Session 注销和过期处理等功能,防止会话劫持和滥用。
  5. CSRF 防护

    • 提供了 CSRF(Cross-Site Request Forgery)攻击防护机制,通过生成和验证 CSRF Token 防止跨站请求伪造攻击。
  6. 密码加密

    • 提供了密码编码器(PasswordEncoder)接口和多种实现,支持密码的加密和验证,确保用户密码的安全存储和传输。
  7. 集成 Spring 框架

    • 与 Spring 框架完全集成,可以方便地与 Spring MVC、Spring Boot 等其他 Spring 组件和框架无缝协作。

工作原理:

Spring Security 的核心是一系列的过滤器链(Filter Chain),每个过滤器负责不同的安全任务,例如身份验证、授权、会话管理等。在应用程序启动时,Spring Security 会自动装配这些过滤器,并根据配置对请求进行安全性检查和处理。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("{noop}password").roles("USER")
                .and()
                .withUser("admin").password("{noop}password").roles("ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .logoutUrl("/logout")
                .permitAll();
    }
}

springboot是什么

spring下的子项目,14年发布,基于spring4.0设计,通过简化配置进一步简化spring应用的开发过程,解决的痛点:

  • 复杂的配置管理 传统spring程序需要编写大量的 XML 配置文件来管理各种 Bean、数据库连接、事务管理等,配置繁琐且容易出错 springboot通过自动配置(Auto Configuration)和约定优于配置(Convention over Configuration)的理念,减少了手动配置的工作量
  • 依赖管理 传统项目需要手动管理大量的依赖项,有各种依赖冲突和版本问题,springboot提供一系列starter,简化依赖管理
  • 部署,传统项目需要打war包,部署到tomcat,过程复杂;springboot内置tomcat等servlet容器,项目打成jar包,直接java -jar
  • 其他,如环境变量profile、监控actuator等

spring boot自动装配

Spring Boot自动配置主要是@EnableAutoConfiguration实现的

核心:两个注解

@Import注解: spring3.0

用于导入一个或多个配置类或组件类,以便将它们注册到当前的 Spring 应用程序上下文中,在需要将外部配置类或第三方库中的配置类集成到现有应用程序中

使用场景

  1. 模块化配置:将应用程序的配置分成多个模块,并通过 @Import 将它们组合起来。
  2. 第三方库集成:将第三方库的配置类导入到应用程序上下文中。
  3. 条件配置:根据特定条件有选择地导入配置类。

动态导入,可以实现 ImportSelector 接口,来根据动态条件选择导入的配置类

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {
            "com.example.FirstConfig",
            "com.example.SecondConfig"
        };
    }
}

@Import(MyImportSelector.class)
public class MainConfig {
}

也可以结合spring4.0的@Conditional注解基于条件导入

@Configuration
@Conditional(MyCondition.class)
public class ConditionalConfig {
    @Bean
    public String conditionalBean() {
        return "This bean is conditionally loaded";
    }
}

public class MyCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 自定义的条件逻辑,返回 true 表示条件满足,返回 false 表示条件不满足
        // 例如,可以根据环境变量、系统属性等来判断
        String myProperty = context.getEnvironment().getProperty("my.property");
        return "enabled".equals(myProperty);
    }
}

@Import(ConditionalConfig.class)
public class MainConfig {
}


# application.properties
my.property=enabled

my.property 的值为 “enabled” 时,Spring 容器会加载 conditionalBean

springboot的条件注解增强

@ConditionalOnClass
当类路径中存在指定的类时,自动配置生效
@ConditionalOnClass(name = "org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType")

@ConditionalOnMissingClass
当类路径中不存在指定的类时,自动配置生效
@ConditionalOnMissingClass("org.springframework.data.redis.core.RedisTemplate")

@ConditionalOnBean
当容器中存在指定的 Bean 时,自动配置生效。
@ConditionalOnBean(DataSource.class)

@ConditionalOnMissingBean
当容器中不存在指定的 Bean 时,自动配置生效
@ConditionalOnMissingBean(DataSource.class)

@ConditionalOnProperty
当配置文件中指定的属性存在并且值符合条件时,自动配置生效
@ConditionalOnProperty(name = "myapp.feature.enabled", havingValue = "true")

@ConditionalOnResource
当指定的资源存在时,自动配置生效。
@ConditionalOnResource(resources = "classpath:data.sql")

自动装配

主要由@EnableAutoConfiguration实现,添加了@EnableAutoConfiguration注解,会导入AutoConfigurationImportSelector类,里面的selectImports方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有含有META-INF/spring.factoriesjar包,将对应key@EnableAutoConfiguration注解全名对应的value类全部装配到IOC容器中

自动配置的工作流程

  1. SpringApplication 初始化:当应用程序启动时,SpringApplication 类被初始化。

  2. 加载 spring.factories 文件:Spring Boot 会扫描所有位于类路径下的 META-INF/spring.factories 文件,加载所有配置的自动配置类。

  3. 条件匹配:对于每个自动配置类,Spring Boot 会根据类上标注的条件注解(如 @ConditionalOnClass@ConditionalOnMissingBean 等)进行条件匹配。如果条件满足,则创建对应的 Bean 并注入应用上下文中。

  4. 应用自动配置:符合条件的自动配置类将被应用,自动配置相应的 Bean 和属性。

以redis-starter为例整个流程

在你的 pom.xml 文件中添加 spring-boot-starter-data-redis 依赖。Spring Boot 会自动扫描项目中所有的 META-INF/spring.factories 文件,并根据这些文件中的配置加载相应的自动配置类。

对于 Redis,这个文件的内容可能包含如下行

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

3. Redis 自动配置类

RedisAutoConfiguration 类是自动配置 Redis 所需的核心类。这个类位于 org.springframework.boot.autoconfigure.data.redis 包中,并包含了许多与 Redis 相关的 Bean 定义和配置。

@Configuration
@ConditionalOnClass(RedisTemplate.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        return new StringRedisTemplate(redisConnectionFactory);
    }
}

4. 条件注解的作用

自动配置类使用了多个条件注解来确保在合适的环境中进行配置。

  • @ConditionalOnClass(RedisTemplate.class):确保只有在类路径中存在 RedisTemplate 类时才进行配置。
  • @ConditionalOnMissingBean(name = "redisTemplate"):确保只有在没有定义名为 redisTemplate 的 Bean 时才创建 RedisTemplate Bean。

5. 配置属性的绑定

自动配置类通过 @EnableConfigurationProperties(RedisProperties.class) 注解来绑定 Redis 配置属性,这些属性通常在 application.propertiesapplication.yml 文件中定义。

spring.redis.host=localhost
spring.redis.port=6379
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    private String host;
    private int port;
    // getters and setters
}

6. 连接工厂的配置

RedisAutoConfiguration 类会导入 LettuceConnectionConfigurationJedisConnectionConfiguration,这些类负责配置具体的 Redis 连接工厂,如 Lettuce 或 Jedis

@Configuration
@ConditionalOnClass(RedisClient.class)
@ConditionalOnMissingBean(RedisConnectionFactory.class)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {

    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public LettuceConnectionFactory redisConnectionFactory(
            ClientResources clientResources, ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
            ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider) {
        // 配置 Lettuce 连接工厂
    }
}

7. Bean 的创建和初始化

在自动配置类中定义的 Bean,如 RedisTemplateStringRedisTemplate,会在应用启动时根据条件自动创建和初始化。这些 Bean 通过 Redis 连接工厂与 Redis 服务器进行交互。

8. 使用 Redis

一旦自动配置完成,你就可以在你的应用中直接使用 Redis 的功能,而不需要手动配置

当你在 Spring Boot 项目中添加 spring-boot-starter-data-redis 依赖时,Spring Boot 会通过以下步骤实现自动装配:

  1. 加载 spring.factories 文件中定义的自动配置类 RedisAutoConfiguration
  2. RedisAutoConfiguration 类会根据条件注解和属性配置创建必要的 Redis 相关 Bean。
  3. 应用启动时,这些 Bean 会被自动创建和初始化,使得你可以直接使用 Redis 的功能,而无需手动配置。
  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Golang是一种新兴的程序设计语言,被广泛应用于网络开发、云计算等领域。50k的高阶面试题对于不少求职者来说是一道难题,需要具备深厚的编程基础和实践经验,所以需要在以下几个方面下功夫: 1. 熟练掌握Golang的语法和特性,包括但不限于变量、函数、结构体、接口、包、并发等。透彻理解Golang的设计理念和哲学,掌握其优秀的并发性能和简洁的语法结构。 2. 掌握Golang的标准库和第三方库,并深入了解其内部实现和使用方法。掌握Golang的常用框架和工具,如Beego、Gin、Echo等,能够在实际工作中熟练使用。 3. 掌握Golang的性能优化和高可用架构设计,能够对应用程序进行优化,包括优化算法、内存管理、并发控制等方面。同时了解分布式系统、消息队列、容器化等技术,为应用程序提供高可用性和弹性。 4. 具备实际应用经验,能够解决实际工作中出现的各种问题。理解业务需求和用户体验,并能够为其提供创新的解决方案。 总之,要成为一名合格的Golang工程师,需要具备深厚的编程背景和实践经验。不断学习和深入掌握Golang的特性和应用场景,才能在高阶面试中获得成功。 ### 回答2: Golang 50K高阶面试题主要包括了Go语言的基础概念、并发编程、内存管理、网络等多个方面的考察。以下是对其中一些难点的简要解答。 并发编程: Go语言天生支持并发编程,因此并发编程是其核心优势之一。与其它编程语言不同,Go语言提供了一些内置的原语,如goroutine和channel,以便于编写并发代码。Goroutine是一个轻量级的线程,可与其他goroutine一起运行,而channel是用于在goroutine之间进行通信的并发安全的数据结构。通过这些特性,Go语言为开发并发程序提供了显着的便利。 内存管理: Go语言的内存管理是自动化的,开发者无需手动进行内存分配及释放。Go的垃圾回收机制可以自动检测不再使用的变量并将其回收,以便于再次利用。同时,Go语言还支持指针操作,但使用指针时需注意指针的生命周期以及指针空间的释放等问题,以免引起内存泄漏或者指针悬空等问题。 网络编程: Go语言提供了内置的网络库,能够轻松地进行TCP/IP和HTTP等协议的编程。与其它语言比较,Go语言提供了更高层次的API,使得开发网络应用变得更加简单,开发人员可以用更少更简洁的代码来实现同样的功能。 总的来说,Golang 50K高阶面试题主要考察了面试者在Go语言开发中的实际应用及其优势,特别是在并发编程、内存管理和网络编程等方面的应用。如果您希望在面试中表现更出色,需要熟练掌握Go语言的基本语法及其特性,并具有实际的项目经验。 ### 回答3: 首先,Golang 是一种高效、简洁、快速的编程语言,它在一定程度上成功地解决了传统语言的缺点,如并行编程的困难和内存泄漏等问题。而对于 Golang 高阶面试题,我们需要了解一些关键知识点。 首先是 Golang 的并发处理。Golang 的并发是基于 Goroutine 和 Channel 实现的,Goroutine 是轻量级线程,可以实现高度并发。而 Channel 是用来在 Goroutine 之间传递数据的通信机制。在面试中,常被问到如何确保 Goroutine 安全并实现并发。这时我们需要回答一些并发编程的关键问题,如共享资源、锁的使用和死锁避免等。 其次是 Golang 的内存管理。Golang 通过自动垃圾回收技术来实现内存管理和防止内存泄漏,而在面试中,常被问及如何避免内存泄漏,如何手动管理内存等问题,需要对 Golang 内存管理和垃圾回收机制有深入了解。 还有一些其他关键的问题,如异常处理和数据结构等,都需要我们进行深入学习和思考。在备战 Golang 的高阶面试时,除了掌握上述知识点外,还需要实践经验和多思考,不断完善自己的编程技能和能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值