文章目录
- 自定义拦截器
- 获取 Spring 容器对象
- 修改 BeanDefinition
- 添加BeanDefinition
- 测试
- 初始化 Bean 前后
- 初始化方法
- 使用@PostConstruct 注解
- 实现 InitializingBean 接口
- BeanFactoryPostProcessor 接口
- 关闭容器前
- 自定义作用域
自定义拦截器
spring mvc 拦截器的顶层接口是:HandlerInterceptor,包含三个方法:
- preHandle 目标方法
执行前执行
- postHandle 目标方法
执行后执行
- afterCompletion 请求完成时执行
一 般 情 况 会 用 HandlerInterceptor 接 口 的 实 现 类HandlerInterceptorAdapter 类。
假如有权限认证、日志、统计的场景,可以使用该拦截器
。
第一步,
继承 HandlerInterceptorAdapter 类
定义拦截器:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
public class AuthInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
String requestUrl = request.getRequestURI ();
if (checkAuth (requestUrl)) {
return true;
}
return false;
}
private boolean checkAuth(String requestUrl) {
System.out.println ("===权限校验===");
return true;
}
}
第二步,将
该拦截器注册到 spring 容器:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import javax.annotation.Resource;
@Configuration
public class WebAuthConfig extends WebMvcConfigurerAdapter {
@Resource
private AuthInterceptor authInterceptor;
@Bean
public AuthInterceptor getAuthInterceptor() {
return new AuthInterceptor ();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor (authInterceptor);
}
}
第三步,在请求接口时 spring mvc 通过该拦截器,能够自动拦截该接口,并且校验权限
获取 Spring 容器对象
在我们日常开发中,经常需要从 Spring 容器中获取 Bean
,但你知道如何获取 Spring 容器对象吗?
ApplicationListener 接口
@Service
public class PersonService2 implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void add() {
Person person = (Person) applicationContext.getBean ("person");
}
}
实现
ApplicationContextAware 接口,然后重写
setApplicationContext 方法,也能从该方法中获取到 spring 容器对象。
修改 BeanDefinition
Spring IOC 在实例化 Bean 对象之前,需要 先读取 Bean 的相关属性
,
保存到 BeanDefinition 对象中,然后通过 BeanDefinition 对象,实例化 Bean 对象。
如果想修改 BeanDefinition 对象中的属性,该怎么办呢?
答:我们可以
实现 BeanFactoryPostProcessor 接口
添加BeanDefinition
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException {
DefaultListableBeanFactory defaultListableBeanFactory =
(DefaultListableBeanFactory) configurableListableBeanFactory;
BeanDefinitionBuilder beanDefinitionBuilder =
BeanDefinitionBuilder.genericBeanDefinition (User.class);
beanDefinitionBuilder.addPropertyValue ("id", 123);
beanDefinitionBuilder.addPropertyValue ("name", "xiaoding");
defaultListableBeanFactory.registerBeanDefinition ("user",
beanDefinitionBuilder.getBeanDefinition ());
}
}
@Data
class User {
private int id;
private String name;
}
测试
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestBean {
@Resource
private User user;
@Test
public void test() {
System.out.println (user.getId ());
System.out.println (user.getName ());
}
}
在 postProcessBeanFactory 方法中,可以获取
BeanDefinition 的相关对象,并且修改该对象的属性。
初始化 Bean 前后
这时可以实现:BeanPostProcessor
接口。
该接口目前有两个方法:
- postProcessBeforeInitialization 该在初始化方法
之前调用
。 - postProcessAfterInitialization 该方法在初始化方法
之后调用
import lombok.Data;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean,
String beanName
) throws BeansException {
if (bean instanceof User) {
((User) bean).setUserName ("mr ding");
}
return bean;
}
}
@Data
class User {
private int id;
private String UserName;
}
如果
spring 中存在 User 对象,则将它的 userName 设置成:mr ding
初始化方法
- 使用**@PostConstruct** 注解
- 实现 InitializingBean 接口
使用@PostConstruct 注解
@Service
public class AService {
@PostConstruct
public void init() {
System.out.println ("===初始化===");
}
}
在需要初始化的方法上
增加@PostConstruct
注解,这样就有初始化的能力。
实现 InitializingBean 接口
@Service
public class BService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println ("===初始化===");
}
}
实现 InitializingBean 接口,重写 afterPropertiesSet 方法,该方法中可以完成初始化功能
BeanFactoryPostProcessor 接口
beanFactory后置处理器,可以
获取BeanDefinition 进行修改
@Component
class UserServiceImpl implements BeanFactoryPostProcessor {
@Override
//实现BeanFactoryPostProcessor ,可以获取beanDefinition ,修改bean的作用范围和className
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition userService = beanFactory.getBeanDefinition("userService");
userService.setScope("singleton");
userService.setBeanClassName("");
}
}
关闭容器前
有时候,我们需要在关闭 spring 容器前,做一些额外的工作
,比如:关闭资源文件
等。
这时可以实现 DisposableBean 接口,并且重写它的 destroy 方法
:
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
@Service
public class DService implements InitializingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println ("DisposableBean destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println ("InitializingBean afterPropertiesSet");
}
}
这样 spring 容器销毁前,会调用该 destroy 方法
,做一些额外的工作。
通常情况下,我们会同时实现 InitializingBean 和 DisposableBean接口,重写初始化方法和销毁方法
自定义作用域
我们都知道 spring 默认支持的 Scope 只有两种:
- singleton 单例,每次从 spring 容器中获取到的 bean 都是同一个对象。
- prototype 多例,每次从 spring 容器中获取到的 bean 都是不同的对象。
spring web 又对 Scope 进行了扩展,增加了:
- RequestScope 同一次请求从 spring 容器中获取到的 bean 都是同一个对象。
- SessionScope 同一个会话从 spring 容器中获取到的 bean 都是同一个对象。
即便如此,有些场景还是无法满足我们的要求。
比如,我们想在
同一个线程中
从 spring 容器获取到的 bean 都是同一个对象,该怎么办?
这就需要自定义 Scope
了。
第一步实现 Scope 接口
:
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
public class ThreadLocalScope implements Scope {
private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal ();
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Object value = THREAD_LOCAL_SCOPE.get ();
if (value != null) {
return value;
}
Object object = objectFactory.getObject ();
THREAD_LOCAL_SCOPE.set (object);
return object;
}
@Override
public Object remove(String name) {
THREAD_LOCAL_SCOPE.remove ();
return null;
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return null;
}
}
第二步将新定义的 Scope 注入到 spring 容器中:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope ("threadLocalScope", new ThreadLocalScope ());
}
}
第三步使用新定义的 Scope:
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
@Scope("threadLocalScope")
@Service
public class CService {
public void add() {
}
}