Spring正如其名,给Java带来了春天;企业中如果使用Spring,可以大大降低开发的成本.
虽然日常工作中我们只要会使用Spring的功能就可以了
但是偶尔有些定制化需求或者BUG时,我们也可以修改其源码
Spring本身也是很优秀的代码,可以学习下设计模式
这节主要从这几个方面学习: Spring的作用,概览,看源码:BeanFactory和FactoryBean
Spring可以看作一个对象容器
如果没有Spring,我们怎么写一个工程?
假设有一个类使用频率很高,就需要在各个地方去new它;
但这样耦合度太高啦,假如某天不想要这个对象啦,想把这个对象换成另一个实现,就很麻烦,一个个去替换修改代码,这时候考虑通过对象工厂创建对象;
但这样代码跟工厂的耦合度又高了,假如哪天不想要这个工厂怎么办?不如干脆用反射
Class<Pet> clazz = Pet.class;
Pet pet = clazz.newInstance();
clazz.getFields();
clazz.getDeclaredFields();
其实Spring就是使用反射的方式去创建对象的,下面简单模拟一下核心流程:
(Spring为了支持扩展性,提供了很多接口,见下面的描述)
/**
* @author liweizhi
* @date 2020/7/5
*
* 扩展性非常重要:
* 1. 抽象
* 2. 设计模式
*
* Spring给出了扩展:
* 1. 在容器初始化之前的processer
* 2. 在创建对象之前的processer
* 3. 在不同的阶段发出不同的事件,listener
* 4. 面向接口编程
*/
public class DemoTest {
Class<UserController> aClass;
UserController userController;
@Before
public void init() {
try {
aClass = UserController.class;
userController = aClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void test01() throws Exception {
Field userServiceField = aClass.getDeclaredField("userService");
// 因为userService属性是私有的,所以设置这个 让JVM跳过检查;这里算是个障眼法,因为字段的访问标识符是在编译期字节码就确定的;
userServiceField.setAccessible(true);
Class<?> fieldType = userServiceField.getType();
Object fieldInstance = fieldType.newInstance();
// userServiceField.set(userController, userService);
String name = userServiceField.getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
String setMethodName = "set" + name;
Method method = aClass.getMethod(setMethodName, UserService.class);
method.invoke(userController, fieldInstance);
System.out.println(userController);
}
@Test
public void test02() {
Arrays.stream(aClass.getDeclaredFields()).forEach(field -> {
MyAutowired annotation = field.getAnnotation(MyAutowired.class);
if (annotation != null) {
field.setAccessible(true);
Class<?> fieldType = field.getType();
Object fieldInstance = null;
try {
/**
* 这里对象是直接newInstance出来的
* Spring中,这个bean是定义在注解或者xml中的
* 已经初始化过的单例对象放入容器中,用两个map维护,分别按id和Type存储
*/
fieldInstance = fieldType.newInstance();
field.set(userController, fieldInstance);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
});
System.out.println(userController);
}
}
/**
* @author liweizhi
* @date 2020/7/5
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAutowired {
}
@Data
public class UserController {
@MyAutowired
private UserService userService;
private String localName;
}
@Data
public class UserService {
private String name;
}
context上下文: 划分一个范围,在范围内活动的数据, 这个范围和数据就是上下文;
新建一个project,只进入spring-context包(4.x,没有webflux,看起来干净些),可以看到有这些包:
Spring容器的根接口
对象作用域:常见的有Singleton单例, Prototype(原型,每次新对象) ,还有request和session现在几乎不用啦
是一个注册中心
依赖注入,Dependency Injection
子工厂可以访问父工厂,当在本工厂找不到时,就去父工厂找; 子容器可以覆盖父容器的同名对象;(具体应用举例:spring-cloud-openfeign)
下面是BeanFactory的类注释
/**
* The root interface for accessing a Spring bean container.
* This is the basic client view of a bean container;
* further interfaces such as {@link ListableBeanFactory} and
* {@link org.springframework.beans.factory.config.ConfigurableBeanFactory}
* are available for specific purposes.
*
* This interface is implemented by objects that hold a number of bean definitions,
* each uniquely identified by a String name. Depending on the bean definition,
* the factory will return either an independent instance of a contained object
* (the Prototype design pattern), or a single shared instance (a superior
* alternative to the Singleton design pattern, in which the instance is a
* singleton in the scope of the factory). Which type of instance will be returned
* depends on the bean factory configuration: the API is the same. Since Spring
* 2.0, further scopes are available depending on the concrete application
* context (e.g. "request" and "session" scopes in a web environment).
*
*
The point of this approach is that the BeanFactory is a central registry
* of application components, and centralizes configuration of application
* components (no more do individual objects need to read properties files,
* for example). See chapters 4 and 11 of "Expert One-on-One J2EE Design and
* Development" for a discussion of the benefits of this approach.
*
*
Note that it is generally better to rely on Dependency Injection
* ("push" configuration) to configure application objects through setters
* or constructors, rather than use any form of "pull" configuration like a
* BeanFactory lookup. Spring's Dependency Injection functionality is
* implemented using this BeanFactory interface and its subinterfaces.
*
*
Normally a BeanFactory will load bean definitions stored in a configuration
* source (such as an XML document), and use the {@code org.springframework.beans}
* package to configure the beans. However, an implementation could simply return
* Java objects it creates as necessary directly in Java code. There are no
* constraints on how the definitions could be stored: LDAP, RDBMS, XML,
* properties file, etc. Implementations are encouraged to support references
* amongst beans (Dependency Injection).
*
*
In contrast to the methods in {@link ListableBeanFactory}, all of the
* operations in this interface will also check parent factories if this is a
* {@link HierarchicalBeanFactory}. If a bean is not found in this factory instance,
* the immediate parent factory will be asked. Beans in this factory instance
* are supposed to override beans of the same name in any parent factory.
*
* **Bean的生命周期**
*
Bean factory implementations should support the standard bean lifecycle interfaces
* as far as possible. The full set of initialization methods and their standard order is:
*
* - BeanNameAware's {@code setBeanName}
*
- BeanClassLoaderAware's {@code setBeanClassLoader}
*
- BeanFactoryAware's {@code setBeanFactory}
*
- EnvironmentAware's {@code setEnvironment}
*
- EmbeddedValueResolverAware's {@code setEmbeddedValueResolver}
*
- ResourceLoaderAware's {@code setResourceLoader}
* (only applicable when running in an application context)
*
- ApplicationEventPublisherAware's {@code setApplicationEventPublisher}
* (only applicable when running in an application context)
*
- MessageSourceAware's {@code setMessageSource}
* (only applicable when running in an application context)
*
- ApplicationContextAware's {@code setApplicationContext}
* (only applicable when running in an application context)
*
- ServletContextAware's {@code setServletContext}
* (only applicable when running in a web application context)
*
- {@code postProcessBeforeInitialization} methods of BeanPostProcessors
*
- InitializingBean's {@code afterPropertiesSet}
*
- a custom init-method definition
*
- {@code postProcessAfterInitialization} methods of BeanPostProcessors
*
*
* On shutdown of a bean factory, the following lifecycle methods apply:
*
* - {@code postProcessBeforeDestruction} methods of DestructionAwareBeanPostProcessors
*
- DisposableBean's {@code destroy}
*
- a custom destroy-method definition
*
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 13 April 2001
* @see BeanNameAware#setBeanName
* @see BeanClassLoaderAware#setBeanClassLoader
* @see BeanFactoryAware#setBeanFactory
* @see org.springframework.context.ResourceLoaderAware#setResourceLoader
* @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher
* @see org.springframework.context.MessageSourceAware#setMessageSource
* @see org.springframework.context.ApplicationContextAware#setApplicationContext
* @see org.springframework.web.context.ServletContextAware#setServletContext
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization
* @see InitializingBean#afterPropertiesSet
* @see org.springframework.beans.factory.support.RootBeanDefinition#getInitMethodName
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization
* @see DisposableBean#destroy
* @see org.springframework.beans.factory.support.RootBeanDefinition#getDestroyMethodName
*/
DI : Dependency Injection,依赖注入;我需要啥,他就给我注入啥
IOC: 控制反转,以前需要对象时我们自己new,现在交给Spring做依赖注入,这就是控制反转
AOP: 面向切面编程,一个点且多个面,一个面切多个点
实现原理: 反射,动态代理
Spring中如何实现: BeanPostProcessor层,运行期增强
运行时增强
编译时增强: aspectJ,lombok
载入时增强
https://github.com/spring-cloud/spring-cloud-openfeign,切换到release tag(v2.1.5.RELEASE)
feign作用:简化Http接口调用
FeignClientFactoryBean
FeignClientRegistrar
Used to dereference a {@link FactoryBean} instance and distinguish it from
beans created by the FactoryBean. For example, if the bean named
{@code myJndiObject} is a FactoryBean, getting {@code &myJndiObject}
will return the factory, not the instance returned by the factory.
Java里面没有指针,但是有很多和指针类似的概念
C语言中,假设int a = 1;
int *b = &a;
就是b存储了a的内容 “1” 在内存中的地址
对应到Spring的BeanFactory中,一般我们都是取FactoryBean中维护的对象
假如需要取FactoryBean本身,就需要在前面加FACTORY_BEAN_PREFIX表示区分
AbstractApplicationContext.finishBeanFactoryInitialization()中,会调用beanFactory.preInstantiateSingletons();
然后会调用DefaultListableBeanFactory.preInstantiateSingletons()
里面就是这么调用:
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
// do sth else
}else {
getBean(beanName);
}
/**
* Interface to be implemented by objects used within a {@link BeanFactory} which
* are themselves factories for individual objects. If a bean implements this
* interface, it is used as a factory for an object to expose, not directly as a
* bean instance that will be exposed itself.
*
* NB: A bean that implements this interface cannot be used as a normal bean.
* A FactoryBean is defined in a bean style, but the object exposed for bean
* references ({@link #getObject()}) is always the object that it creates.
*
*
FactoryBeans can support singletons and prototypes, and can either create
* objects lazily on demand or eagerly on startup. The {@link SmartFactoryBean}
* interface allows for exposing more fine-grained behavioral metadata.
*
*
This interface is heavily used within the framework itself, for example for
* the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
* {@link org.springframework.jndi.JndiObjectFactoryBean}. It can be used for
* custom components as well; however, this is only common for infrastructure code.
*
*
{@code FactoryBean} is a programmatic contract. Implementations are not
* supposed to rely on annotation-driven injection or other reflective facilities.
* {@link #getObjectType()} {@link #getObject()} invocations may arrive early in the
* bootstrap process, even ahead of any post-processor setup. If you need access to
* other beans, implement {@link BeanFactoryAware} and obtain them programmatically.
*
*
The container is only responsible for managing the lifecycle of the FactoryBean
* instance, not the lifecycle of the objects created by the FactoryBean. Therefore,
* a destroy method on an exposed bean object (such as {@link java.io.Closeable#close()}
* will not be called automatically. Instead, a FactoryBean should implement
* {@link DisposableBean} and delegate any such close call to the underlying object.
*
*
Finally, FactoryBean objects participate in the containing BeanFactory's
* synchronization of bean creation. There is usually no need for internal
* synchronization other than for purposes of lazy initialization within the
* FactoryBean itself (or the like).
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 08.03.2003
* @param the bean type
* @see org.springframework.beans.factory.BeanFactory
* @see org.springframework.aop.framework.ProxyFactoryBean
* @see org.springframework.jndi.JndiObjectFactoryBean
*/
ProxyFactoryBean
This interface is heavily used within the framework itself, for example for
the AOP {@link org.springframework.aop.framework.ProxyFactoryBean} or the
{@link org.springframework.jndi.JndiObjectFactoryBean}
org.springframework.cloud.openfeign.FeignClientFactoryBean
org.springframework.cloud.openfeign.FeignHttpClientUrlTests
看测试用例:
这个测试用例是自己定义了一个API,然后通过Feign访问那个API(getHello)
@Test
public void testBeanUrlNoProtocol() {
Hello hello = this.beanClientNoProtocol.getHello();
assertThat(hello).as("hello was null").isNotNull();
assertThat(hello).as("first hello didn't match")
.isEqualTo(new Hello("hello world 1"));
}
我们也可以在在其基础上,写个访问baidu的Feign-service:
@Autowired
private BaiduUrlClient baiduUrlClient;
// 这就是我们自定义的Feign-service了
@FeignClient(name = "baiduUrl", url = "https://www.baidu.com")
protected interface BaiduUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/")
String getHello();
}
// 这里测试用例是写的固定扫描几个类,加上我们自定义的类 BaiduUrlClient.class
// 项目中一般都是扫描包
@Configuration
@EnableAutoConfiguration
@RestController
@EnableFeignClients(clients = { UrlClient.class, BeanUrlClient.class,
BeanUrlClientNoProtocol.class, BaiduUrlClient.class })
@Import(NoSecurityConfiguration.class)
protected static class TestConfig {
// xxx 代码
}
// 测试代码
@Test
public void testBaiduUrl() {
String hello = this.baiduUrlClient.getHello();
System.out.println("testBaiduUrl========================\n" + hello);
}
上面的测试中,@FeignClient(name = “baiduUrl”, url = “https://www.baidu.com”)
,尽管我们只需要一个URL,不需要name,但是不输入name就会抛异常
可以修改一下源码,去掉这个机制
org.springframework.cloud.openfeign.FeignClientsRegistrar
我们一步步来,先把测试用例中的Feign-service的@FeignClient的name去掉,看哪里报错,然后就改那里
// 这就是我们自定义的Feign-service了
@FeignClient(url = "https://www.baidu.com")
protected interface BaiduUrlClient {
@RequestMapping(method = RequestMethod.GET, value = "/")
String getHello();
}
经过测试,改掉FeignClientsRegistrar的getClientName()和registerFeignClient()
private String getClientName(Map<String, Object> client) {
// xxxcode
// 主要是在最后,给个默认值
// 这里简单测试,随便写个
return "defaultName";
/*throw new IllegalStateException("Either 'name' or 'value' must be provided in @"
+ FeignClient.class.getSimpleName());*/
}
private void registerFeignClient(BeanDefinitionRegistry registry,
AnnotationMetadata annotationMetadata, Map<String, Object> attributes) {
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder
.genericBeanDefinition(FeignClientFactoryBean.class);
validate(attributes);
definition.addPropertyValue("url", getUrl(attributes));
definition.addPropertyValue("path", getPath(attributes));
String name = getName(attributes);
// 主要是再这里,加一个判断,如果name为空,就给个默认值
if (name == null || name.length() == 0) {
name = "defaultName";
}
// xxx code
}
然后改掉FeignClientFactoryBean的afterPropertiesSet()
// 这里有个参数校验,去掉他
@Override
public void afterPropertiesSet() throws Exception {
// Assert.hasText(this.contextId, "Context id must be set");
// Assert.hasText(this.name, "Name must be set");
}
现在就可以不输入name参数了
当然,改完后要跑一遍测试用例,保证其他的用例也不受影响
上面我们看到,FeignClientsRegistrar的registerFeignClient()中:
String className = annotationMetadata.getClassName();
definition.addPropertyValue(“type”, className);
这个type是个String类型的
但是FeignClientsRegistrar的type是Class>类型呀,setType(Class> type)接收的也是Class类型
那这是在哪里做的类型转换?Spring帮我们做的
头一次也是看的有点懵,但是大致思路是可以看出来的
FeignClientFactoryBean:
@Override
public Object getObject() throws Exception {
return getTarget();
}
/**
* @param the target type of the Feign client
* @return a {@link Feign} client created with the specified data and the context
* information
*/
<T> T getTarget() {
/*
FeignContext:
A factory that creates instances of feign classes.
It creates a Spring ApplicationContext per client name, and extracts the beans that it needs from there.
点击FeignContext,可以看到是在FeignAutoConfiguration中创建
*/
FeignContext context = this.applicationContext.getBean(FeignContext.class);
// 这个builder是建造者模式,点击查看feign()
Feign.Builder builder = feign(context);
// xxx code 剩下的就不重要了
}
protected Feign.Builder feign(FeignContext context) {
// 主要是这个get方法
FeignLoggerFactory loggerFactory = get(context, FeignLoggerFactory.class);
Logger logger = loggerFactory.create(this.type);
// @formatter:off
Feign.Builder builder = get(context, Feign.Builder.class)
// required values
.logger(logger)
.encoder(get(context, Encoder.class))
.decoder(get(context, Decoder.class))
.contract(get(context, Contract.class));
// @formatter:on
configureFeign(context, builder);
return builder;
}
protected <T> T get(FeignContext context, Class<T> type) {
// 主要是这个getInstance方法,会调用NamedContextFactory的getInstance()
T instance = context.getInstance(this.contextId, type);
if (instance == null) {
throw new IllegalStateException(
"No bean found of type " + type + " for " + this.contextId);
}
return instance;
}
NamedContextFactory:
private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();
public <T> T getInstance(String name, Class<T> type) {
AnnotationConfigApplicationContext context = getContext(name);
if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
type).length > 0) {
return context.getBean(type);
}
return null;
}
protected AnnotationConfigApplicationContext getContext(String name) {
if (!this.contexts.containsKey(name)) {
synchronized (this.contexts) {
if (!this.contexts.containsKey(name)) {
this.contexts.put(name, createContext(name));
}
}
}
return this.contexts.get(name);
}