一、获取Spring容器对象
1.1 实现BeanFactoryAware接口
实现BeanFactoryAware接口,然后重写setBeanFactory
方法,就能从该方法中获取到Spring容器对象。
@Service
public class PersonService implements BeanFactoryAware {
private BeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
public void add() {
Person person = (Person) beanFactory.getBean("person");
}
}
1.2 实现ApplicationContextAware接口
实现ApplicationContextAware
接口,然后重写setApplicationContext
方法,也能从该方法中获取到Spring容器对象。
@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");
}
}
1.3 实现ApplicationListener接口
实现ApplicationListener
接口,需要注意的是该接口接收的泛型是ContextRefreshedEvent
类,然后重写onApplicationEvent
方法,也能从该方法中获取到Spring容器对象。
@Service
public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> {
private ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
applicationContext = event.getApplicationContext();
}
public void add() {
Person person = (Person) applicationContext.getBean("person");
}
}
二、初始化bean
Spring中支持3种初始化bean的方法:
xml中指定init-method方法
使用@PostConstruct注解
实现InitializingBean接口
第一种方法太古老了,现在用的人不多,具体用法就不介绍了。
2.1 使用@PostConstruct注解
在需要初始化的方法上增加@PostConstruct注解,这样就有初始化的能力。
@Service
public class AService {
@PostConstruct
public void init() {
System.out.println("===初始化===");
}
}
@Component
public class AlipayUtils {
@Resource
private AlipayConfigIOS configIOS;
@Resource
private AlipayConfigAndroid configAndroid;
public AlipayClient alipayClientIOS;
public AlipayClient alipayClientAndroid;
@PostConstruct
public void init(){
System.out.println("===初始化===");
//构建IOS
alipayClientIOS = new DefaultAlipayClient(
configIOS.getGateWay(),
configIOS.getAppId(),
configIOS.getAppPrivateKey(),
configIOS.getFormat(),
configIOS.getCharset(),
configIOS.getAliPayPublicKey(),
configIOS.getSignType());
//构建Android
alipayClientAndroid = new DefaultAlipayClient(
configAndroid.getGateWay(),
configAndroid.getAppId(),
configAndroid.getAppPrivateKey(),
configAndroid.getFormat(),
configAndroid.getCharset(),
configAndroid.getAliPayPublicKey(),
configAndroid.getSignType());
}
}
2.2 实现InitializingBean接口
实现InitializingBean
接口,重写afterPropertiesSet
方法,该方法中可以完成初始化功能。
@Service
public class BService implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("===初始化===");
}
}
三、自定义自己的Scope
我们都知道Spring默认支持的Scope只有两种:
- singleton 单例,每次从spring容器中获取到的bean都是同一个对象。
- prototype 多例,每次从spring容器中获取到的bean都是不同的对象。
Spring web又对Scope进行了扩展,增加了:
- RequestScope 同一次请求从spring容器中获取到的bean都是同一个对象。
- SessionScope 同一个会话从spring容器中获取到的bean都是同一个对象。
即便如此,有些场景还是无法满足我们的要求。比如,我们想在同一个线程中从spring容器获取到的bean都是同一个对象,该怎么办?这就需要自定义Scope了。
3.1 第一步实现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;
}
}
3.2 第二步将新定义的Scope注入到Spring容器中
@Component
public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());
}
}
3.3 第三步使用新定义的Scope
@Scope("threadLocalScope")
@Service
public class CService {
public void add() {
}
}
四、自定义类型转换
Spring目前支持3种类型转换器:
Converter<S,T>:将 S 类型对象转为 T 类型对象
ConverterFactory<S, R>:将 S 类型对象转为 R 类型及子类对象
GenericConverter:它支持多个source和目标类型的转化,同时还提供了source和目标类型的上下文,这个上下文能让你实现基于属性上的注解或信息来进行类型转换。
这3种类型转换器使用的场景不一样,我们以Converter<S,T>为例。假如:接口中接收参数的实体对象中,有个字段的类型是Date,但是实际传参的是字符串类型:2021-01-03 10:20:15,要如何处理呢?
4.1 第一步,定义一个实体User
@Data
public class User {
private Long id;
private String name;
private Date registerDate;
}
4.2 第二步,实现Converter接口
public class DateConverter implements Converter<String, Date> {
private static final String dateFormat = "yyyy-MM-dd HH:mm:ss";
private static final String shortDateFormat = "yyyy-MM-dd";
@Override
public Date convert(String source) {
if(StringUtils.isEmpty(value)) {
return null;
}
value = value.trim();
try {
if(value.contains("-")) {
SimpleDateFormat formatter;
if(value.contains(":")) {
formatter = new SimpleDateFormat(dateFormat);
}else {
formatter = new SimpleDateFormat(shortDateFormat);
}
Date dtDate = formatter.parse(value);
return dtDate;
}else if(value.matches("^\\d+$")) {
Long lDate = new Long(value);
return new Date(lDate);
}
} catch (Exception e) {
throw new RuntimeException(String.format("parser %s to Date fail", value));
}
throw new RuntimeException(String.format("parser %s to Date fail", value));
}
}
4.3 第三步,将新定义的类型转换器注入到Spring容器中
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new DateConverter());
}
}
4.4 第四步,调用接口
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/save")
public String save(@RequestBody User user) {
return "success";
}
}
请求接口时User对象中registerDate字段会被自动转换成Date类型。
五、Enable开关
不知道你有没有用过Enable开头的注解,比如:EnableAsync
、EnableCaching
、EnableAspectJAutoProxy
等,这类注解就像开关一样,只要在@Configuration
定义的配置类上加上这类注解,就能开启相关的功能,让我们一起实现一个自己的开关。
5.1 第一步,定义一个LogFilter
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("记录请求日志");
chain.doFilter(request, response);
System.out.println("记录响应日志");
}
@Override
public void destroy() {
}
}
5.2 第二步,注册LogFilter
@ConditionalOnWebApplication
public class LogFilterWebConfig {
@Bean
public LogFilter timeFilter() {
return new LogFilter();
}
}
注意,这里用了@ConditionalOnWebApplication注解,没有直接使用@Configuration注解。
5.3 第三步,定义开关@EnableLog注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogFilterWebConfig.class)
public @interface EnableLog {
}
5.4 第四步,启动类加上@EnableLog注解
只需在Springboot
启动类加上@EnableLog
注解即可开启LogFilter
记录请求和响应日志的功能。