Mind mapping
Star: github.com/yehongzhi/l…
An overview of the
A good framework requires the use of various design patterns, and the Spring Framework is no exception. Since many articles on the web are quite messy, I want to summarize the design patterns used in Spring and hope that you will have a deeper understanding of Spring after reading them.
The factory pattern
The factory pattern is known to reduce class-to-class coupling by leaving the creation of objects to factories. The factory pattern is widely used in Spring. Examples are ApplicationContext and BeanFactory, which are the basis of Spring’s IOC container.
Let’s start with the BeanFactory, which is the lowest level interface.
public interface BeanFactory {
Object getBean(String name) throws BeansException;
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
/ / to omit...
}
Copy the code
ApplicationContext is an extension class and interface that creates all the beans at once when the container is started.
public interface ApplicationContext extends EnvironmentCapable.ListableBeanFactory.HierarchicalBeanFactory.MessageSource.ApplicationEventPublisher.ResourcePatternResolver {
@Nullable
String getId(a);
String getApplicationName(a);
String getDisplayName(a);
long getStartupDate(a);
@Nullable
ApplicationContext getParent(a);
AutowireCapableBeanFactory getAutowireCapableBeanFactory(a) throws IllegalStateException;
}
Copy the code
ApplicationContext has three implementation classes:
- ClassPathXmlApplication: Finds the specified XML configuration file from the ClassPath ClassPath, finds and loads the instantiation of ApplicationContext.
- FileSystemXMLApplicationContext: from the specified file system path to find the specified XML configuration files, find and load the ApplicationContext the instantiation of the work.
- XMLWebApplicationContext: Loads the Bean definition information from an XML file in the Web system. The Web application finds the specified XML configuration file, finds and loads the ApplicationContext to complete its instantiation.
So the relationship between these classes is clear, and the class diagram looks like this:
So where do we initialize it? It’s a little bit complicated, so I won’t go into it, but I’ll mention it. Basically see AbstractApplicationContext class refresh () method.
@Override
public void refresh(a) throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
/ / to omit...
try {
/ / to omit...
// Initialize all single-instance beans (no deadload configuration)
finishBeanFactoryInitialization(beanFactory);
}catch (BeansException ex) {
/ / to omit...
}finally {
/ / to omit...}}}Copy the code
The singleton pattern
In the system, there are many objects that we only need one, such as thread pool, Spring context object, log object, etc. The advantage of the singleton pattern is that for some heavyweight objects, it saves the time to create objects and reduces the overhead of the system. The second point is that using singleton can reduce the number of new operations and reduce the pressure on the GC thread to reclaim memory.
In fact, the default scope of a Bean in Spring is that of a Singleton. How do you do that?
Basically see DefaultSingletonBeanRegistry getSingleton () method:
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
ConcurrentHashMap, key: beanName --> value: bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
public Object getSingleton(String beanName, ObjectFactory
singletonFactory) {
Assert.notNull(beanName, "Bean name must not be null");
synchronized (this.singletonObjects) {
// Check if there is an instance in the cache. If there is an instance in the cache, return it directly
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
/ / to omit...
try {
// Get the singleton from singletonFactory
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
/ / to omit...
if(newSingleton) { addSingleton(beanName, singletonObject); }}// Return the instance
returnsingletonObject; }}protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName); }}}Copy the code
ConcurrentHashMap () returns if it exists in the Map, creates it if it does not exist, and puts it into the Map. The whole logic is wrapped in synchronous code blocks, so it is thread safe.
The strategy pattern
In a nutshell, the policy mode encapsulates a group of policy algorithms, and external clients choose different policy algorithms to solve problems according to different conditions. This is a design pattern used in many frameworks, as well as in everyday development. In Spring, my example here is the Resource class, which is the interface that all Resource access classes implement.
Spring defines implementation classes for different Resource classes for different ways of accessing resources. Let’s look at a class diagram:
A brief introduction to the Resource implementation class:
- UrlResource: Implementation class for accessing network resources.
- ServletContextResource: Implementation class that accesses resources relative to the ServletContext path.
- ByteArrayResource: Implementation class that accesses byte array resources.
- PathResource: Implementation class for accessing file path resources.
- ClassPathResource: Implementation class that accesses resources in the classload path.
Write pseudocode to demonstrate the use of the Resource class:
@RequestMapping(value = "/resource", method = RequestMethod.GET)
public String resource(@RequestParam(name = "type") String type,
@RequestParam(name = "arg") String arg) throws Exception {
Resource resource;
// This can be optimized to create an implementation class for Resource based on type through the factory pattern
if ("classpath".equals(type)) {
// Resources under classpath
resource = new ClassPathResource(arg);
} else if ("file".equals(type)) {
// Local file system resources
resource = new PathResource(arg);
} else if ("url".equals(type)) {
// Network resources
resource = new UrlResource(arg);
} else {
return "fail";
}
InputStream is = resource.getInputStream();
ByteArrayOutputStream os = new ByteArrayOutputStream();
int i;
while((i = is.read()) ! = -1) {
os.write(i);
}
String result = new String(os.toByteArray(), StandardCharsets.UTF_8);
is.close();
os.close();
return "type:" + type + ",arg:" + arg + "\r\n" + result;
}
Copy the code
That’s the idea of the strategy pattern, using different algorithms to solve problems based on external conditions. The getInputStream() method of each implementation class is different. The ClassPathResource is loaded by the class loader:
public class ClassPathResource extends AbstractFileResolvingResource {
private final String path;
@Nullable
private ClassLoader classLoader;
@Nullable
privateClass<? > clazz;@Override
public InputStream getInputStream(a) throws IOException {
InputStream is;
// Load resources under the classpath through the class loader
if (this.clazz ! =null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader ! =null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
// If the input stream is null, an error occurs
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
/ / returns an InputStream
returnis; }}Copy the code
Looking at the source of UrlResource, getting an implementation of InputStream is another strategy.
public class UrlResource extends AbstractFileResolvingResource {
@Nullable
private final URI uri;
private final URL url;
private final URL cleanedUrl;
@Override
public InputStream getInputStream(a) throws IOException {
// Get the connection
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try {
// Get the input stream and return
return con.getInputStream();
}
catch (IOException ex) {
// Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection) {
((HttpURLConnection) con).disconnect();
}
throwex; }}}Copy the code
The proxy pattern
The other core of Spring in addition to IOC(Inversion of Control) is AOP(aspect oriented programming). AOP can encapsulate logic (such as logging, permission control, etc.) that has nothing to do with business, but is called by business modules together, reducing system duplication code, reducing coupling between systems, and facilitating system maintenance and expansion.
Spring AOP is primarily implemented based on dynamic proxies, using JDK dynamic proxies if the class to be proxied implements an interface, and Cglib dynamic proxies if no interface is implemented.
Let’s look at the createAopProxy() method of DefaultAopProxyFactory, which Spring uses to create a dynamic proxy class:
public class DefaultAopProxyFactory implements AopProxyFactory.Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<? > targetClass = config.getTargetClass();if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return newJdkDynamicAopProxy(config); }}}Copy the code
JDK dynamic proxy and Cglib dynamic proxy differences:
- JDK dynamic proxies can only generate proxies for classes that implement the interface, not classes that do not implement the interface.
- The Cglib dynamic proxy can be used even if the proxied class does not implement an interface, because the Cglib dynamic proxy is extended by inheriting the proxied class.
- Cglib dynamic proxies override methods of the proxied class by inheritance, so methods that are final cannot be proxied.
If the interface is implemented, use the JDK dynamic proxy. If the interface is not implemented, use the Cglib dynamic proxy. You can also configure the Cglib dynamic proxy.
<aop:aspectj-autoproxy proxy-target-class="true"/>
Copy the code
Template pattern
The template pattern is used too much in Spring, defining the skeleton of an algorithm while deferring some steps to subclasses. Typically, an abstract class is defined as a skeleton, and subclasses override template methods in the abstract class to implement specific steps in the algorithm skeleton. The template pattern allows you to redefine specific steps of an algorithm without changing its structure.
Spring in the transaction manager is using the template pattern design, see the PlatformTransactionManager class first. This is the lowest level interface that defines methods for committing and rolling back.
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
Copy the code
Not surprisingly, the use of the abstract class as a skeleton, then see AbstractPlatformTransactionManager class.
@Override
public final void commit(TransactionStatus status) throws TransactionException {
/ / to omit...
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
/ / to omit...
/ / call processRollback ()
processRollback(defStatus, false);
return;
}
if(! shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {/ / to omit...
/ / call processRollback ()
processRollback(defStatus, true);
return;
}
/ / call processCommit ()
processCommit(defStatus);
}
// This method defines the skeleton, which calls a template method called doRollback()
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
if (status.hasSavepoint()) {
/ / to omit...
}
else if (status.isNewTransaction()) {
// Call the doRollback() template method
doRollback(status);
}
else {
/ / to omit...
}
// Omit a lot of code...
}
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
/ / to omit...
if (status.hasSavepoint()) {
/ / to omit...
}
else if (status.isNewTransaction()) {
/ / to omit...
// Call the doCommit() template method
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Omit a lot of code...
}
// Template method doRollback() to defer important steps to subclasses
protected abstract void doRollback(DefaultTransactionStatus status) throws TransactionException;
// The template method doCommit() deferred important steps to subclasses
protected abstract void doCommit(DefaultTransactionStatus status) throws TransactionException;
Copy the code
The template methods are implemented by the various transaction manager implementation classes, deferring the skeleton’s important doRollback() to subclasses. In general, the default is to use the Spring transaction manager implementation class is DataSourceTransactionManager.
/ / by inheritance AbstractPlatformTransactionManager abstract classes
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager.InitializingBean {
// Rewrite the doCommit() method to implement commit logic
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex); }}// Override the doRollback() method to implement the logic of rollback
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex); }}}Copy the code
If you are using the Hibernate framework, Hibernate has its own implementation, which reflects the open closed principle of design pattern, extending through inheritance or composition rather than directly modifying the class code. The transaction manager is HibernateTransactionManager Hibernate.
public class HibernateTransactionManager extends AbstractPlatformTransactionManager
implements ResourceTransactionManager.BeanFactoryAware.InitializingBean {
// Rewrite the doCommit() method to implement Hibernate specific commit logic
@Override
protected void doCommit(DefaultTransactionStatus status) {HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); Transaction hibTx = txObject.getSessionHolder().getTransaction(); Assert.state(hibTx ! =null."No Hibernate transaction");
if (status.isDebug()) {
logger.debug("Committing Hibernate transaction on Session [" +
txObject.getSessionHolder().getSession() + "]");
}
try {
hibTx.commit();
}
catch (org.hibernate.TransactionException ex) {
throw new TransactionSystemException("Could not commit Hibernate transaction", ex);
}
/ / to omit...
}
// Override doRollback() to implement Hibernate's specific rollback logic
@Override
protected void doRollback(DefaultTransactionStatus status) { HibernateTransactionObject txObject = (HibernateTransactionObject) status.getTransaction(); Transaction hibTx = txObject.getSessionHolder().getTransaction(); Assert.state(hibTx ! =null."No Hibernate transaction");
/ / to omit...
try {
hibTx.rollback();
}
catch (org.hibernate.TransactionException ex) {
throw new TransactionSystemException("Could not roll back Hibernate transaction", ex);
}
/ / to omit...
finally {
if(! txObject.isNewSession() && !this.hibernateManagedSession) { txObject.getSessionHolder().getSession().clear(); }}}}Copy the code
In fact, template pattern is often used in daily development, for example, in a method, before and after the code is the same, only a part of the middle operation is different, you can use template pattern to optimize the code, which can greatly reduce the redundant code, very practical.
Adapter pattern versus chain of responsibility pattern
The adapter pattern is a structural design pattern that enables objects with incompatible interfaces to cooperate to transform the interface of one class into the desired interface of another.
In SpringAOP, there is a very important function is to use Advice (Advice) to enhance the function of the proxyed class. Advice mainly includes MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice. Each Advice has a corresponding interceptor, as shown below:
Spring needs to wrap each Advice back to the container as a corresponding interceptor type, so the Advice needs to be transformed using the adapter pattern. There are three corresponding adapters, let’s look at the class diagram:
To see how the adapter converts the notification class and the interceptor class in Spring, let’s first look at the adapter interface. Two methods are defined, supportsAdvice() and getInterceptor().
public interface AdvisorAdapter {
// Determine if the notification class matches
boolean supportsAdvice(Advice advice);
// Pass in the notification class and return the corresponding interceptor class
MethodInterceptor getInterceptor(Advisor advisor);
}
Copy the code
The conversion method is getInterceptor(), which is provided by supportsAdvice(). We look at the implementation of the adapter class MethodBeforeAdviceAdapter pre notice.
class MethodBeforeAdviceAdapter implements AdvisorAdapter.Serializable {
// Determines whether the MethodBeforeAdvice notification class matches
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof MethodBeforeAdvice);
}
/ / incoming MethodBeforeAdvice, converted to MethodBeforeAdviceInterceptor interceptor class
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice();
return newMethodBeforeAdviceInterceptor(advice); }}Copy the code
In the getInterceptor() method, the constructor of the corresponding interceptor class is called to create the corresponding interceptor return, passing the advice class as an argument. Then we see MethodBeforeAdviceInterceptor interceptor.
public class MethodBeforeAdviceInterceptor implements MethodInterceptor.Serializable {
// A member variable notifies the class
private MethodBeforeAdvice advice;
/ / define a constructor, external through a constructor to create MethodBeforeAdviceInterceptor refs
public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
// When the interceptor's invoke method is called, the before() method of the notification class is called to implement pre-notification
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// Call the before() method of the notification class to implement the pre-notification
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
returnmi.proceed(); }}Copy the code
So where is the initialization of the adapter, we see DefaultAdvisorAdapterRegistry ().
public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry.Serializable {
private final List<AdvisorAdapter> adapters = new ArrayList<>(3);
public DefaultAdvisorAdapterRegistry(a) {
// Initialize the adapter and add it to the Adapters collection
registerAdvisorAdapter(new MethodBeforeAdviceAdapter());
registerAdvisorAdapter(new AfterReturningAdviceAdapter());
registerAdvisorAdapter(new ThrowsAdviceAdapter());
}
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
// Get all interceptors
@Override
public MethodInterceptor[] getInterceptors(Advisor advisor) throws UnknownAdviceTypeException {
List<MethodInterceptor> interceptors = new ArrayList<>(3);
Advice advice = advisor.getAdvice();
if (advice instanceof MethodInterceptor) {
interceptors.add((MethodInterceptor) advice);
}
// Run the adapters collection
for (AdvisorAdapter adapter : this.adapters) {
// Call the supportsAdvice() method to determine if the advisor has a matching adapter
if (adapter.supportsAdvice(advice)) {
// If it does, call getInterceptor() to convert it into the corresponding interceptor, which is added to the interceptors collectioninterceptors.add(adapter.getInterceptor(advisor)); }}if (interceptors.isEmpty()) {
throw new UnknownAdviceTypeException(advisor.getAdvice());
}
// returns the interceptor collection
return interceptors.toArray(new MethodInterceptor[0]); }}Copy the code
The adapter pattern here is to turn a notification class into an interceptor class, which is then added to the interceptor collection. Added to the collection of interceptor, with the chain of responsibility pattern, in ReflectiveMethodInvocation class is called, we see the JDK dynamic proxy JdkDynamicAopProxy invoke () method.
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
GetInterceptors () = getInterceptors();
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
if (chain.isEmpty()) {
/ / to omit...
}else {
// Create a MethodInvocation
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Call the proceed() method, which underlies a pointer to traverse the interceptor collection, and then implements pre-notification, etc
retVal = invocation.proceed();
}
/ / to omit...
}
Copy the code
Finally in the ReflectiveMethodInvocation calls proceed () method, the proceed () method is a recursive method, by the end of the pointer control recursive. This is a classic chain of responsibility pattern.
public class ReflectiveMethodInvocation implements ProxyMethodInvocation.Cloneable {
protected finalList<? > interceptorsAndDynamicMethodMatchers;/ / pointer
private int currentInterceptorIndex = -1;
protected ReflectiveMethodInvocation(Object proxy, @Nullable Object target, Method method, @Nullable Object[] arguments, @NullableClass<? > targetClass, List<Object> interceptorsAndDynamicMethodMatchers) {
/ / to omit...
// Set of interceptors
this.interceptorsAndDynamicMethodMatchers = interceptorsAndDynamicMethodMatchers;
}
@Override
@Nullable
public Object proceed(a) throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
// End of recursion
return invokeJoinpoint();
}
// Get interceptor, and the current pointer +1
Object interceptorOrInterceptionAdvice = this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Fail to match, skip, recurse to next
returnproceed(); }}else {
The invoke() method is then executed, and before(), afterReturning() and other member variables in the interceptor are called to implement pre-notification, post-notification, and exception notification
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); }}}Copy the code
This may be a little confusing for those of you who haven’t studied the Chain of Responsibility pattern, but it should be very easy to understand, and it’s almost identical to the logical implementation of SpringMVC’s interceptor.
Observer model
The observer pattern is an object behavior pattern in which when an object changes, the objects on which the object depends also react. The Spring event-driven model is a classic application of the Observer pattern.
The event character
In the Spring event-driven model, you begin with the event role ApplicationEvent, which is an abstract class with four implementation classes representing four kinds of events.
- ContextStartedEvent: Event triggered after ApplicationContext is started.
- ContextStoppedEvent: Event triggered when ApplicationContext stops.
- ContextRefreshedEvent: Event triggered after ApplicationContext has been initialized or refreshed.
- ContextClosedEvent: Event triggered when ApplicationContext is closed.
Event publisher
After the incident, need a publisher publish event, corresponding class is ApplicationEventPublisher publishers.
@FunctionalInterface
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
publishEvent((Object) event);
}
void publishEvent(Object event);
}
Copy the code
@functionalinterface indicates that this is a FunctionalInterface, and a FunctionalInterface has only one abstract method. The ApplicationContext class inherits again
ApplicationEventPublisher class, so we can use the ApplicationContext publishing events.
Event listener
Event listeners are defined by implementing the interface ApplicationListener, which is a functional interface with generics and requires that the E argument be a subclass of ApplicationEvent.
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
Copy the code
To demonstrate how to use it, first inherit the abstract class ApplicationEvent and define an event role, PayApplicationEvent.
public class PayApplicationEvent extends ApplicationEvent {
private String message;
public PayApplicationEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage(a) {
returnmessage; }}Copy the code
We then define a PayApplicationEvent listener for the PayApplicationEvent.
@Component
public class PayListener implements ApplicationListener<PayApplicationEvent> {
@Override
public void onApplicationEvent(PayApplicationEvent event) {
String message = event.getMessage();
System.out.println("Listen to the PayApplicationEvent with the message:"+ message); }}Copy the code
Finally, we publish events using ApplicationContext.
@SpringBootApplication
public class SpringmvcApplication {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = SpringApplication.run(SpringmvcApplication.class, args);
applicationContext.publishEvent(new PayApplicationEvent(applicationContext,"100 yuan paid successfully!")); }}Copy the code
After startup we can see the console print:
omg
In fact, the design patterns used in Spring are everywhere in the source code, and not just the ones I’ve listed, so Spring’s source code is well worth reading and learning. On the other hand, if you can’t design patterns, reading the source code is very laborious, so I recommend learning the design patterns before learning the source code.
I hope you have a better understanding of Spring after reading this article, so that’s it. Thank you for reading.
Please give me a thumbs-up if you think it is useful. Your thumbs-up is the biggest motivation for my creation
I’m a programmer who tries to be remembered. See you next time!!
Ability is limited, if there is any mistake or improper place, please criticize and correct, study together!