diff --git a/build.gradle b/build.gradle index 299efbe9c806..7a3d6512c9cc 100644 --- a/build.gradle +++ b/build.gradle @@ -18,16 +18,25 @@ ext { configure(allprojects) { project -> repositories { + mavenLocal() mavenCentral() + google() maven { - url "https://repo.spring.io/milestone" + url "https://maven.aliyun.com/repository/spring" +// url "https://repo.spring.io/milestone" content { // Netty 5 optional support includeGroup 'io.projectreactor.netty' } } if (version.contains('-')) { - maven { url "https://repo.spring.io/milestone" } + maven { url "https://maven.aliyun.com/repository/public" } + maven { url "https://maven.aliyun.com/repository/central" } + maven { url "https://maven.aliyun.com/repository/gradle-plugin" } + maven { url "https://maven.aliyun.com/repository/apache-snapshots" } + maven { url "https://maven.aliyun.com/repository/spring" } + maven { url "https://maven.aliyun.com/repository/spring-plugin"} +// maven { url "https://repo.spring.io/milestone" } } if (version.endsWith('-SNAPSHOT')) { maven { url "https://repo.spring.io/snapshot" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0e5490..0ad92c57126f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +# distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=file:///D:/ENV/Gradle/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index adb99dc3e97c..82be72b16f32 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,13 @@ pluginManagement { repositories { + mavenLocal() + maven { url "https://maven.aliyun.com/repository/public" } + maven { url "https://maven.aliyun.com/repository/central" } + maven { url "https://maven.aliyun.com/repository/gradle-plugin" } + maven { url "https://maven.aliyun.com/repository/apache-snapshots" } + maven { url "https://maven.aliyun.com/repository/spring" } + maven { url "https://maven.aliyun.com/repository/spring-plugin"} + google() mavenCentral() gradlePluginPortal() maven { url "https://repo.spring.io/release" } @@ -59,3 +67,8 @@ settings.gradle.projectsLoaded { } } } +include 'spring-lxcecho-sample' +include 'spring-lxcecho' +include 'spring-lxcecho-notes' +include 'spring-lxcecho-mvc-sample' + diff --git a/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java b/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java index 9188e25e1d0d..337ccac258df 100644 --- a/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java +++ b/spring-aop/src/main/java/org/aopalliance/intercept/MethodInterceptor.java @@ -20,6 +20,8 @@ import javax.annotation.Nullable; /** + * Spring AIO 调用链中拦截器的内部核心接口,所有类型的切面最终都会包装成此接口触发统一拦截 + * * Intercepts calls on an interface on its way to the target. These * are nested "on top" of the target. * diff --git a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java index a318ea56bb49..4d9f9238982f 100644 --- a/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java +++ b/spring-aop/src/main/java/org/springframework/aop/aspectj/annotation/BeanFactoryAspectJAdvisorsBuilder.java @@ -86,7 +86,7 @@ public BeanFactoryAspectJAdvisorsBuilder(ListableBeanFactory beanFactory, Aspect * @see #isEligibleBean */ public List buildAspectJAdvisors() { - List aspectNames = this.aspectBeanNames; + List aspectNames = this.aspectBeanNames; // TODO 这个值是什么时候赋值的??? if (aspectNames == null) { synchronized (this) { diff --git a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java index 1bba8f1c2048..ee41a6c4809b 100644 --- a/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java +++ b/spring-aop/src/main/java/org/springframework/aop/config/AopConfigUtils.java @@ -72,7 +72,7 @@ public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionR @Nullable public static BeanDefinition registerAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { - + // InfrastructureAdvisorAutoProxyCreator 父类是 AbstractAutoProxyCreator,主要是开启 AOP,该类不会和 AOP 那样去扫描 @Before 等注解 return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source); } @@ -120,10 +120,14 @@ private static BeanDefinition registerOrEscalateApcAsRequired( Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); + // 优先判断是否存在默认入口类【internalAutoProxyCreator】的 BeanDefinition if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); + // 注册的入口类不是默认入口类 if (!cls.getName().equals(apcDefinition.getBeanClassName())) { + // 查找当前注入接口的优先级 int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); + // 当前入口类对应的优先级,priority 数字越大,优先级越高 int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); @@ -132,6 +136,7 @@ private static BeanDefinition registerOrEscalateApcAsRequired( return null; } + // 封装到 Bean Definition 中,并注册到 BeanDefinitionRegistry 中 RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index dceed756b414..4c3248b897bd 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -699,6 +699,7 @@ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { + // CglibMethodInvocation:把所有信息封装到这里【代理对象、真实对象、增强器链、当前方法、使用的参数】(这里是 FilterChain) // We need to create a method invocation... retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java index f97455dfc45c..4eba1e7644ee 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/DefaultAopProxyFactory.java @@ -59,18 +59,27 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { - if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { + // isOptimize:是否对代理进行优化 + // isProxyTargetClass:值为 true,使用 CGLib 代理,默认为 false + // hasNoUserSuppliedProxyInterfaces:如果长度为 0,也就是接口为空,返回 false;或 如果接口类型不是 SpringProxy 类型的,返回 false + if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { // 如果条件不满足:直接走 JDK 动态代理(return) 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."); } + // 如果 targetClass 是接口类,使用 JDK 来生成 Proxy + // 如果目标对象实现了接口,默认情况下会采用 JDK 动态代理 + // 但也可以通过配置 proxy-target-class=true 强制使用 CGLIB 代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass) || ClassUtils.isLambdaClass(targetClass)) { + // JDK Proxy return new JdkDynamicAopProxy(config); } + // CGLIB Proxy return new ObjenesisCglibAopProxy(config); } else { + // JDK Proxy return new JdkDynamicAopProxy(config); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java index e5f9c08531dc..dde59237f14a 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/JdkDynamicAopProxy.java @@ -105,7 +105,9 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException { Assert.notNull(config, "AdvisedSupport must not be null"); this.advised = config; + // 获取代理对象需要实现的接口(业务接口和内置接口) this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true); + // 判断接口中是否重写了 equals 和 hashCode 方法 findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces); } @@ -120,6 +122,11 @@ public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource()); } + /** + * 参数 1:类加载器(目标类) + * 参数 2:代理类需要实现的接口,即目标类实现的接口(含系统接口)(数组) + * 参数 3:InvocationHandler 本类实现了此接口 + */ return Proxy.newProxyInstance(determineClassLoader(classLoader), this.proxiedInterfaces, this); } @@ -189,6 +196,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl Object oldProxy = null; boolean setProxyContext = false; + // 拿到 targetSource,其中包含被代理的 Bean 对象 TargetSource targetSource = this.advised.targetSource; Object target = null; @@ -224,23 +232,24 @@ else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && target = targetSource.getTarget(); Class targetClass = (target != null ? target.getClass() : null); - // Get the interception chain for this method. + // Get the interception chain for this method. 从 ProxyFactory(this.advised) 中构建拦截器链,包含了目标方法的所有切面方法 List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fall back on direct // reflective invocation of the target, and avoid creating a MethodInvocation. - if (chain.isEmpty()) { + if (chain.isEmpty()) { // 被代理对象中没有方法可以被增强,直接返回调用方法 // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); + // 直接触发反射 method 调用 retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // We need to create a method invocation... MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); - // Proceed to the joinpoint through the interceptor chain. + // Proceed to the joinpoint through the interceptor chain. 责任链还是调用:ReflectiveMethodInvocation.proceed(); retVal = invocation.proceed(); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java index 56330a6395a3..50d923bdad17 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ProxyFactory.java @@ -98,6 +98,8 @@ public Object getProxy() { } /** + * 通过类加载期获取代理 + * * Create a new proxy according to the settings in this factory. *

Can be called repeatedly. Effect will vary if we've added * or removed interfaces. Can add and remove interceptors. @@ -107,6 +109,7 @@ public Object getProxy() { * @return the proxy object */ public Object getProxy(@Nullable ClassLoader classLoader) { + // 分别进入 createAopProxy() getProxy() return createAopProxy().getProxy(classLoader); } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java b/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java index cc29883d590a..c2ee4eb72deb 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/ReflectiveMethodInvocation.java @@ -158,6 +158,7 @@ public void setArguments(Object... arguments) { @Override @Nullable public Object proceed() throws Throwable { + // 若所有的增强方法调用完成,则调用被代理方法 // We start with an index of -1 and increment early. if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { return invokeJoinpoint(); @@ -170,17 +171,19 @@ public Object proceed() throws Throwable { // been evaluated and found to match. Class targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass()); if (dm.matcher().matches(this.method, targetClass, this.arguments)) { + // 有了前面所有 Advice 统一包装成 MethodInterceptor,可以统一调用 invoke() 方法 return dm.interceptor().invoke(this); } else { // Dynamic matching failed. // Skip this interceptor and invoke the next in the chain. - return proceed(); + return proceed(); // 忽略本次调用,路由到下次调用 } } else { // It's an interceptor, so we just invoke it: The pointcut will have // been evaluated statically before this object was constructed. + // TODO 事务入口拦截器,都会从这里链式调用触发:org.springframework.transaction.interceptor.TransactionInterceptor.invoke() return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this); } } diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java index 502fd9e29259..3046dac553ac 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/autoproxy/AbstractAutoProxyCreator.java @@ -306,6 +306,8 @@ public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, Str } /** + * 如果当前的 Bean 适合被代理,则需要包装指定的 Bean + * * Create a proxy with the configured interceptors if the bean is * identified as one to proxy by the subclass. * @see #getAdvicesAndAdvisorsForBean @@ -313,8 +315,10 @@ public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, Str @Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { + // 根据给定的 Bean 的 Class 和 Name 构建一个 key Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { + // 如果当前的 Bean 适合被代理,则需包装指定的 Bean return wrapIfNecessary(bean, beanName, cacheKey); } } @@ -344,6 +348,11 @@ protected Object getCacheKey(Class beanClass, @Nullable String beanName) { } /** + * 目标: + * 1.判断当前 Bean 是否已经生成过代理对象,或者是否应该被略过的对象,是则直接返回,否则进行下一步; + * 2.拿到切面类中所有增强方法【拦截器:环绕、前置、后置等】; + * 3. 生成代理对象 + * * Wrap the given bean if necessary, i.e. if it is eligible for being proxied. * @param bean the raw bean instance * @param beanName the name of the bean @@ -351,27 +360,40 @@ protected Object getCacheKey(Class beanClass, @Nullable String beanName) { * @return a proxy wrapping the bean, or the raw bean instance as-is */ protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { + // 判断是否为空:判断当前 Bean 是否在 TargetSource 缓存中,如果存在,则直接返回当前 Bean if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } + // 这里的 advisedBeans 缓存了不需要代理的 Bean(为 false的),如果缓存中存在,则可以直接返回 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } + // isInfrastructureClass() 是否基础设施:用于判断当前 Bean 是否为 Spring 自带的 Bean,自带的 Bean 是不用进行代理的; + // shouldSkip():用于判断当前 Bean 是否应该被略过 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { + // 对当前 Bean 进行缓存 this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } - // Create proxy if we have advice. + // Create proxy if we have advice. 找到那些切面会作用在当前 Bean 上,满足条件的抓出来 + // AOP:【关键点1】反射来过滤,看看那些 Aspect 的 execution 能匹配上当前 Bean + // ===【【【注意:这里要分两步调试,修改切面表达式做对比】】】=== + // 匹配上的话列出前后和置换的方法【拦截器:环绕、前置、后置等】 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); - if (specificInterceptors != DO_NOT_PROXY) { + if (specificInterceptors != DO_NOT_PROXY) { // 如果拿到的增强方法不为空 + // 对当前的 Bean 的代理状态进行缓存 this.advisedBeans.put(cacheKey, Boolean.TRUE); + // 开始生成 AOP 代理 + // AOP:【关键点2】 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); + // 缓存生成的代理 Bean 类型,并且返回生成的代理 Bean this.proxyTypes.put(cacheKey, proxy.getClass()); + // 此处返回的代理和在 Test 函数中返回的一样,说明此处代理成功创建 return proxy; } - + // 如果拿到的增强方法为空,缓存起来(使用 false 标记不需要代理) this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } @@ -470,16 +492,36 @@ private Class createProxyClass(Class beanClass, @Nullable String beanName, return (Class) buildProxy(beanClass, beanName, specificInterceptors, targetSource, true); } + /** + * 创建代理对象 + * + * @param beanClass 目标对象 Class + * @param beanName 目标对象名称 + * @param specificInterceptors 拦截器里面的拦截方法 + * @param targetSource 目标资源 + * @param classOnly + * @return + */ private Object buildProxy(Class beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource, boolean classOnly) { + // 为 true,DefaultListableBeanFactory if (this.beanFactory instanceof ConfigurableListableBeanFactory clbf) { + // 给当前的 BD设置 originalTargetClass 属性 AutoProxyUtils.exposeTargetClass(clbf, beanName, beanClass); } + // 创建一个默认的代理工厂 DefaultAopProxyFactory,父类无参构造器 ProxyFactory proxyFactory = new ProxyFactory(); + // proxyFactory 通过复制配置进行初始化 + // this:为 AbstractAutoProxyCreator 对象,说明 AbstractAutoProxyCreator 继承参数实际类型 proxyFactory.copyFrom(this); + /** + * isProxyTargetClass():默认 false + * -true:目标对象没有接口(只有实现类),使用 CGLib 代理机制 + * -false:目标对象实现了接口,使用 JDK 代理机制【代理所有实现了的接口】 + */ if (proxyFactory.isProxyTargetClass()) { // Explicit handling of JDK proxy targets and lambdas (for introduction advice scenarios) if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) { @@ -490,22 +532,32 @@ private Object buildProxy(Class beanClass, @Nullable String beanName, } } else { + // 来判断 @EnabledAspectJAutoProxy 注解或者 XML 的 proxyTargetClass 参数 + // 看看用户有没有指定什么方式生成代理,如果没有配置就是为空,此处返回 false // No proxyTargetClass flag enforced, let's apply our default checks... if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { + // 评估接口的合理性,一些内部回调接口,比如 InitializingBean 等,不会被实现 JDK 代理 evaluateProxyInterfaces(beanClass, proxyFactory); } } + // 把 advice(增强)类型的增强包装成 advisor 类型【强制类型转换】 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); + // 加入到代理工厂 proxyFactory.addAdvisors(advisors); + // 设置要代理的类-目标类 proxyFactory.setTargetSource(targetSource); + // 子类实现,定制代理 customizeProxyFactory(proxyFactory); + // 用来控制代理工厂被设置后后是否还允许修改通知,缺省值为 false proxyFactory.setFrozen(this.freezeProxy); + // 明明是 false???此处注意:它是在子类 AbstractAdvisorAutoProxyCreator 重写了 advisorPreFiltered 方法 if (advisorsPreFiltered()) { + // 设置预过滤 proxyFactory.setPreFiltered(true); } @@ -514,6 +566,7 @@ private Object buildProxy(Class beanClass, @Nullable String beanName, if (classLoader instanceof SmartClassLoader smartClassLoader && classLoader != beanClass.getClassLoader()) { classLoader = smartClassLoader.getOriginalClassLoader(); } + // 【关键点】通过加载期获取代理;getProxyClassLoader 为默认的类加载器 return (classOnly ? proxyFactory.getProxyClass(classLoader) : proxyFactory.getProxy(classLoader)); } diff --git a/spring-aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java b/spring-aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java index 9822374da1a3..45587c2ae113 100644 --- a/spring-aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java +++ b/spring-aop/src/main/java/org/springframework/aop/interceptor/ExposeInvocationInterceptor.java @@ -91,8 +91,8 @@ private ExposeInvocationInterceptor() { @Override @Nullable public Object invoke(MethodInvocation mi) throws Throwable { - MethodInvocation oldInvocation = invocation.get(); - invocation.set(mi); + MethodInvocation oldInvocation = invocation.get(); // 线程共享数据的 + invocation.set(mi); // ThreadLocal 保存 mi try { return mi.proceed(); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java index c15ff8a81cdb..db08dee32248 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java @@ -425,12 +425,26 @@ public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, S return result; } + /** + * 循环所有后置处理器调用:AOP 调试,此处最好用断点表达式,否则要循环很多次 + * 【因为在 AbstractApplicationContext#refresh() 中的 invokeBeanFactoryPostProcessor() 方法也调用到这里】 + * + * @param existingBean the existing bean instance + * @param beanName the name of the bean, to be passed to it if necessary + * (only passed to {@link BeanPostProcessor BeanPostProcessors}; + * can follow the {@link #ORIGINAL_INSTANCE_SUFFIX} convention in order to + * enforce the given instance to be returned, i.e. no proxies etc) + * @return + * @throws BeansException + */ @Override public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException { Object result = existingBean; + // AOP:此处有 7 个内置后置处理器,生成代理会调用里面的 AnnotationAwareAspectJAutoCreator for (BeanPostProcessor processor : getBeanPostProcessors()) { + // AOP 调用 AbstractAutoProxyCreator#postProcessAfterInitialization() Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java index 2e5fa3dc1300..3df80ba2b95c 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AutoProxyRegistrar.java @@ -64,14 +64,18 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, B if (candidate == null) { continue; } + // AOP 注入相关属性信息 Object mode = candidate.get("mode"); Object proxyTargetClass = candidate.get("proxyTargetClass"); if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() && Boolean.class == proxyTargetClass.getClass()) { candidateFound = true; if (mode == AdviceMode.PROXY) { + // TODO 注册事务入口类:InfrastructureAdvisorAutoProxyCreator + // InfrastructureAdvisorAutoProxyCreator 事务入口类和 AOP 入口类 AnnotationAspectJAutoProxyCreator 之间有优先级 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry); if ((Boolean) proxyTargetClass) { + // 设置 InfrastructureAdvisorAutoProxyCreator 的 proxyTargetClass 为 true AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); return; } diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index 40fb85104ebf..0b11f1041376 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -967,7 +967,7 @@ protected void finishRefresh() { // Propagate refresh to lifecycle processor first. getLifecycleProcessor().onRefresh(); - // Publish the final event. + // Publish the final event. 发布上下文已刷新完成的事件 publishEvent(new ContextRefreshedEvent(this)); } diff --git a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java index d5b788487682..a9bc89d4bd52 100644 --- a/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java +++ b/spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java @@ -112,6 +112,8 @@ public static void invokeBeanFactoryPostProcessors( } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); + // ConfigurationClassPostProcessor implements PriorityOrdered, it will be here to solve the SpringConfig.class, + // and scan and register all component information in the package. invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup()); currentRegistryProcessors.clear(); diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java index 2965ebff03b6..9be340023b9f 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java @@ -244,8 +244,11 @@ public Object getResourceFactory() { @Override protected Object doGetTransaction() { + // 创建事务对象 DataSourceTransactionObject txObject = new DataSourceTransactionObject(); + // 设置是否允许回滚点:只有 Nest 传播属性才为 true【嵌套事务】 txObject.setSavepointAllowed(isNestedTransactionAllowed()); + // 数据库连接包装,第一次拿到的 conHolder 为空 ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource()); txObject.setConnectionHolder(conHolder, false); @@ -277,6 +280,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) { con = txObject.getConnectionHolder().getConnection(); Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); + // 设置事务对象隔离级别 txObject.setPreviousIsolationLevel(previousIsolationLevel); txObject.setReadOnly(definition.isReadOnly()); @@ -288,6 +292,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) { if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } + // 设置手动提交 con.setAutoCommit(false); } @@ -299,7 +304,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } - // Bind the connection holder to the thread. + // Bind the connection holder to the thread. 将连接绑定到连接上下文 ConnectionHolder if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } @@ -318,7 +323,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) { protected Object doSuspend(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; txObject.setConnectionHolder(null); - return TransactionSynchronizationManager.unbindResource(obtainDataSource()); + return TransactionSynchronizationManager.unbindResource(obtainDataSource()); // 挂起事务就是从 NamedThreadLocal 移除 } @Override @@ -334,6 +339,7 @@ protected void doCommit(DefaultTransactionStatus status) { logger.debug("Committing JDBC transaction on Connection [" + con + "]"); } try { + // TODO:JDBC 链接提交事务 con.commit(); } catch (SQLException ex) { @@ -349,6 +355,7 @@ protected void doRollback(DefaultTransactionStatus status) { logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); } try { + // TODO:JDBC 回滚事务 con.rollback(); } catch (SQLException ex) { diff --git a/spring-lxcecho-mvc-sample/build.gradle b/spring-lxcecho-mvc-sample/build.gradle new file mode 100644 index 000000000000..e3d99d45a9a4 --- /dev/null +++ b/spring-lxcecho-mvc-sample/build.gradle @@ -0,0 +1,25 @@ +description "Spring MVC Sample" + +group = 'com.lxcecho' +version = '6.0.15-SNAPSHOT' + +dependencies { + /*当你引入 Spring Context 依赖之后,表示将 Spring 的基础依赖引入了*/ + api(project(":spring-webmvc")) + + optional("org.apache.tomcat.embed:tomcat-embed-core") + implementation("org.apache.tomcat.embed:tomcat-embed-jasper:10.1.15") + compileOnly("jakarta.servlet:jakarta.servlet-api") + + optional("org.apache.logging.log4j:log4j-api") + optional("org.slf4j:slf4j-api") + optional("org.apache.logging.log4j:log4j-core") + optional("org.apache.logging.log4j:log4j-jul") + optional("org.apache.logging.log4j:log4j-slf4j2-impl") + + api(project(":spring-test")) + + api("junit:junit:4.13.2") + testFixturesApi("org.junit.jupiter:junit-jupiter-api") + testFixturesApi("org.junit.jupiter:junit-jupiter-params") +} \ No newline at end of file diff --git a/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/Main.java b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/Main.java new file mode 100644 index 000000000000..440ce0548cd8 --- /dev/null +++ b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/Main.java @@ -0,0 +1,50 @@ +package com.lxcecho; + +import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.startup.Tomcat; + +/** + * 怎么启动起来? + * Tomcat启动 + * SPI机制下 QuickAppStarter生效创建 ioc容器配置DispatcherServlet等各种组件 + *

+ * 导入各种starter依赖,SpringBoot封装了很多的自动配置,帮我们给容器中放了很多组件。 + * SpringBoot封装了功能的自动配置 + *

+ * WebServerFactory做到了 + * + * @author lxcecho azaki0426@gmail.com + * @since 2024/1/1 + */ +public class Main { + public static void main(String[] args) throws LifecycleException { + // 自己写 Tomcat 的启动源码 + Tomcat tomcat = new Tomcat(); + + tomcat.setPort(8888); + tomcat.setHostname("localhost"); + tomcat.setBaseDir("."); + + Context context = tomcat.addWebapp("/com/lxcecho", System.getProperty("user.dir") + "/src/main"); + + +// DispatcherServlet servlet = new DispatcherServlet(); + // 给 Tomcat 里面添加一个 Servlet +// Wrapper hello = tomcat.addServlet("/boot", "hello", new HelloServlet()); +// Wrapper hello = tomcat.addServlet("/boot", "hello", servlet); +// hello.addMapping("/"); // 指定处理的请求 + + // 自己创建 DispatcherServlet 对象,并且创建 ioc 容器,DispatcherServlet 里面有 ioc 容器 + + // 自己创建一个 DispatcherServlet 注册进去 +// tomcat.addServlet(自己创建一个DispatcherServlet注册进去) + + // 启动 tomcat 注解版 MVC 利用 Tomcat SPI 机制 + tomcat.start(); + + // 服务器等待 + tomcat.getServer().await(); + + } +} \ No newline at end of file diff --git a/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/QuickAppStarter.java b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/QuickAppStarter.java new file mode 100644 index 000000000000..a33c8c36b344 --- /dev/null +++ b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/QuickAppStarter.java @@ -0,0 +1,52 @@ +package com.lxcecho; + +import com.lxcecho.config.SpringConfig; +import com.lxcecho.config.SpringMvcConfig; +import jakarta.servlet.ServletRegistration; +import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; + +/** + * 最快速的整合注解版 SpringMVC 和 Spring 的 + * + * @author lxcecho azaki0426@gmail.com + * @since 2024/1/1 + */ +public class QuickAppStarter extends AbstractAnnotationConfigDispatcherServletInitializer { + + /** + * 根容器的配置(Spring 的配置文件===Spring 的配置类) + * + * @return + */ + @Override + protected Class[] getRootConfigClasses() { + return new Class[]{SpringConfig.class}; + } + + /** + * web 容器的配置(SpringMVC 的配置文件===SpringMVC 的配置类) + * + * @return + */ + @Override + protected Class[] getServletConfigClasses() { + return new Class[]{SpringMvcConfig.class}; + } + + /** + * Servlet 的映射,DispatcherServlet 的映射路径 + * + * @return + */ + @Override + protected String[] getServletMappings() { + return new String[]{"/"}; + } + + @Override + protected void customizeRegistration(ServletRegistration.Dynamic registration) { +// super.customizeRegistration(registration); + +// registration.addMapping("");// + } +} diff --git a/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/config/SpringConfig.java b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/config/SpringConfig.java new file mode 100644 index 000000000000..1324e903f1f8 --- /dev/null +++ b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/config/SpringConfig.java @@ -0,0 +1,20 @@ +package com.lxcecho.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.FilterType; +import org.springframework.stereotype.Controller; + +/** + * Spring 不扫描 controller 组件、AOP 咋实现的....????? + * + * @author lxcecho azaki0426@gmail.com + * @since 2024/1/1 + */ +@ComponentScan(value = "com.lxcecho", excludeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class) +}) +@Configuration +public class SpringConfig { + // Spring 的父容器 +} diff --git a/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/config/SpringMvcConfig.java b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/config/SpringMvcConfig.java new file mode 100644 index 000000000000..37485e562e10 --- /dev/null +++ b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/config/SpringMvcConfig.java @@ -0,0 +1,18 @@ +package com.lxcecho.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.FilterType; +import org.springframework.stereotype.Controller; + +/** + * SpringMVC 只扫描 controller 组件,可以不指定父容器类,让 MVC 扫所有。@Component+@RequestMapping 就生效了 + * + * @author lxcecho azaki0426@gmail.com + * @since 2024/1/1 + */ +@ComponentScan(value = "com.lxcecho", includeFilters = { + @ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class) +}, useDefaultFilters = false) +public class SpringMvcConfig { + // SpringMVC 的子容器,能扫描的 Spring 容器中的组件 +} \ No newline at end of file diff --git a/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/controller/HelloController.java b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/controller/HelloController.java new file mode 100644 index 000000000000..18b7d4a8a87c --- /dev/null +++ b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/controller/HelloController.java @@ -0,0 +1,18 @@ +package com.lxcecho.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2024/1/1 + */ +@RestController +public class HelloController { + + @GetMapping("/hello66") + public String hello() { + + return "66666666~~~~~"; + } +} diff --git a/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/servlet/HelloServlet.java b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/servlet/HelloServlet.java new file mode 100644 index 000000000000..dad76ce36bd8 --- /dev/null +++ b/spring-lxcecho-mvc-sample/src/main/java/com/lxcecho/servlet/HelloServlet.java @@ -0,0 +1,27 @@ +package com.lxcecho.servlet; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2024/1/1 + */ +public class HelloServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + doPost(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.getWriter().write("hello tomcat"); + } +} diff --git a/spring-lxcecho-notes/images/spring6/2097896352.png b/spring-lxcecho-notes/images/spring6/2097896352.png new file mode 100644 index 000000000000..1a85641f09a3 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/2097896352.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031155315111.png b/spring-lxcecho-notes/images/spring6/image-20221031155315111.png new file mode 100644 index 000000000000..4a139f884178 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031155315111.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031155411978.png b/spring-lxcecho-notes/images/spring6/image-20221031155411978.png new file mode 100644 index 000000000000..7b7729293989 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031155411978.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031164828150.png b/spring-lxcecho-notes/images/spring6/image-20221031164828150.png new file mode 100644 index 000000000000..7c9ed4c81d62 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031164828150.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031165108003.png b/spring-lxcecho-notes/images/spring6/image-20221031165108003.png new file mode 100644 index 000000000000..0272a3a4bd3a Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031165108003.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031170253382.png b/spring-lxcecho-notes/images/spring6/image-20221031170253382.png new file mode 100644 index 000000000000..5d80527872f3 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031170253382.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031170725352.png b/spring-lxcecho-notes/images/spring6/image-20221031170725352.png new file mode 100644 index 000000000000..4b9086427ac7 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031170725352.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031170930747.png b/spring-lxcecho-notes/images/spring6/image-20221031170930747.png new file mode 100644 index 000000000000..f7031ed72e7e Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031170930747.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031172354535.png b/spring-lxcecho-notes/images/spring6/image-20221031172354535.png new file mode 100644 index 000000000000..658fd3876c11 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031172354535.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031181430720.png b/spring-lxcecho-notes/images/spring6/image-20221031181430720.png new file mode 100644 index 000000000000..7e4035decb21 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031181430720.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031214305224.png b/spring-lxcecho-notes/images/spring6/image-20221031214305224.png new file mode 100644 index 000000000000..e6dfd1e825b1 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031214305224.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221031214547501.png b/spring-lxcecho-notes/images/spring6/image-20221031214547501.png new file mode 100644 index 000000000000..4095c952d75b Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221031214547501.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221101153556681.png b/spring-lxcecho-notes/images/spring6/image-20221101153556681.png new file mode 100644 index 000000000000..61443bf64d20 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221101153556681.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221102103318844.png b/spring-lxcecho-notes/images/spring6/image-20221102103318844.png new file mode 100644 index 000000000000..aed5a7e2af42 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221102103318844.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221102103628391.png b/spring-lxcecho-notes/images/spring6/image-20221102103628391.png new file mode 100644 index 000000000000..414ed17b395d Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221102103628391.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221102103931372.png b/spring-lxcecho-notes/images/spring6/image-20221102103931372.png new file mode 100644 index 000000000000..029dade8fc5f Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221102103931372.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221102155523983.png b/spring-lxcecho-notes/images/spring6/image-20221102155523983.png new file mode 100644 index 000000000000..db678074ddc3 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221102155523983.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201102513199.png b/spring-lxcecho-notes/images/spring6/image-20221201102513199.png new file mode 100644 index 000000000000..1d996d7b6d66 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201102513199.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201102642526.png b/spring-lxcecho-notes/images/spring6/image-20221201102642526.png new file mode 100644 index 000000000000..fadf0e79af4f Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201102642526.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201103138194.png b/spring-lxcecho-notes/images/spring6/image-20221201103138194.png new file mode 100644 index 000000000000..04284e8f7fea Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201103138194.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201103842379.png b/spring-lxcecho-notes/images/spring6/image-20221201103842379.png new file mode 100644 index 000000000000..7d41e8f01dc5 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201103842379.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201103914530.png b/spring-lxcecho-notes/images/spring6/image-20221201103914530.png new file mode 100644 index 000000000000..77f0c77e235b Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201103914530.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201104003391.png b/spring-lxcecho-notes/images/spring6/image-20221201104003391.png new file mode 100644 index 000000000000..004a2d549473 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201104003391.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201104123322.png b/spring-lxcecho-notes/images/spring6/image-20221201104123322.png new file mode 100644 index 000000000000..69c228780ad2 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201104123322.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201104749612.png b/spring-lxcecho-notes/images/spring6/image-20221201104749612.png new file mode 100644 index 000000000000..f38d2d120bea Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201104749612.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201104827752.png b/spring-lxcecho-notes/images/spring6/image-20221201104827752.png new file mode 100644 index 000000000000..1f7f3a6cd573 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201104827752.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201104913541.png b/spring-lxcecho-notes/images/spring6/image-20221201104913541.png new file mode 100644 index 000000000000..654163e5c83f Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201104913541.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201105001197.png b/spring-lxcecho-notes/images/spring6/image-20221201105001197.png new file mode 100644 index 000000000000..34f9488d1038 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201105001197.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201105416558.png b/spring-lxcecho-notes/images/spring6/image-20221201105416558.png new file mode 100644 index 000000000000..b890643a57a5 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201105416558.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221201173958165.png b/spring-lxcecho-notes/images/spring6/image-20221201173958165.png new file mode 100644 index 000000000000..8b78df95c015 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221201173958165.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221205201741893.png b/spring-lxcecho-notes/images/spring6/image-20221205201741893.png new file mode 100644 index 000000000000..e17984df6a5e Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221205201741893.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221205202000198.png b/spring-lxcecho-notes/images/spring6/image-20221205202000198.png new file mode 100644 index 000000000000..ef9565e92e2d Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221205202000198.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221205202117383.png b/spring-lxcecho-notes/images/spring6/image-20221205202117383.png new file mode 100644 index 000000000000..9b03323a2992 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221205202117383.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221205202154225.png b/spring-lxcecho-notes/images/spring6/image-20221205202154225.png new file mode 100644 index 000000000000..24c562effb7b Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221205202154225.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221206184117531.png b/spring-lxcecho-notes/images/spring6/image-20221206184117531.png new file mode 100644 index 000000000000..98037ef87405 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221206184117531.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221206220207266.png b/spring-lxcecho-notes/images/spring6/image-20221206220207266.png new file mode 100644 index 000000000000..6839dedb67bf Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221206220207266.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221206221002615.png b/spring-lxcecho-notes/images/spring6/image-20221206221002615.png new file mode 100644 index 000000000000..776dc729e792 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221206221002615.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221206231535991.png b/spring-lxcecho-notes/images/spring6/image-20221206231535991.png new file mode 100644 index 000000000000..2edcf9bef528 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221206231535991.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221206232920494.png b/spring-lxcecho-notes/images/spring6/image-20221206232920494.png new file mode 100644 index 000000000000..e6c32b308258 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221206232920494.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207102315185.png b/spring-lxcecho-notes/images/spring6/image-20221207102315185.png new file mode 100644 index 000000000000..2069fde75730 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207102315185.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207103020854.png b/spring-lxcecho-notes/images/spring6/image-20221207103020854.png new file mode 100644 index 000000000000..6eedb9474a42 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207103020854.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207110539954.png b/spring-lxcecho-notes/images/spring6/image-20221207110539954.png new file mode 100644 index 000000000000..41884f9c19ef Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207110539954.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207110947997.png b/spring-lxcecho-notes/images/spring6/image-20221207110947997.png new file mode 100644 index 000000000000..bee034d05cc0 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207110947997.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111012582.png b/spring-lxcecho-notes/images/spring6/image-20221207111012582.png new file mode 100644 index 000000000000..569878d0873b Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111012582.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111105569.png b/spring-lxcecho-notes/images/spring6/image-20221207111105569.png new file mode 100644 index 000000000000..fd5603fc3d11 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111105569.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111206279.png b/spring-lxcecho-notes/images/spring6/image-20221207111206279.png new file mode 100644 index 000000000000..1c2bd372e73d Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111206279.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111420056.png b/spring-lxcecho-notes/images/spring6/image-20221207111420056.png new file mode 100644 index 000000000000..0985ea5ddc70 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111420056.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111509837.png b/spring-lxcecho-notes/images/spring6/image-20221207111509837.png new file mode 100644 index 000000000000..3d5b3692da69 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111509837.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111609878.png b/spring-lxcecho-notes/images/spring6/image-20221207111609878.png new file mode 100644 index 000000000000..767deffe5405 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111609878.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111644950.png b/spring-lxcecho-notes/images/spring6/image-20221207111644950.png new file mode 100644 index 000000000000..f9870ebcc548 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111644950.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111731150.png b/spring-lxcecho-notes/images/spring6/image-20221207111731150.png new file mode 100644 index 000000000000..02d3bf4ca677 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111731150.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207111947283.png b/spring-lxcecho-notes/images/spring6/image-20221207111947283.png new file mode 100644 index 000000000000..2126a9bd44ae Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207111947283.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207112009852.png b/spring-lxcecho-notes/images/spring6/image-20221207112009852.png new file mode 100644 index 000000000000..716d9000979d Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207112009852.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207113544080.png b/spring-lxcecho-notes/images/spring6/image-20221207113544080.png new file mode 100644 index 000000000000..9e762bde0311 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207113544080.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207122416669.png b/spring-lxcecho-notes/images/spring6/image-20221207122416669.png new file mode 100644 index 000000000000..201c1cdac818 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207122416669.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207122500801.png b/spring-lxcecho-notes/images/spring6/image-20221207122500801.png new file mode 100644 index 000000000000..073bc377abad Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207122500801.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207122802758.png b/spring-lxcecho-notes/images/spring6/image-20221207122802758.png new file mode 100644 index 000000000000..dbb96fb19d47 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207122802758.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207124839565.png b/spring-lxcecho-notes/images/spring6/image-20221207124839565.png new file mode 100644 index 000000000000..ea08ede948f1 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207124839565.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207140024056.png b/spring-lxcecho-notes/images/spring6/image-20221207140024056.png new file mode 100644 index 000000000000..73c19e3e192b Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207140024056.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207142746771.png b/spring-lxcecho-notes/images/spring6/image-20221207142746771.png new file mode 100644 index 000000000000..ad9bb5e707a6 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207142746771.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207152841304.png b/spring-lxcecho-notes/images/spring6/image-20221207152841304.png new file mode 100644 index 000000000000..59e7fc12fb26 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207152841304.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207153018092.png b/spring-lxcecho-notes/images/spring6/image-20221207153018092.png new file mode 100644 index 000000000000..7617150939af Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207153018092.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207153642253.png b/spring-lxcecho-notes/images/spring6/image-20221207153642253.png new file mode 100644 index 000000000000..870bae3f8fef Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207153642253.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207153724340.png b/spring-lxcecho-notes/images/spring6/image-20221207153724340.png new file mode 100644 index 000000000000..64412ea16b46 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207153724340.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207153755732.png b/spring-lxcecho-notes/images/spring6/image-20221207153755732.png new file mode 100644 index 000000000000..812f4fbfb6a0 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207153755732.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207153944132.png b/spring-lxcecho-notes/images/spring6/image-20221207153944132.png new file mode 100644 index 000000000000..36afe694d47e Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207153944132.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207155009832.png b/spring-lxcecho-notes/images/spring6/image-20221207155009832.png new file mode 100644 index 000000000000..1168e4b3f4cb Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207155009832.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207155726572.png b/spring-lxcecho-notes/images/spring6/image-20221207155726572.png new file mode 100644 index 000000000000..817795285b75 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207155726572.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221207155756512.png b/spring-lxcecho-notes/images/spring6/image-20221207155756512.png new file mode 100644 index 000000000000..d504302f396f Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221207155756512.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221209105947105.png b/spring-lxcecho-notes/images/spring6/image-20221209105947105.png new file mode 100644 index 000000000000..23e13c9d37a2 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221209105947105.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221209110043449.png b/spring-lxcecho-notes/images/spring6/image-20221209110043449.png new file mode 100644 index 000000000000..23e13c9d37a2 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221209110043449.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221216132844066.png b/spring-lxcecho-notes/images/spring6/image-20221216132844066.png new file mode 100644 index 000000000000..0660abca0d62 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221216132844066.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221216223135162.png b/spring-lxcecho-notes/images/spring6/image-20221216223135162.png new file mode 100644 index 000000000000..642994785c77 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221216223135162.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221217115515670.png b/spring-lxcecho-notes/images/spring6/image-20221217115515670.png new file mode 100644 index 000000000000..fbac482ea560 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221217115515670.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221218154728062.png b/spring-lxcecho-notes/images/spring6/image-20221218154728062.png new file mode 100644 index 000000000000..660236cedbb0 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221218154728062.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221218154808754.png b/spring-lxcecho-notes/images/spring6/image-20221218154808754.png new file mode 100644 index 000000000000..11d069f2cdd6 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221218154808754.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221218154841001.png b/spring-lxcecho-notes/images/spring6/image-20221218154841001.png new file mode 100644 index 000000000000..cab6e29edb15 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221218154841001.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221218154945878.png b/spring-lxcecho-notes/images/spring6/image-20221218154945878.png new file mode 100644 index 000000000000..d1371d14e047 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221218154945878.png differ diff --git a/spring-lxcecho-notes/images/spring6/image-20221219112426052.png b/spring-lxcecho-notes/images/spring6/image-20221219112426052.png new file mode 100644 index 000000000000..c0a44113eb8c Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/image-20221219112426052.png differ diff --git a/spring-lxcecho-notes/images/spring6/img005.png b/spring-lxcecho-notes/images/spring6/img005.png new file mode 100644 index 000000000000..6004b75fa741 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img005.png differ diff --git a/spring-lxcecho-notes/images/spring6/img007.png b/spring-lxcecho-notes/images/spring6/img007.png new file mode 100644 index 000000000000..9fedfea44d65 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img007.png differ diff --git a/spring-lxcecho-notes/images/spring6/img010.png b/spring-lxcecho-notes/images/spring6/img010.png new file mode 100644 index 000000000000..340c49c060bf Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img010.png differ diff --git a/spring-lxcecho-notes/images/spring6/img014.png b/spring-lxcecho-notes/images/spring6/img014.png new file mode 100644 index 000000000000..834fae1307c5 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img014.png differ diff --git a/spring-lxcecho-notes/images/spring6/img015.png b/spring-lxcecho-notes/images/spring6/img015.png new file mode 100644 index 000000000000..63c9b185907c Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img015.png differ diff --git a/spring-lxcecho-notes/images/spring6/img016.png b/spring-lxcecho-notes/images/spring6/img016.png new file mode 100644 index 000000000000..fcd784e8861b Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img016.png differ diff --git a/spring-lxcecho-notes/images/spring6/img017.png b/spring-lxcecho-notes/images/spring6/img017.png new file mode 100644 index 000000000000..8d3719016515 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img017.png differ diff --git a/spring-lxcecho-notes/images/spring6/img018.png b/spring-lxcecho-notes/images/spring6/img018.png new file mode 100644 index 000000000000..0b58120cec55 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img018.png differ diff --git a/spring-lxcecho-notes/images/spring6/img019.png b/spring-lxcecho-notes/images/spring6/img019.png new file mode 100644 index 000000000000..5851404bebbe Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img019.png differ diff --git a/spring-lxcecho-notes/images/spring6/img020.png b/spring-lxcecho-notes/images/spring6/img020.png new file mode 100644 index 000000000000..35c0930b02e4 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img020.png differ diff --git a/spring-lxcecho-notes/images/spring6/img021.png b/spring-lxcecho-notes/images/spring6/img021.png new file mode 100644 index 000000000000..7a11ee389111 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img021.png differ diff --git a/spring-lxcecho-notes/images/spring6/img022.png b/spring-lxcecho-notes/images/spring6/img022.png new file mode 100644 index 000000000000..55e1770f49e7 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img022.png differ diff --git a/spring-lxcecho-notes/images/spring6/img023.png b/spring-lxcecho-notes/images/spring6/img023.png new file mode 100644 index 000000000000..778ef9c11d08 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img023.png differ diff --git a/spring-lxcecho-notes/images/spring6/img024.png b/spring-lxcecho-notes/images/spring6/img024.png new file mode 100644 index 000000000000..ca417ccead0f Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img024.png differ diff --git a/spring-lxcecho-notes/images/spring6/img025.png b/spring-lxcecho-notes/images/spring6/img025.png new file mode 100644 index 000000000000..9de60ec415e5 Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img025.png differ diff --git a/spring-lxcecho-notes/images/spring6/img026.png b/spring-lxcecho-notes/images/spring6/img026.png new file mode 100644 index 000000000000..22c54531b9fa Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img026.png differ diff --git a/spring-lxcecho-notes/images/spring6/img027.png b/spring-lxcecho-notes/images/spring6/img027.png new file mode 100644 index 000000000000..22cad312fd7a Binary files /dev/null and b/spring-lxcecho-notes/images/spring6/img027.png differ diff --git a/spring-lxcecho-notes/spring6.md b/spring-lxcecho-notes/spring6.md new file mode 100644 index 000000000000..4ae80c022355 --- /dev/null +++ b/spring-lxcecho-notes/spring6.md @@ -0,0 +1,6603 @@ +# Spring6 + +![image-20221209110043449](images\spring6\image-20221209110043449.png) + +## 1、概述 + +### 1.1、Spring是什么? + +Spring 是一款主流的 Java EE 轻量级开源框架 ,Spring 由“Spring 之父”Rod Johnson 提出并创立,其目的是用于简化 Java 企业级应用的开发难度和开发周期。Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring 框架除了自己提供功能外,还提供整合其他技术和框架的能力。 + +Spring 自诞生以来备受青睐,一直被广大开发人员作为 Java 企业级应用程序开发的首选。时至今日,Spring 俨然成为了 Java EE 代名词,成为了构建 Java EE 应用的事实标准。 + +自 2004 年 4 月,Spring 1.0 版本正式发布以来,Spring 已经步入到了第 6 个大版本,也就是 Spring 6。本课程采用Spring当前最新发布的正式版本**6.0.2**。 + +![image-20221216223135162](images\spring6\image-20221201102513199.png) + + + +### 1.2、Spring 的狭义和广义 + +在不同的语境中,Spring 所代表的含义是不同的。下面我们就分别从“广义”和“狭义”两个角度,对 Spring 进行介绍。 + +**广义的 Spring:Spring 技术栈** + +广义上的 Spring 泛指以 Spring Framework 为核心的 Spring 技术栈。 + +经过十多年的发展,Spring 已经不再是一个单纯的应用框架,而是逐渐发展成为一个由多个不同子项目(模块)组成的成熟技术,例如 Spring Framework、Spring MVC、SpringBoot、Spring Cloud、Spring Data、Spring Security 等,其中 Spring Framework 是其他子项目的基础。 + +这些子项目涵盖了从企业级应用开发到云计算等各方面的内容,能够帮助开发人员解决软件发展过程中不断产生的各种实际问题,给开发人员带来了更好的开发体验。 + +**狭义的 Spring:Spring Framework** + +狭义的 Spring 特指 Spring Framework,通常我们将它称为 Spring 框架。 + +Spring 框架是一个分层的、面向切面的 Java 应用程序的一站式轻量级解决方案,它是 Spring 技术栈的核心和基础,是为了解决企业级应用开发的复杂性而创建的。 + +Spring 有两个最核心模块: IoC 和 AOP。 + +**IoC**:Inverse of Control 的简写,译为“控制反转”,指把创建对象过程交给 Spring 进行管理。 + +**AOP**:Aspect Oriented Programming 的简写,译为“面向切面编程”。AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。 + + + +### 1.3、Spring Framework特点 + +- 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。 + +- 控制反转:IoC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好,我们享受资源注入。 + +- 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。 + +- 容器:Spring IoC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发效率。 + +- 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。 + +- 一站式:在 IoC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。 + + + +### 1.4、Spring模块组成 + +官网地址:https://spring.io/ + +![image-20221207142746771](images\spring6\image-20221207142746771.png) + +![image-2097896352](images/spring6/2097896352.png) + +上图中包含了 Spring 框架的所有模块,这些模块可以满足一切企业级应用开发的需求,在开发过程中可以根据需求有选择性地使用所需要的模块。下面分别对这些模块的作用进行简单介绍。 + +**①Spring Core(核心容器)** + +spring core提供了IOC,DI,Bean配置装载创建的核心实现。核心概念: Beans、BeanFactory、BeanDefinitions、ApplicationContext。 + +- spring-core :IOC和DI的基本实现 + +- spring-beans:BeanFactory和Bean的装配管理(BeanFactory) +- spring-context:Spring context上下文,即IOC容器(AppliactionContext) +- spring-expression:spring表达式语言 + +**②Spring AOP** + +- spring-aop:面向切面编程的应用模块,整合ASM,CGLib,JDK Proxy +- spring-aspects:集成AspectJ,AOP应用框架 +- spring-instrument:动态Class Loading模块 + +**③Spring Data Access** + +- spring-jdbc:spring对JDBC的封装,用于简化jdbc操作 +- spring-orm:java对象与数据库数据的映射框架 +- spring-oxm:对象与xml文件的映射框架 +- spring-jms: Spring对Java Message Service(java消息服务)的封装,用于服务之间相互通信 +- spring-tx:spring jdbc事务管理 + +**④Spring Web** + +- spring-web:最基础的web支持,建立于spring-context之上,通过servlet或listener来初始化IOC容器 +- spring-webmvc:实现web mvc +- spring-websocket:与前端的全双工通信协议 +- spring-webflux:Spring 5.0提供的,用于取代传统java servlet,非阻塞式Reactive Web框架,异步,非阻塞,事件驱动的服务 + +**⑤Spring Message** + +- Spring-messaging:spring 4.0提供的,为Spring集成一些基础的报文传送服务 + +**⑥Spring test** + +- spring-test:集成测试支持,主要是对junit的封装 + + + +### 1.5、Spring6特点 + +#### 1.5.1、版本要求 + +**(1)Spring6要求JDK最低版本是JDK17** + +![image-20221201103138194](images\spring6\image-20221201103138194.png) + +#### 1.5.2、本课程软件版本 + +(1)IDEA开发工具:2022.1.2 + +(2)JDK:Java17**(Spring6要求JDK最低版本是Java17)** + +(3)Spring:6.0.2 + + + +## 2、入门 + +### 2.1、环境要求 + +- JDK:Java17+**(Spring6要求JDK最低版本是Java17)** + +- Maven:3.6+ + +- Spring:6.0.2 + + + +### 2.2、构建模块 + +**(1)构建父模块spring6** + +在idea中,依次单击 File -> New -> Project -> New Project + +![image-20221205201741893](images\spring6\image-20221205201741893.png) + + + +点击“Create” + +![image-20221205202000198](images\spring6\image-20221205202000198.png) + +删除src目录 + +**(2)构建子模块spring6-first** + +![image-20221205202117383](images\spring6\image-20221205202117383.png) + +点击 Create 完成 + +![image-20221205202154225](images\spring6\image-20221205202154225.png) + + + +### 2.3、程序开发 + +#### 2.3.1、引入依赖 + +https://spring.io/projects/spring-framework#learn + +**添加依赖:** + +```xml + + + + + org.springframework + spring-context + 6.0.2 + + + + + org.junit.jupiter + junit-jupiter-api + 5.3.1 + + +``` + +**查看依赖:** + +![image-20221201105416558](images\spring6\image-20221201105416558.png) + +#### 2.3.2、创建java类 + +```java +package com.lxcecho.bean; + +public class HelloWorld { + + public void sayHello(){ + System.out.println("helloworld"); + } +} +``` + +#### 2.3.3、创建配置文件 + +在resources目录创建一个 Spring 配置文件 beans.xml(配置文件名称可随意命名,如:springs.xm) + +![img007](images/spring6/img007.png) + +```xml + + + + + + + +``` + +#### 2.3.4、创建测试类测试 + +```java +package com.lxcecho.bean; + +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class HelloWorldTest { + + @Test + public void testHelloWorld(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); + HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld"); + helloworld.sayHello(); + } +} +``` + +#### 2.3.5、运行测试程序 + +![image-20221031172354535](images/spring6/image-20221031172354535.png) + + + +### 2.4、程序分析 + +**1. 底层是怎么创建对象的,是通过反射机制调用无参数构造方法吗?** + +修改HelloWorld类: + +```java +package com.lxcecho.bean; + +public class HelloWorld { + + public HelloWorld() { + System.out.println("无参数构造方法执行"); + } + + public void sayHello(){ + System.out.println("helloworld"); + } +} +``` + +执行结果: + +![image-20221031181430720](images/spring6/image-20221031181430720.png) + +**测试得知:创建对象时确实调用了无参数构造方法。** + + + +**2. Spring是如何创建对象的呢?原理是什么?** + +```java +// dom4j解析beans.xml文件,从中获取class属性值,类的全类名 + // 通过反射机制调用无参数构造方法创建对象 + Class clazz = Class.forName("com.lxcecho.bean.HelloWorld"); + //Object obj = clazz.newInstance(); + Object object = clazz.getDeclaredConstructor().newInstance(); +``` + + + +**3. 把创建好的对象存储到一个什么样的数据结构当中了呢?** + +bean对象最终存储在spring容器中,在spring源码底层就是一个map集合,存储bean的map在**DefaultListableBeanFactory**类中: + +```java +private final Map beanDefinitionMap = new ConcurrentHashMap<>(256); +``` + +Spring容器加载到Bean类时 , 会把这个类的描述信息, 以包名加类名的方式存到beanDefinitionMap 中, +Map , 其中 String是Key , 默认是类名首字母小写 , BeanDefinition , 存的是类的定义(描述信息) , 我们通常叫BeanDefinition接口为 : bean的定义对象。 + + + +### 2.5、启用Log4j2日志框架 + +#### 2.5.1、Log4j2日志概述 + +在项目开发中,日志十分的重要,不管是记录运行情况还是定位线上问题,都离不开对日志的分析。日志记录了系统行为的时间、地点、状态等相关信息,能够帮助我们了解并监控系统状态,在发生错误或者接近某种危险状态时能够及时提醒我们处理,同时在系统产生问题时,能够帮助我们快速的定位、诊断并解决问题。 + +**Apache Log4j2**是一个开源的日志记录组件,使用非常的广泛。在工程中以易用方便代替了 System.out 等打印语句,它是JAVA下最流行的日志输入工具。 + +**Log4j2主要由几个重要的组件构成:** + +**(1)日志信息的优先级**,日志信息的优先级从高到低有**TRACE < DEBUG < INFO < WARN < ERROR < FATAL** + TRACE:追踪,是最低的日志级别,相当于追踪程序的执行 + DEBUG:调试,一般在开发中,都将其设置为最低的日志级别 + INFO:信息,输出重要的信息,使用较多 + WARN:警告,输出警告的信息 + ERROR:错误,输出错误信息 + FATAL:严重错误 + +这些级别分别用来指定这条日志信息的重要程度;级别高的会自动屏蔽级别低的日志,也就是说,设置了WARN的日志,则INFO、DEBUG的日志级别的日志不会显示 + +**(2)日志信息的输出目的地**,日志信息的输出目的地指定了日志将打印到**控制台**还是**文件中**; + +**(3)日志信息的输出格式**,而输出格式则控制了日志信息的显示内容。 + + + +#### 2.5.2、引入Log4j2依赖 + +```xml + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.19.0 + +``` + + + +#### 2.5.3、加入日志配置文件 + +在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放到类根路径下。) + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + + + +#### 2.5.4、测试 + +运行原测试程序 + +![image-20221031214305224](images/spring6/image-20221031214305224.png) + +运行原测试程序,多了spring打印日志 + + + +#### 2.5.5、使用日志 + +```java +public class HelloWorldTest { + + private Logger logger = LoggerFactory.getLogger(HelloWorldTest.class); + + @Test + public void testHelloWorld(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); + HelloWorld helloworld = (HelloWorld) ac.getBean("helloWorld"); + helloworld.sayHello(); + logger.info("执行成功"); + } +} +``` + +控制台: + +![image-20221031214547501](images/spring6/image-20221031214547501.png) + + + +## 3、容器:IoC + +IoC 是 Inversion of Control 的简写,译为“控制反转”,它不是一门技术,而是一种设计思想,是一个重要的面向对象编程法则,能够指导我们如何设计出松耦合、更优良的程序。 + +Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。 + +IoC 容器是 Spring 框架中最重要的核心组件之一,它贯穿了 Spring 从诞生到成长的整个过程。 + +### 3.1、IoC容器 + +#### 3.1.1、控制反转(IoC) + +- 控制反转是一种思想。 +- 控制反转是为了降低程序耦合度,提高程序扩展力。 +- 控制反转,反转的是什么? + +- - 将对象的创建权利交出去,交给第三方容器负责。 + - 将对象和对象之间关系的维护权交出去,交给第三方容器负责。 + +- 控制反转这种思想如何实现呢? + +- - DI(Dependency Injection):依赖注入 + +#### 3.1.2、依赖注入 + +DI(Dependency Injection):依赖注入,依赖注入实现了控制反转的思想。 + +**依赖注入:** + +- **指Spring创建对象的过程中,将对象依赖属性通过配置进行注入** + +依赖注入常见的实现方式包括两种: + +- 第一种:set注入 +- 第二种:构造注入 + +所以结论是:IOC 就是一种控制反转的思想, 而 DI 是对IoC的一种具体实现。 + +**Bean管理说的是:Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。** + +#### 3.1.3、IoC容器在Spring的实现 + +Spring 的 IoC 容器就是 IoC思想的一个落地的产品实现。IoC容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建IoC 容器。Spring 提供了IoC 容器的两种实现方式: + +**①BeanFactory** + +这是 IoC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。 + +**②ApplicationContext** + +BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。 + +**③ApplicationContext的主要实现类** + +![iamges](images/spring6/img005.png) + +| 类型名 | 简介 | +| ------------------------------- | ------------------------------------------------------------ | +| ClassPathXmlApplicationContext | 通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象 | +| FileSystemXmlApplicationContext | 通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象 | +| ConfigurableApplicationContext | ApplicationContext 的子接口,包含一些扩展方法 refresh() 和 close() ,让 ApplicationContext 具有启动、关闭和刷新上下文的能力。 | +| WebApplicationContext | 专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。 | + + + +### 3.2、基于XML管理Bean + +#### 3.2.1、搭建子模块spring6-ioc-xml + +**①搭建模块** + +搭建方式如:spring-first + +**②引入配置文件** + +引入spring-first模块配置文件:beans.xml、log4j2.xml + +**③添加依赖** + +```xml + + + + + org.springframework + spring-context + 6.0.3 + + + + + org.junit.jupiter + junit-jupiter-api + 5.3.1 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.19.0 + + +``` + +**④引入java类** + +引入spring-first模块java及test目录下实体类 + +```java +package com.lxcecho.bean; + +public class HelloWorld { + + public HelloWorld() { + System.out.println("无参数构造方法执行"); + } + + public void sayHello(){ + System.out.println("helloworld"); + } +} + +``` + +```java +package com.lxcecho.bean; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class HelloWorldTest { + + private Logger logger = LoggerFactory.getLogger(HelloWorldTest.class); + + @Test + public void testHelloWorld(){ + + } +} +``` + + + +#### 3.2.2、实验一:获取bean + +##### ①方式一:根据id获取 + +由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。上个实验中我们使用的就是这种方式。 + +##### ②方式二:根据类型获取 + +```java +@Test +public void testHelloWorld1(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); + HelloWorld bean = ac.getBean(HelloWorld.class); + bean.sayHello(); +} +``` + +##### ③方式三:根据id和类型 + +```java +@Test +public void testHelloWorld2(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); + HelloWorld bean = ac.getBean("helloworld", HelloWorld.class); + bean.sayHello(); +} +``` + +##### ④注意的地方 + +当根据类型获取bean时,要求IOC容器中指定类型的bean有且只能有一个 + +当IOC容器中一共配置了两个: + +```xml + + +``` + +根据类型获取时会抛出异常: + +> org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.lxcecho.bean.HelloWorld' available: expected single matching bean but found 2: helloworldOne,helloworldTwo + +##### ⑤扩展知识 + +如果组件类实现了接口,根据接口类型可以获取 bean 吗? + +> 可以,前提是bean唯一 + +如果一个接口有多个实现类,这些实现类都配置了 bean,根据接口类型可以获取 bean 吗? + +> 不行,因为bean不唯一 + +**结论** + +根据类型来获取bean时,在满足bean唯一性的前提下,其实只是看:『对象 **instanceof** 指定的类型』的返回结果,只要返回的是true就可以认定为和类型匹配,能够获取到。 + +java中,instanceof运算符用于判断前面的对象是否是后面的类,或其子类、实现类的实例。如果是返回true,否则返回false。也就是说:用instanceof关键字做判断时, instanceof 操作符的左右操作必须有继承或实现关系 + + +#### 3.2.3、实验二:依赖注入之setter注入 + +**①创建学生类Student** + +```java +package com.lxcecho.bean; + +public class Student { + + private Integer id; + + private String name; + + private Integer age; + + private String sex; + + public Student() { + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } + + @Override + public String toString() { + return "Student{" + + "id=" + id + + ", name='" + name + '\'' + + ", age=" + age + + ", sex='" + sex + '\'' + + '}'; + } + +} +``` + +**②配置bean时为属性赋值** + +spring-di.xml + +```xml + + + + + + + + + +``` + +**③测试** + +```java +@Test +public void testDIBySet(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml"); + Student studentOne = ac.getBean("studentOne", Student.class); + System.out.println(studentOne); +} +``` + + + +#### 3.2.4、实验三:依赖注入之构造器注入 + +**①在Student类中添加有参构造** + +```java +public Student(Integer id, String name, Integer age, String sex) { + this.id = id; + this.name = name; + this.age = age; + this.sex = sex; +} +``` + +**②配置bean** + +spring-di.xml + +```xml + + + + + + +``` + +> 注意: +> +> constructor-arg标签还有两个属性可以进一步描述构造器参数: +> +> - index属性:指定参数所在位置的索引(从0开始) +> - name属性:指定参数名 + +**③测试** + +```java +@Test +public void testDIByConstructor(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("spring-di.xml"); + Student studentOne = ac.getBean("studentTwo", Student.class); + System.out.println(studentOne); +} +``` + + + +#### 3.2.5、实验四:特殊值处理 + +##### ①字面量赋值 + +> 什么是字面量? +> +> int a = 10; +> +> 声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。 +> +> 而如果a是带引号的:'a',那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。 + +```xml + + +``` + +##### ②null值 + +```xml + + + +``` + +> 注意: +> +> ```xml +> +> ``` +> +> 以上写法,为name所赋的值是字符串null + +##### ③xml实体 + +```xml + + + +``` + +##### ④CDATA节 + +```xml + + + + + + + +``` + + + +#### 3.2.6、实验五:为对象类型属性赋值 + +**①创建班级类Clazz** + +```java +package com.lxcecho.bean + +public class Clazz { + + private Integer clazzId; + + private String clazzName; + + public Integer getClazzId() { + return clazzId; + } + + public void setClazzId(Integer clazzId) { + this.clazzId = clazzId; + } + + public String getClazzName() { + return clazzName; + } + + public void setClazzName(String clazzName) { + this.clazzName = clazzName; + } + + @Override + public String toString() { + return "Clazz{" + + "clazzId=" + clazzId + + ", clazzName='" + clazzName + '\'' + + '}'; + } + + public Clazz() { + } + + public Clazz(Integer clazzId, String clazzName) { + this.clazzId = clazzId; + this.clazzName = clazzName; + } +} +``` + +**②修改Student类** + +在Student类中添加以下代码: + +```java +private Clazz clazz; + +public Clazz getClazz() { + return clazz; +} + +public void setClazz(Clazz clazz) { + this.clazz = clazz; +} +``` + +##### 方式一:引用外部bean + +配置Clazz类型的bean: + +```xml + + + + +``` + +为Student中的clazz属性赋值: + +```xml + + + + + + + + +``` + +错误演示: + +```xml + + + + + + + +``` + +> 如果错把ref属性写成了value属性,会抛出异常: Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.lxcecho.bean.Clazz' for property 'clazz': no matching editors or conversion strategy found +> +> 意思是不能把String类型转换成我们要的Clazz类型,说明我们使用value属性时,Spring只把这个属性看做一个普通的字符串,不会认为这是一个bean的id,更不会根据它去找到bean来赋值 + + + +##### 方式二:内部bean + +```xml + + + + + + + + + + + + + + +``` + +##### 方式三:级联属性赋值 + +```xml + + + + + + + + + +``` + + + +#### 3.2.7、实验六:为数组类型属性赋值 + +**①修改Student类** + +在Student类中添加以下代码: + +```java +private String[] hobbies; + +public String[] getHobbies() { + return hobbies; +} + +public void setHobbies(String[] hobbies) { + this.hobbies = hobbies; +} +``` + +**②配置bean** + +```xml + + + + + + + + + + 抽烟 + 喝酒 + 烫头 + + + +``` + + + +#### 3.2.8、实验七:为集合类型属性赋值 + +##### ①为List集合类型属性赋值 + +在Clazz类中添加以下代码: + +```java +private List students; + +public List getStudents() { + return students; +} + +public void setStudents(List students) { + this.students = students; +} +``` + +配置bean: + +```xml + + + + + + + + + + + +``` + +> 若为Set集合类型属性赋值,只需要将其中的list标签改为set标签即可 + +##### ②为Map集合类型属性赋值 + +创建教师类Teacher: + +```java +package com.lxcecho.bean; +public class Teacher { + + private Integer teacherId; + + private String teacherName; + + public Integer getTeacherId() { + return teacherId; + } + + public void setTeacherId(Integer teacherId) { + this.teacherId = teacherId; + } + + public String getTeacherName() { + return teacherName; + } + + public void setTeacherName(String teacherName) { + this.teacherName = teacherName; + } + + public Teacher(Integer teacherId, String teacherName) { + this.teacherId = teacherId; + this.teacherName = teacherName; + } + + public Teacher() { + + } + + @Override + public String toString() { + return "Teacher{" + + "teacherId=" + teacherId + + ", teacherName='" + teacherName + '\'' + + '}'; + } +} +``` + +在Student类中添加以下代码: + +```java +private Map teacherMap; + +public Map getTeacherMap() { + return teacherMap; +} + +public void setTeacherMap(Map teacherMap) { + this.teacherMap = teacherMap; +} +``` + +配置bean: + +```xml + + + + + + + + + + + + + + + + + + + + 抽烟 + 喝酒 + 烫头 + + + + + + + 10010 + + + + + + 10086 + + + + + + +``` + +##### ③引用集合类型的bean + +```xml + + + + + + + + + + + 10010 + + + + + + 10086 + + + + + + + + + + + + + + + + + + + 抽烟 + 喝酒 + 烫头 + + + + +``` + +> 使用util:list、util:map标签必须引入相应的命名空间 +> +> ```xml +> +> xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +> xmlns:util="http://www.springframework.org/schema/util" +> xsi:schemaLocation="http://www.springframework.org/schema/util +> http://www.springframework.org/schema/util/spring-util.xsd +> http://www.springframework.org/schema/beans +> http://www.springframework.org/schema/beans/spring-beans.xsd"> +> ``` + + + +#### 3.2.9、实验八:p命名空间 + +引入p命名空间 + +```xml + + +``` + +引入p命名空间后,可以通过以下方式为bean的各个属性赋值 + +```xml + +``` + + + +#### 3.2.10、实验九:引入外部属性文件 + +**①加入依赖** + +```xml + + + mysql + mysql-connector-java + 8.0.30 + + + + + com.alibaba + druid + 1.2.15 + +``` + +**②创建外部属性文件** + +![images](images/spring6/img010.png) + +```properties +jdbc.user=root +jdbc.password=lxcecho +jdbc.url=jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC +jdbc.driver=com.mysql.cj.jdbc.Driver +``` + +**③引入属性文件** + +引入context 名称空间 + +```xml + + + + +``` + +```xml + + +``` + +注意:在使用 元素加载外包配置文件功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。 + +**④配置bean** + +```xml + + + + + + +``` + +**⑤测试** + +```java +@Test +public void testDataSource() throws SQLException { + ApplicationContext ac = new ClassPathXmlApplicationContext("spring-datasource.xml"); + DataSource dataSource = ac.getBean(DataSource.class); + Connection connection = dataSource.getConnection(); + System.out.println(connection); +} +``` + + + +#### 3.2.11、实验十:bean的作用域 + +**①概念** + +在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表: + +| 取值 | 含义 | 创建对象的时机 | +| ----------------- | --------------------------------------- | --------------- | +| singleton(默认) | 在IOC容器中,这个bean的对象始终为单实例 | IOC容器初始化时 | +| prototype | 这个bean在IOC容器中有多个实例 | 获取bean时 | + +如果是在WebApplicationContext环境下还会有另外几个作用域(但不常用): + +| 取值 | 含义 | +| ------- | -------------------- | +| request | 在一个请求范围内有效 | +| session | 在一个会话范围内有效 | + +**②创建类User** + +```java +package com.lxcecho.bean; +public class User { + + private Integer id; + + private String username; + + private String password; + + private Integer age; + + public User() { + } + + public User(Integer id, String username, String password, Integer age) { + this.id = id; + this.username = username; + this.password = password; + this.age = age; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + ", age=" + age + + '}'; + } +} +``` + +**③配置bean** + +```xml + + + +``` + +**④测试** + +```java +@Test +public void testBeanScope(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("spring-scope.xml"); + User user1 = ac.getBean(User.class); + User user2 = ac.getBean(User.class); + System.out.println(user1==user2); +} +``` + + + +#### 3.2.12、实验十一:bean生命周期 + +**①具体的生命周期过程** + +- bean对象创建(调用无参构造器) + +- 给bean对象设置属性 + +- bean的后置处理器(初始化之前) + +- bean对象初始化(需在配置bean时指定初始化方法) + +- bean的后置处理器(初始化之后) + +- bean对象就绪可以使用 + +- bean对象销毁(需在配置bean时指定销毁方法) + +- IOC容器关闭 + +**②修改类User** + +```java +public class User { + + private Integer id; + + private String username; + + private String password; + + private Integer age; + + public User() { + System.out.println("生命周期:1、创建对象"); + } + + public User(Integer id, String username, String password, Integer age) { + this.id = id; + this.username = username; + this.password = password; + this.age = age; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + System.out.println("生命周期:2、依赖注入"); + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public void initMethod(){ + System.out.println("生命周期:3、初始化"); + } + + public void destroyMethod(){ + System.out.println("生命周期:5、销毁"); + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + ", username='" + username + '\'' + + ", password='" + password + '\'' + + ", age=" + age + + '}'; + } +} +``` + +> 注意其中的initMethod()和destroyMethod(),可以通过配置bean指定为初始化和销毁的方法 + +**③配置bean** + +```xml + + + + + + + + +``` + +**④测试** + +```java +@Test +public void testLife(){ + ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring-lifecycle.xml"); + User bean = ac.getBean(User.class); + System.out.println("生命周期:4、通过IOC容器获取bean并使用"); + ac.close(); +} +``` + +**⑤bean的后置处理器** + +bean的后置处理器会在生命周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行 + +创建bean的后置处理器: + +```java +package com.lxcecho.process; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +public class MyBeanProcessor implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("☆☆☆" + beanName + " = " + bean); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("★★★" + beanName + " = " + bean); + return bean; + } +} +``` + +在IOC容器中配置后置处理器: + +```xml + + +``` + + + +#### 3.2.13、实验十二:FactoryBean + +**①简介** + +FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。 + +将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。 + +```java +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.beans.factory; + +import org.springframework.lang.Nullable; + +/** + * 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 + */ +public interface FactoryBean { + + /** + * The name of an attribute that can be + * {@link org.springframework.core.AttributeAccessor#setAttribute set} on a + * {@link org.springframework.beans.factory.config.BeanDefinition} so that + * factory beans can signal their object type when it can't be deduced from + * the factory bean class. + * @since 5.2 + */ + String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType"; + + /** + * Return an instance (possibly shared or independent) of the object + * managed by this factory. + *

As with a {@link BeanFactory}, this allows support for both the + * Singleton and Prototype design pattern. + *

If this FactoryBean is not fully initialized yet at the time of + * the call (for example because it is involved in a circular reference), + * throw a corresponding {@link FactoryBeanNotInitializedException}. + *

As of Spring 2.0, FactoryBeans are allowed to return {@code null} + * objects. The factory will consider this as normal value to be used; it + * will not throw a FactoryBeanNotInitializedException in this case anymore. + * FactoryBean implementations are encouraged to throw + * FactoryBeanNotInitializedException themselves now, as appropriate. + * @return an instance of the bean (can be {@code null}) + * @throws Exception in case of creation errors + * @see FactoryBeanNotInitializedException + */ + @Nullable + T getObject() throws Exception; + + /** + * Return the type of object that this FactoryBean creates, + * or {@code null} if not known in advance. + *

This allows one to check for specific types of beans without + * instantiating objects, for example on autowiring. + *

In the case of implementations that are creating a singleton object, + * this method should try to avoid singleton creation as far as possible; + * it should rather estimate the type in advance. + * For prototypes, returning a meaningful type here is advisable too. + *

This method can be called before this FactoryBean has + * been fully initialized. It must not rely on state created during + * initialization; of course, it can still use such state if available. + *

NOTE: Autowiring will simply ignore FactoryBeans that return + * {@code null} here. Therefore it is highly recommended to implement + * this method properly, using the current state of the FactoryBean. + * @return the type of object that this FactoryBean creates, + * or {@code null} if not known at the time of the call + * @see ListableBeanFactory#getBeansOfType + */ + @Nullable + Class getObjectType(); + + /** + * Is the object managed by this factory a singleton? That is, + * will {@link #getObject()} always return the same object + * (a reference that can be cached)? + *

NOTE: If a FactoryBean indicates to hold a singleton object, + * the object returned from {@code getObject()} might get cached + * by the owning BeanFactory. Hence, do not return {@code true} + * unless the FactoryBean always exposes the same reference. + *

The singleton status of the FactoryBean itself will generally + * be provided by the owning BeanFactory; usually, it has to be + * defined as singleton there. + *

NOTE: This method returning {@code false} does not + * necessarily indicate that returned objects are independent instances. + * An implementation of the extended {@link SmartFactoryBean} interface + * may explicitly indicate independent instances through its + * {@link SmartFactoryBean#isPrototype()} method. Plain {@link FactoryBean} + * implementations which do not implement this extended interface are + * simply assumed to always return independent instances if the + * {@code isSingleton()} implementation returns {@code false}. + *

The default implementation returns {@code true}, since a + * {@code FactoryBean} typically manages a singleton instance. + * @return whether the exposed object is a singleton + * @see #getObject() + * @see SmartFactoryBean#isPrototype() + */ + default boolean isSingleton() { + return true; + } +} +``` + +**②创建类UserFactoryBean** + +```java +package com.lxcecho.bean; +public class UserFactoryBean implements FactoryBean { + @Override + public User getObject() throws Exception { + return new User(); + } + + @Override + public Class getObjectType() { + return User.class; + } +} +``` + +**③配置bean** + +```xml + +``` + +**④测试** + +```java +@Test +public void testUserFactoryBean(){ + //获取IOC容器 + ApplicationContext ac = new ClassPathXmlApplicationContext("spring-factorybean.xml"); + User user = (User) ac.getBean("user"); + System.out.println(user); +} +``` + + + +#### 3.2.14、实验十三:基于xml自动装配 + +> 自动装配: +> +> 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值 + +**①场景模拟** + +创建类UserController + +```java +package com.lxcecho.autowire.controller +public class UserController { + + private UserService userService; + + public void setUserService(UserService userService) { + this.userService = userService; + } + + public void saveUser(){ + userService.saveUser(); + } + +} +``` + +创建接口UserService + +```java +package com.lxcecho.autowire.service +public interface UserService { + + void saveUser(); + +} +``` + +创建类UserServiceImpl实现接口UserService + +```java +package com.lxcecho.autowire.service.impl +public class UserServiceImpl implements UserService { + + private UserDao userDao; + + public void setUserDao(UserDao userDao) { + this.userDao = userDao; + } + + @Override + public void saveUser() { + userDao.saveUser(); + } + +} +``` + +创建接口UserDao + +```java +package com.lxcecho.autowire.dao +public interface UserDao { + + void saveUser(); + +} +``` + +创建类UserDaoImpl实现接口UserDao + +```java +package com.lxcecho.autowire.dao.impl +public class UserDaoImpl implements UserDao { + + @Override + public void saveUser() { + System.out.println("保存成功"); + } + +} +``` + +**②配置bean** + +> 使用bean标签的autowire属性设置自动装配效果 +> +> 自动装配方式:byType +> +> byType:根据类型匹配IOC容器中的某个兼容类型的bean,为属性自动赋值 +> +> 若在IOC中,没有任何一个兼容类型的bean能够为属性赋值,则该属性不装配,即值为默认值null +> +> 若在IOC中,有多个兼容类型的bean能够为属性赋值,则抛出异常NoUniqueBeanDefinitionException + +```xml + + + + + +``` + +> 自动装配方式:byName +> +> byName:将自动装配的属性的属性名,作为bean的id在IOC容器中匹配相对应的bean进行赋值 + +```xml + + + + + + + +``` + +**③测试** + +```java +@Test +public void testAutoWireByXML(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("autowire-xml.xml"); + UserController userController = ac.getBean(UserController.class); + userController.saveUser(); +} +``` + + + +### 3.3、基于注解管理Bean(☆) + +从 Java 5 开始,Java 增加了对注解(Annotation)的支持,它是代码中的一种特殊标记,可以在编译、类加载和运行时被读取,执行相应的处理。开发人员可以通过注解在不改变原有代码和逻辑的情况下,在源代码中嵌入补充信息。 + +Spring 从 2.5 版本开始提供了对注解技术的全面支持,我们可以使用注解来实现自动装配,简化 Spring 的 XML 配置。 + +Spring 通过注解实现自动装配的步骤如下: + +1. 引入依赖 +2. 开启组件扫描 +3. 使用注解定义 Bean +4. 依赖注入 + +#### 3.3.1、搭建子模块spring6-ioc-annotation + +**①搭建模块** + +搭建方式如:spring6-ioc-xml + +**②引入配置文件** + +引入spring-ioc-xml模块日志log4j2.xml + +**③添加依赖** + +```xml + + + + + org.springframework + spring-context + 6.0.3 + + + + + org.junit.jupiter + junit-jupiter-api + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.19.0 + + +``` + +#### 3.3.2、开启组件扫描 + +Spring 默认不使用注解装配 Bean,因此我们需要在 Spring 的 XML 配置中,通过 元素开启 Spring Beans的自动扫描功能。开启此功能后,Spring 会自动从扫描指定的包(base-package 属性设置)及其子包下的所有类,如果类上使用了 @Component 注解,就将该类装配到容器中。 + +```xml + + + + + +``` + +注意:在使用 元素开启自动扫描功能前,首先需要在 XML 配置的一级标签 中添加 context 相关的约束。 + +**情况一:最基本的扫描方式** + +```xml + + +``` + +**情况二:指定要排除的组件** + +```xml + + + + + + +``` + +**情况三:仅扫描指定组件** + +```xml + + + + + + + + +``` + + + +#### 3.3.3、使用注解定义 Bean + +Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。 + +| 注解 | 说明 | +| ----------- | ------------------------------------------------------------ | +| @Component | 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。 | +| @Repository | 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 | +| @Service | 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 | +| @Controller | 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。 | + + + +#### 3.3.4、实验一:@Autowired注入 + +单独使用@Autowired注解,**默认根据类型装配**。【默认是byType】 + +查看源码: + +```java +package org.springframework.beans.factory.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Autowired { + boolean required() default true; +} +``` + +源码中有两处需要注意: + +- 第一处:该注解可以标注在哪里? + +- - 构造方法上 + - 方法上 + - 形参上 + - 属性上 + - 注解上 + +- 第二处:该注解有一个required属性,默认值是true,表示在注入的时候要求被注入的Bean必须是存在的,如果不存在则报错。如果required属性设置为false,表示注入的Bean存在或者不存在都没关系,存在的话就注入,不存在的话,也不报错。 + +##### ①场景一:属性注入 + +创建UserDao接口 + +```java +package com.lxcecho.dao; + +public interface UserDao { + + public void print(); +} +``` + +创建UserDaoImpl实现 + +```java +package com.lxcecho.dao.impl; + +import com.lxcecho.dao.UserDao; +import org.springframework.stereotype.Repository; + +@Repository +public class UserDaoImpl implements UserDao { + + @Override + public void print() { + System.out.println("Dao层执行结束"); + } +} +``` + +创建UserService接口 + +```java +package com.lxcecho.service; + +public interface UserService { + + public void out(); +} +``` + +创建UserServiceImpl实现类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserDao userDao; + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +创建UserController类 + +```java +package com.lxcecho.controller; + +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class UserController { + + @Autowired + private UserService userService; + + public void out() { + userService.out(); + System.out.println("Controller层执行结束。"); + } + +} +``` + +**测试一** + +```java +package com.lxcecho.bean; + +import com.lxcecho.controller.UserController; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class UserTest { + + private Logger logger = LoggerFactory.getLogger(UserTest.class); + + @Test + public void testAnnotation(){ + ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml"); + UserController userController = context.getBean("userController", UserController.class); + userController.out(); + logger.info("执行成功"); + } + + +} +``` + +测试结果: + +![image-20221101153556681](images/spring6/image-20221101153556681.png) + +以上构造方法和setter方法都没有提供,经过测试,仍然可以注入成功。 + +##### ②场景二:set注入 + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + private UserDao userDao; + + @Autowired + public void setUserDao(UserDao userDao) { + this.userDao = userDao; + } + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +修改UserController类 + +```java +package com.lxcecho.controller; + +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class UserController { + + private UserService userService; + + @Autowired + public void setUserService(UserService userService) { + this.userService = userService; + } + + public void out() { + userService.out(); + System.out.println("Controller层执行结束。"); + } + +} +``` + +测试:成功调用 + +##### ③场景三:构造方法注入 + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + private UserDao userDao; + + @Autowired + public UserServiceImpl(UserDao userDao) { + this.userDao = userDao; + } + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +修改UserController类 + +```java +package com.lxcecho.controller; + +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class UserController { + + private UserService userService; + + @Autowired + public UserController(UserService userService) { + this.userService = userService; + } + + public void out() { + userService.out(); + System.out.println("Controller层执行结束。"); + } + +} +``` + +测试:成功调用 + +##### ④场景四:形参上注入 + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + private UserDao userDao; + + public UserServiceImpl(@Autowired UserDao userDao) { + this.userDao = userDao; + } + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +修改UserController类 + +```java +package com.lxcecho.controller; + +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +@Controller +public class UserController { + + private UserService userService; + + public UserController(@Autowired UserService userService) { + this.userService = userService; + } + + public void out() { + userService.out(); + System.out.println("Controller层执行结束。"); + } + +} +``` + +测试:成功调用 + +##### ⑤场景五:只有一个构造函数,无注解 + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + private UserDao userDao; + + public UserServiceImpl(UserDao userDao) { + this.userDao = userDao; + } + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +测试通过 + +**当有参数的构造方法只有一个时,@Autowired注解可以省略。** + +说明:有多个构造方法时呢?大家可以测试(再添加一个无参构造函数),测试报错 + +##### ⑥场景六:@Autowired注解和@Qualifier注解联合 + +添加dao层实现 + +```java +package com.lxcecho.dao.impl; + +import com.lxcecho.dao.UserDao; +import org.springframework.stereotype.Repository; + +@Repository +public class UserDaoRedisImpl implements UserDao { + + @Override + public void print() { + System.out.println("Redis Dao层执行结束"); + } +} +``` + +测试:测试异常 + +错误信息中说:不能装配,UserDao这个Bean的数量等于2 + +怎么解决这个问题呢?**当然要byName,根据名称进行装配了。** + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + @Autowired + @Qualifier("userDaoImpl") // 指定bean的名字 + private UserDao userDao; + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +**总结** + +- @Autowired注解可以出现在:属性上、构造方法上、构造方法的参数上、setter方法上。 +- 当带参数的构造方法只有一个,@Autowired注解可以省略。() +- @Autowired注解默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。 + + + +#### 3.3.5、实验二:@Resource注入 + +@Resource注解也可以完成属性注入。那它和@Autowired注解有什么区别? + +- @Resource注解是JDK扩展包中的,也就是说属于JDK的一部分。所以该注解是标准注解,更加具有通用性。(JSR-250标准中制定的注解类型。JSR是Java规范提案。) +- @Autowired注解是Spring框架自己的。 +- **@Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。** +- **@Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。** +- @Resource注解用在属性上、setter方法上。 +- @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。 + +@Resource注解属于JDK扩展包,所以不在JDK当中,需要额外引入以下依赖:【**如果是JDK8的话不需要额外引入依赖。高于JDK11或低于JDK8需要引入以下依赖。**】 + +```xml + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + +``` + +源码: + +```java +package jakarta.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Repeatable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Repeatable(Resources.class) +public @interface Resource { + String name() default ""; + + String lookup() default ""; + + Class type() default Object.class; + + Resource.AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER; + + boolean shareable() default true; + + String mappedName() default ""; + + String description() default ""; + + public static enum AuthenticationType { + CONTAINER, + APPLICATION; + + private AuthenticationType() { + } + } +} +``` + +##### ①场景一:根据name注入 + +修改UserDaoImpl类 + +```java +package com.lxcecho.dao.impl; + +import com.lxcecho.dao.UserDao; +import org.springframework.stereotype.Repository; + +@Repository("myUserDao") +public class UserDaoImpl implements UserDao { + + @Override + public void print() { + System.out.println("Dao层执行结束"); + } +} +``` + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + @Resource(name = "myUserDao") + private UserDao myUserDao; + + @Override + public void out() { + myUserDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +测试通过 + +##### ②场景二:name未知注入 + +修改UserDaoImpl类 + +```java +package com.lxcecho.dao.impl; + +import com.lxcecho.dao.UserDao; +import org.springframework.stereotype.Repository; + +@Repository("myUserDao") +public class UserDaoImpl implements UserDao { + + @Override + public void print() { + System.out.println("Dao层执行结束"); + } +} +``` + +修改UserServiceImpl类 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + @Resource + private UserDao myUserDao; + + @Override + public void out() { + myUserDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +测试通过 + +当@Resource注解使用时没有指定name的时候,还是根据name进行查找,这个name是属性名。 + +##### ③场景三 其他情况 + +修改UserServiceImpl类,userDao1属性名不存在 + +```java +package com.lxcecho.service.impl; + +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.stereotype.Service; + +@Service +public class UserServiceImpl implements UserService { + + @Resource + private UserDao userDao1; + + @Override + public void out() { + userDao1.print(); + System.out.println("Service层执行结束"); + } +} +``` + +测试异常 + +根据异常信息得知:显然当通过name找不到的时候,自然会启动byType进行注入,以上的错误是因为UserDao接口下有两个实现类导致的。所以根据类型注入就会报错。 + +@Resource的set注入可以自行测试 + +**总结:** + +@Resource注解:默认byName注入,没有指定name时把属性名当做name,根据name找不到时,才会byType注入。byType注入时,某种类型的Bean只能有一个 + + + +#### 3.3.6、Spring全注解开发 + +全注解开发就是不再使用spring配置文件了,写一个配置类来代替配置文件。 + +```java +package com.lxcecho.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +//@ComponentScan({"com.lxcecho.controller", "com.lxcecho.service","com.lxcecho.dao"}) +@ComponentScan("com.lxcecho") +public class Spring6Config { +} +``` + +测试类 + +```java +@Test +public void testAllAnnotation(){ + ApplicationContext context = new AnnotationConfigApplicationContext(Spring6Config.class); + UserController userController = context.getBean("userController", UserController.class); + userController.out(); + logger.info("执行成功"); +} +``` + + + +## 4、原理-手写IoC + +我们都知道,Spring框架的IOC是基于Java反射机制实现的,下面我们先回顾一下java反射。 + +### 4.1、回顾Java反射 + +`Java`反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为`Java`语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。 + +要想解剖一个类,必须先要**获取到该类的Class对象**。而剖析一个类或用反射解决具体的问题就是使用相关API**(1)java.lang.Class(2)java.lang.reflect**,所以,**Class对象是反射的根源**。 + +**自定义类** + +```java +package com.lxcecho.reflect; + +public class Car { + + //属性 + private String name; + private int age; + private String color; + + //无参数构造 + public Car() { + } + + //有参数构造 + public Car(String name, int age, String color) { + this.name = name; + this.age = age; + this.color = color; + } + + //普通方法 + private void run() { + System.out.println("私有方法-run....."); + } + + //get和set方法 + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + public String getColor() { + return color; + } + public void setColor(String color) { + this.color = color; + } + + @Override + public String toString() { + return "Car{" + + "name='" + name + '\'' + + ", age=" + age + + ", color='" + color + '\'' + + '}'; + } +} +``` + +**编写测试类** + +```java +package com.lxcecho.reflect; + +import org.junit.jupiter.api.Test; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +public class TestCar { + + //1、获取Class对象多种方式 + @Test + public void test01() throws Exception { + //1 类名.class + Class clazz1 = Car.class; + + //2 对象.getClass() + Class clazz2 = new Car().getClass(); + + //3 Class.forName("全路径") + Class clazz3 = Class.forName("com.lxcecho.reflect.Car"); + + //实例化 + Car car = (Car)clazz3.getConstructor().newInstance(); + System.out.println(car); + } + + //2、获取构造方法 + @Test + public void test02() throws Exception { + Class clazz = Car.class; + //获取所有构造 + // getConstructors()获取所有public的构造方法 +// Constructor[] constructors = clazz.getConstructors(); + // getDeclaredConstructors()获取所有的构造方法public private + Constructor[] constructors = clazz.getDeclaredConstructors(); + for (Constructor c:constructors) { + System.out.println("方法名称:"+c.getName()+" 参数个数:"+c.getParameterCount()); + } + + //指定有参数构造创建对象 + //1 构造public +// Constructor c1 = clazz.getConstructor(String.class, int.class, String.class); +// Car car1 = (Car)c1.newInstance("夏利", 10, "红色"); +// System.out.println(car1); + + //2 构造private + Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class); + c2.setAccessible(true); + Car car2 = (Car)c2.newInstance("捷达", 15, "白色"); + System.out.println(car2); + } + + //3、获取属性 + @Test + public void test03() throws Exception { + Class clazz = Car.class; + Car car = (Car)clazz.getDeclaredConstructor().newInstance(); + //获取所有public属性 + //Field[] fields = clazz.getFields(); + //获取所有属性(包含私有属性) + Field[] fields = clazz.getDeclaredFields(); + for (Field field:fields) { + if(field.getName().equals("name")) { + //设置允许访问 + field.setAccessible(true); + field.set(car,"五菱宏光"); + System.out.println(car); + } + System.out.println(field.getName()); + } + } + + //4、获取方法 + @Test + public void test04() throws Exception { + Car car = new Car("奔驰",10,"黑色"); + Class clazz = car.getClass(); + //1 public方法 + Method[] methods = clazz.getMethods(); + for (Method m1:methods) { + //System.out.println(m1.getName()); + //执行方法 toString + if(m1.getName().equals("toString")) { + String invoke = (String)m1.invoke(car); + //System.out.println("toString执行了:"+invoke); + } + } + + //2 private方法 + Method[] methodsAll = clazz.getDeclaredMethods(); + for (Method m:methodsAll) { + //执行方法 run + if(m.getName().equals("run")) { + m.setAccessible(true); + m.invoke(car); + } + } + } +} +``` + + + +### 4.2、实现Spring的IoC + +我们知道,IoC(控制反转)和DI(依赖注入)是Spring里面核心的东西,那么,我们如何自己手写出这样的代码呢?下面我们就一步一步写出Spring框架最核心的部分。 + +**①搭建子模块** + +搭建模块:lxcecho-spring,搭建方式如其他spring子模块 + +**②准备测试需要的bean** + +添加依赖 + +```xml + + + + org.junit.jupiter + junit-jupiter-api + 5.3.1 + + +``` + +创建UserDao接口 + +```java +package com.lxcecho.test.dao; + +public interface UserDao { + + public void print(); +} +``` + +创建UserDaoImpl实现 + +```java +package com.lxcecho.test.dao.impl; + +import com.lxcecho.spring.dao.UserDao; + +public class UserDaoImpl implements UserDao { + + @Override + public void print() { + System.out.println("Dao层执行结束"); + } +} + +``` + +创建UserService接口 + +```java +package com.lxcecho.test.service; + +public interface UserService { + + public void out(); +} +``` + +创建UserServiceImpl实现类 + +```java +package com.lxcecho.spring.test.service.impl; + +import com.lxcecho.spring.core.annotation.Bean; +import com.lxcecho.spring.service.UserService; + +@Bean +public class UserServiceImpl implements UserService { + +// private UserDao userDao; + + @Override + public void out() { + //userDao.print(); + System.out.println("Service层执行结束"); + } +} + +``` + +**③定义注解** + +我们通过注解的形式加载bean与实现依赖注入 + +bean注解 + +```java +package com.lxcecho.spring.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Bean { +} +``` + +依赖注入注解 + +```java +package com.lxcecho.spring.core.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Di { +} +``` + +说明:上面两个注解可以随意取名 + +**④定义bean容器接口** + +```java +package com.lxcecho.spring.core; + +public interface ApplicationContext { + + Object getBean(Class clazz); +} +``` + +**⑤编写注解bean容器接口实现** + +AnnotationApplicationContext基于注解扫描bean + +```java +package com.lxcecho.spring.core; + +import java.util.HashMap; + +public class AnnotationApplicationContext implements ApplicationContext { + + //存储bean的容器 + private HashMap beanFactory = new HashMap<>(); + + @Override + public Object getBean(Class clazz) { + return beanFactory.get(clazz); + } + + /** + * 根据包扫描加载bean + * @param basePackage + */ + public AnnotationApplicationContext(String basePackage) { + + } +} +``` + +**⑥编写扫描bean逻辑** + +我们通过构造方法传入包的base路径,扫描被@Bean注解的java对象,完整代码如下: + +```java +package com.lxcecho.spring.core; + +import com.lxcecho.spring.core.annotation.Bean; + +import java.io.File; +import java.util.HashMap; + +public class AnnotationApplicationContext implements ApplicationContext { + + //存储bean的容器 + private HashMap beanFactory = new HashMap<>(); + private static String rootPath; + + @Override + public Object getBean(Class clazz) { + return beanFactory.get(clazz); + } + + /** + * 根据包扫描加载bean + * @param basePackage + */ + public AnnotationApplicationContext(String basePackage) { + try { + String packageDirName = basePackage.replaceAll("\\.", "\\\\"); + Enumeration dirs =Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String filePath = URLDecoder.decode(url.getFile(),"utf-8"); + rootPath = filePath.substring(0, filePath.length()-packageDirName.length()); + loadBean(new File(filePath)); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void loadBean(File fileParent) { + if (fileParent.isDirectory()) { + File[] childrenFiles = fileParent.listFiles(); + if(childrenFiles == null || childrenFiles.length == 0){ + return; + } + for (File child : childrenFiles) { + if (child.isDirectory()) { + //如果是个文件夹就继续调用该方法,使用了递归 + loadBean(child); + } else { + //通过文件路径转变成全类名,第一步把绝对路径部分去掉 + String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1); + //选中class文件 + if (pathWithClass.contains(".class")) { + // com.xinzhi.dao.UserDao + //去掉.class后缀,并且把 \ 替换成 . + String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", ""); + try { + Class aClass = Class.forName(fullName); + //把非接口的类实例化放在map中 + if(!aClass.isInterface()){ + Bean annotation = aClass.getAnnotation(Bean.class); + if(annotation != null){ + Object instance = aClass.newInstance(); + //判断一下有没有接口 + if(aClass.getInterfaces().length > 0) { + //如果有接口把接口的class当成key,实例对象当成value + System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName()); + beanFactory.put(aClass.getInterfaces()[0], instance); + }else{ + //如果有接口把自己的class当成key,实例对象当成value + System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName()); + beanFactory.put(aClass, instance); + } + } + } + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + e.printStackTrace(); + } + } + } + } + } + } + +} +``` + +**⑦java类标识Bean注解** + +```java +@Bean +public class UserServiceImpl implements UserService +``` + +```java +@Bean +public class UserDaoImpl implements UserDao +``` + +**⑧测试Bean加载** + +```java +package com.lxcecho.spring; + +import com.lxcecho.spring.core.AnnotationApplicationContext; +import com.lxcecho.spring.core.ApplicationContext; +import com.lxcecho.spring.test.service.UserService; +import org.junit.jupiter.api.Test; + +public class SpringIocTest { + + @Test + public void testIoc() { + ApplicationContext applicationContext = new AnnotationApplicationContext("com.lxcecho.spring.test"); + UserService userService = (UserService)applicationContext.getBean(UserService.class); + userService.out(); + System.out.println("run success"); + } +} +``` + +控制台打印测试 + +**⑨依赖注入** + +只要userDao.print();调用成功,说明就注入成功 + +```java +package com.lxcecho.spring.test.service.impl; + +import com.lxcecho.spring.core.annotation.Bean; +import com.lxcecho.spring.core.annotation.Di; +import com.lxcecho.spring.dao.UserDao; +import com.lxcecho.spring.service.UserService; + +@Bean +public class UserServiceImpl implements UserService { + + @Di + private UserDao userDao; + + @Override + public void out() { + userDao.print(); + System.out.println("Service层执行结束"); + } +} +``` + +执行第八步:报错了,说明当前userDao是个空对象 + +**⑩依赖注入实现** + +```java +package com.lxcecho.spring.core; + +import com.lxcecho.spring.core.annotation.Bean; +import com.lxcecho.spring.core.annotation.Di; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class AnnotationApplicationContext implements ApplicationContext { + + //存储bean的容器 + private HashMap beanFactory = new HashMap<>(); + private static String rootPath; + + @Override + public Object getBean(Class clazz) { + return beanFactory.get(clazz); + } + + /** + * 根据包扫描加载bean + * @param basePackage + */ + public AnnotationApplicationContext(String basePackage) { + try { + String packageDirName = basePackage.replaceAll("\\.", "\\\\"); + Enumeration dirs =Thread.currentThread().getContextClassLoader().getResources(packageDirName); + while (dirs.hasMoreElements()) { + URL url = dirs.nextElement(); + String filePath = URLDecoder.decode(url.getFile(),"utf-8"); + rootPath = filePath.substring(0, filePath.length()-packageDirName.length()); + loadBean(new File(filePath)); + } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + //依赖注入 + loadDi(); + } + + private void loadBean(File fileParent) { + if (fileParent.isDirectory()) { + File[] childrenFiles = fileParent.listFiles(); + if(childrenFiles == null || childrenFiles.length == 0){ + return; + } + for (File child : childrenFiles) { + if (child.isDirectory()) { + //如果是个文件夹就继续调用该方法,使用了递归 + loadBean(child); + } else { + //通过文件路径转变成全类名,第一步把绝对路径部分去掉 + String pathWithClass = child.getAbsolutePath().substring(rootPath.length() - 1); + //选中class文件 + if (pathWithClass.contains(".class")) { + // com.xinzhi.dao.UserDao + //去掉.class后缀,并且把 \ 替换成 . + String fullName = pathWithClass.replaceAll("\\\\", ".").replace(".class", ""); + try { + Class aClass = Class.forName(fullName); + //把非接口的类实例化放在map中 + if(!aClass.isInterface()){ + Bean annotation = aClass.getAnnotation(Bean.class); + if(annotation != null){ + Object instance = aClass.newInstance(); + //判断一下有没有接口 + if(aClass.getInterfaces().length > 0) { + //如果有接口把接口的class当成key,实例对象当成value + System.out.println("正在加载【"+ aClass.getInterfaces()[0] +"】,实例对象是:" + instance.getClass().getName()); + beanFactory.put(aClass.getInterfaces()[0], instance); + }else{ + //如果有接口把自己的class当成key,实例对象当成value + System.out.println("正在加载【"+ aClass.getName() +"】,实例对象是:" + instance.getClass().getName()); + beanFactory.put(aClass, instance); + } + } + } + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + e.printStackTrace(); + } + } + } + } + } + } + + private void loadDi() { + for(Map.Entry entry : beanFactory.entrySet()){ + //就是咱们放在容器的对象 + Object obj = entry.getValue(); + Class aClass = obj.getClass(); + Field[] declaredFields = aClass.getDeclaredFields(); + for (Field field : declaredFields){ + Di annotation = field.getAnnotation(Di.class); + if( annotation != null ){ + field.setAccessible(true); + try { + System.out.println("正在给【"+obj.getClass().getName()+"】属性【" + field.getName() + "】注入值【"+ beanFactory.get(field.getType()).getClass().getName() +"】"); + field.set(obj,beanFactory.get(field.getType())); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + } + } + +} +``` + +执行第八步:执行成功,依赖注入成功 + + + +## 5、面向切面:AOP + +### 5.1、场景模拟 + +**搭建子模块:spring6-aop** + +#### 5.1.1、声明接口 + +声明计算器接口Calculator,包含加减乘除的抽象方法 + +```java +public interface Calculator { + + int add(int i, int j); + + int sub(int i, int j); + + int mul(int i, int j); + + int div(int i, int j); + +} +``` + +#### 5.1.2、创建实现类 + +![images](images/spring6/img014.png) + +```java +public class CalculatorImpl implements Calculator { + + @Override + public int add(int i, int j) { + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int sub(int i, int j) { + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int div(int i, int j) { + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + return result; + } +} +``` + +#### 5.1.3、创建带日志功能的实现类 + +![images](images/spring6/img015.png) + +```java +public class CalculatorLogImpl implements Calculator { + + @Override + public int add(int i, int j) { + + System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] add 方法结束了,结果是:" + result); + + return result; + } + + @Override + public int sub(int i, int j) { + + System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j); + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] sub 方法结束了,结果是:" + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j); + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] mul 方法结束了,结果是:" + result); + + return result; + } + + @Override + public int div(int i, int j) { + + System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j); + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] div 方法结束了,结果是:" + result); + + return result; + } +} +``` + +#### 5.1.4、提出问题 + +**①现有代码缺陷** + +针对带日志功能的实现类,我们发现有如下缺陷: + +- 对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力 +- 附加功能分散在各个业务功能方法中,不利于统一维护 + +**②解决思路** + +解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。 + +**③困难** + +解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。 + + + +### 5.2、代理模式 + +#### 5.2.1、概念 + +**①介绍** + +二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类**间接**调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——**解耦**。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。 + +![images](images/spring6/img016.png) + +使用代理后: + +![images](images/spring6/img017.png) + +**②生活中的代理** + +- 广告商找大明星拍广告需要经过经纪人 +- 合作伙伴找大老板谈合作要约见面时间需要经过秘书 +- 房产中介是买卖双方的代理 + +**③相关术语** + +- 代理:将非核心逻辑剥离出来以后,封装这些非核心逻辑的类、对象、方法。 +- 目标:被代理“套用”了非核心逻辑代码的类、对象、方法。 + + + +#### 5.2.2、静态代理 + +创建静态代理类: + +```java +public class CalculatorStaticProxy implements Calculator { + + // 将被代理的目标对象声明为成员变量 + private Calculator target; + + public CalculatorStaticProxy(Calculator target) { + this.target = target; + } + + @Override + public int add(int i, int j) { + + // 附加功能由代理类中的代理方法来实现 + System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); + + // 通过目标对象来实现核心业务逻辑 + int addResult = target.add(i, j); + + System.out.println("[日志] add 方法结束了,结果是:" + addResult); + + return addResult; + } +} +``` + +> 静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。 +> +> 提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。 + + + +#### 5.2.3、动态代理 + +![images](images/spring6/img018.png) + +生产代理对象的工厂类: + +```java +public class ProxyFactory { + + private Object target; + + public ProxyFactory(Object target) { + this.target = target; + } + + public Object getProxy(){ + + /** + * newProxyInstance():创建一个代理实例 + * 其中有三个参数: + * 1、classLoader:加载动态生成的代理类的类加载器 + * 2、interfaces:目标对象实现的所有接口的class对象所组成的数组 + * 3、invocationHandler:设置代理对象实现目标对象方法的过程,即代理类中如何重写接口中的抽象方法 + */ + ClassLoader classLoader = target.getClass().getClassLoader(); + Class[] interfaces = target.getClass().getInterfaces(); + InvocationHandler invocationHandler = new InvocationHandler() { + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + /** + * proxy:代理对象 + * method:代理对象需要实现的方法,即其中需要重写的方法 + * args:method所对应方法的参数 + */ + Object result = null; + try { + System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args)); + result = method.invoke(target, args); + System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("[动态代理][日志] "+method.getName()+",异常:"+e.getMessage()); + } finally { + System.out.println("[动态代理][日志] "+method.getName()+",方法执行完毕"); + } + return result; + } + }; + + return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler); + } +} +``` + + + +#### 5.2.4、测试 + +```java +@Test +public void testDynamicProxy(){ + ProxyFactory factory = new ProxyFactory(new CalculatorLogImpl()); + Calculator proxy = (Calculator) factory.getProxy(); + proxy.div(1,0); + //proxy.div(1,1); +} +``` + + + +### 5.3、AOP概念及相关术语 + +#### 5.3.1、概述 + +AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 + +#### 5.3.2、相关术语 + +##### ①横切关注点 + +分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。 + +从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。 + +这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。 + +![images](images/spring6/img019.png) + +##### ②通知(增强) + +**增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等。** + +每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。 + +- 前置通知:在被代理的目标方法**前**执行 +- 返回通知:在被代理的目标方法**成功结束**后执行(**寿终正寝**) +- 异常通知:在被代理的目标方法**异常结束**后执行(**死于非命**) +- 后置通知:在被代理的目标方法**最终结束**后执行(**盖棺定论**) +- 环绕通知:使用try...catch...finally结构围绕**整个**被代理的目标方法,包括上面四种通知对应的所有位置 + +![images](images/spring6/img020.png) + +##### ③切面 + +封装通知方法的类。 + +![images](images/spring6/img021.png) + +##### ④目标 + +被代理的目标对象。 + +##### ⑤代理 + +向目标对象应用通知之后创建的代理对象。 + +##### ⑥连接点 + +这也是一个纯逻辑概念,不是语法定义的。 + +把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。**通俗说,就是spring允许你使用通知的地方** + +![images](images/spring6/img022.png) + +##### ⑦切入点 + +定位连接点的方式。 + +每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。 + +如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。 + +**Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法** + +切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。 + +#### 5.3.3、作用 + +- 简化代码:把方法中固定位置的重复的代码**抽取**出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。 + +- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被**套用**了切面逻辑的方法就被切面给增强了。 + + + +### 5.4、基于注解的AOP + +#### 5.4.1、技术说明 + +![images](images/spring6/img023.png) + + + +![image-20221216132844066](images\spring6\image-20221216132844066.png) + +- 动态代理分为JDK动态代理和cglib动态代理 +- 当目标类有接口的情况使用JDK动态代理和cglib动态代理,没有接口时只能使用cglib动态代理 +- JDK动态代理动态生成的代理类会在com.sun.proxy包下,类名为$proxy1,和目标类实现相同的接口 +- cglib动态代理动态生成的代理类会和目标在在相同的包下,会继承目标类 +- 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求**代理对象和目标对象实现同样的接口**(兄弟两个拜把子模式)。 +- cglib:通过**继承被代理的目标类**(认干爹模式)实现代理,所以不需要目标类实现接口。 +- AspectJ:是AOP思想的一种实现。本质上是静态代理,**将代理逻辑“织入”被代理的目标类编译得到的字节码文件**,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。 + +#### 5.4.2、准备工作 + +**①添加依赖** + +在IOC所需依赖基础上再加入下面依赖即可: + +```xml + + + + + org.springframework + spring-context + 6.0.2 + + + + + org.springframework + spring-aop + 6.0.2 + + + + org.springframework + spring-aspects + 6.0.2 + + + + + org.junit.jupiter + junit-jupiter-api + 5.3.1 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.19.0 + + +``` + +**②准备被代理的目标资源** + +接口: + +```java +public interface Calculator { + + int add(int i, int j); + + int sub(int i, int j); + + int mul(int i, int j); + + int div(int i, int j); + +} +``` + +实现类: + +```java +@Component +public class CalculatorImpl implements Calculator { + + @Override + public int add(int i, int j) { + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int sub(int i, int j) { + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int div(int i, int j) { + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + return result; + } +} +``` + + + +#### 5.4.3、创建切面类并配置 + +```java +// @Aspect表示这个类是一个切面类 +@Aspect +// @Component注解保证这个切面类能够放入IOC容器 +@Component +public class LogAspect { + + @Before("execution(public int com.lxcecho.aop.annotation.CalculatorImpl.*(..))") + public void beforeMethod(JoinPoint joinPoint){ + String methodName = joinPoint.getSignature().getName(); + String args = Arrays.toString(joinPoint.getArgs()); + System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); + } + + @After("execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))") + public void afterMethod(JoinPoint joinPoint){ + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->后置通知,方法名:"+methodName); + } + + @AfterReturning(value = "execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))", returning = "result") + public void afterReturningMethod(JoinPoint joinPoint, Object result){ + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result); + } + + @AfterThrowing(value = "execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))", throwing = "ex") + public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){ + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex); + } + + @Around("execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))") + public Object aroundMethod(ProceedingJoinPoint joinPoint){ + String methodName = joinPoint.getSignature().getName(); + String args = Arrays.toString(joinPoint.getArgs()); + Object result = null; + try { + System.out.println("环绕通知-->目标对象方法执行之前"); + //目标对象(连接点)方法的执行 + result = joinPoint.proceed(); + System.out.println("环绕通知-->目标对象方法返回值之后"); + } catch (Throwable throwable) { + throwable.printStackTrace(); + System.out.println("环绕通知-->目标对象方法出现异常时"); + } finally { + System.out.println("环绕通知-->目标对象方法执行完毕"); + } + return result; + } + +} +``` + +在Spring的配置文件中配置: + +```xml + + + + + + + +``` + +执行测试: + +```java +public class CalculatorTest { + + private Logger logger = LoggerFactory.getLogger(CalculatorTest.class); + + @Test + public void testAdd(){ + ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml"); + Calculator calculator = ac.getBean( Calculator.class); + int add = calculator.add(1, 1); + logger.info("执行成功:"+add); + } + +} +``` + +执行结果: + +![image-20221102155523983](images/spring6/image-20221102155523983.png) + + + +#### 5.4.4、各种通知 + +- 前置通知:使用@Before注解标识,在被代理的目标方法**前**执行 +- 返回通知:使用@AfterReturning注解标识,在被代理的目标方法**成功结束**后执行(**寿终正寝**) +- 异常通知:使用@AfterThrowing注解标识,在被代理的目标方法**异常结束**后执行(**死于非命**) +- 后置通知:使用@After注解标识,在被代理的目标方法**最终结束**后执行(**盖棺定论**) +- 环绕通知:使用@Around注解标识,使用try...catch...finally结构围绕**整个**被代理的目标方法,包括上面四种通知对应的所有位置 + +> 各种通知的执行顺序: +> +> - Spring版本5.3.x以前: +> - 前置通知 +> - 目标操作 +> - 后置通知 +> - 返回通知或异常通知 +> - Spring版本5.3.x以后: +> - 前置通知 +> - 目标操作 +> - 返回通知或异常通知 +> - 后置通知 + + + +#### 5.4.5、切入点表达式语法 + +**①作用** + +![images](images/spring6/img024.png) + +**②语法细节** + +- 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限 +- 在包名的部分,一个“*”号只能代表包的层次结构中的一层,表示这一层是任意的。 + - 例如:*.Hello匹配com.Hello,不匹配com.lxcecho.Hello +- 在包名的部分,使用“*..”表示包名任意、包的层次深度任意 +- 在类名的部分,类名部分整体用*号代替,表示类名任意 +- 在类名的部分,可以使用*号代替类名的一部分 + - 例如:*Service匹配所有名称以Service结尾的类或接口 + +- 在方法名部分,可以使用*号表示方法名任意 +- 在方法名部分,可以使用*号代替方法名的一部分 + - 例如:*Operation匹配所有方法名以Operation结尾的方法 + +- 在方法参数列表部分,使用(..)表示参数列表任意 +- 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头 +- 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的 + - 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的 +- 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符 + - 例如:execution(public int *..*Service.*(.., int)) 正确 + 例如:execution(* int *..*Service.*(.., int)) 错误 + +![images](images/spring6/img025.png) + + + +#### 5.4.6、重用切入点表达式 + +**①声明** + +```java +@Pointcut("execution(* com.lxcecho.aop.annotation.*.*(..))") +public void pointCut(){} +``` + +**②在同一个切面中使用** + +```java +@Before("pointCut()") +public void beforeMethod(JoinPoint joinPoint){ + String methodName = joinPoint.getSignature().getName(); + String args = Arrays.toString(joinPoint.getArgs()); + System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); +} +``` + +**③在不同切面中使用** + +```java +@Before("com.lxcecho.aop.CommonPointCut.pointCut()") +public void beforeMethod(JoinPoint joinPoint){ + String methodName = joinPoint.getSignature().getName(); + String args = Arrays.toString(joinPoint.getArgs()); + System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); +} +``` + + + +#### 5.4.7、获取通知的相关信息 + +**①获取连接点信息** + +获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参 + +```java +@Before("execution(public int com.lxcecho.aop.annotation.CalculatorImpl.*(..))") +public void beforeMethod(JoinPoint joinPoint){ + //获取连接点的签名信息 + String methodName = joinPoint.getSignature().getName(); + //获取目标方法到的实参信息 + String args = Arrays.toString(joinPoint.getArgs()); + System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args); +} +``` + +**②获取目标方法的返回值** + +@AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值 + +```java +@AfterReturning(value = "execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))", returning = "result") +public void afterReturningMethod(JoinPoint joinPoint, Object result){ + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result); +} +``` + +**③获取目标方法的异常** + +@AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常 + +```java +@AfterThrowing(value = "execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))", throwing = "ex") +public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){ + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex); +} +``` + + + +#### 5.4.8、环绕通知 + +```java +@Around("execution(* com.lxcecho.aop.annotation.CalculatorImpl.*(..))") +public Object aroundMethod(ProceedingJoinPoint joinPoint){ + String methodName = joinPoint.getSignature().getName(); + String args = Arrays.toString(joinPoint.getArgs()); + Object result = null; + try { + System.out.println("环绕通知-->目标对象方法执行之前"); + //目标方法的执行,目标方法的返回值一定要返回给外界调用者 + result = joinPoint.proceed(); + System.out.println("环绕通知-->目标对象方法返回值之后"); + } catch (Throwable throwable) { + throwable.printStackTrace(); + System.out.println("环绕通知-->目标对象方法出现异常时"); + } finally { + System.out.println("环绕通知-->目标对象方法执行完毕"); + } + return result; +} +``` + + + +#### 5.4.9、切面的优先级 + +相同目标方法上同时存在多个切面时,切面的优先级控制切面的**内外嵌套**顺序。 + +- 优先级高的切面:外面 +- 优先级低的切面:里面 + +使用@Order注解可以控制切面的优先级: + +- @Order(较小的数):优先级高 +- @Order(较大的数):优先级低 + +![images](images/spring6/img026.png) + + + +### 5.5、基于XML的AOP + +#### 5.5.1、准备工作 + +参考基于注解的AOP环境 + +#### 5.5.2、实现 + +```xml + + + + + + + + + + + + + +``` + + + +## 6、单元测试:JUnit + +在之前的测试方法中,几乎都能看到以下的两行代码: + +```java +ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml"); +Xxxx xxx = context.getBean(Xxxx.class); +``` + +这两行代码的作用是创建Spring容器,最终获取到对象,但是每次测试都需要重复编写。针对上述问题,我们需要的是程序能自动帮我们创建容器。我们都知道JUnit无法知晓我们是否使用了 Spring 框架,更不用说帮我们创建 Spring 容器了。Spring提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件位置就可以了。这样一来,我们通过Spring整合JUnit可以使程序创建spring容器了 + +### 6.1、整合JUnit5 + +#### 6.1.1、搭建子模块 + +搭建spring-junit模块 + +#### 6.1.2、引入依赖 + +```xml + + + + + org.springframework + spring-context + 6.0.2 + + + + + org.springframework + spring-test + 6.0.2 + + + + + org.junit.jupiter + junit-jupiter-api + 5.9.0 + + + + + org.apache.logging.log4j + log4j-core + 2.19.0 + + + org.apache.logging.log4j + log4j-slf4j2-impl + 2.19.0 + + +``` + +#### 6.1.3、添加配置文件 + +beans.xml + +```xml + + + + +``` + +copy日志文件:log4j2.xml + +#### 6.1.4、添加java类 + +```java +package com.lxcecho.bean; + +import org.springframework.stereotype.Component; + +@Component +public class User { + + public User() { + System.out.println("run user"); + } +} +``` + +#### 6.1.5、测试 + +```java +import com.lxcecho.bean.User; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +//两种方式均可 +//方式一 +//@ExtendWith(SpringExtension.class) +//@ContextConfiguration("classpath:beans.xml") +//方式二 +@SpringJUnitConfig(locations = "classpath:beans.xml") +public class SpringJUnit5Test { + + @Autowired + private User user; + + @Test + public void testUser(){ + System.out.println(user); + } +} +``` + + + +### 6.2、整合JUnit4 + +JUnit4在公司也会经常用到,在此也学习一下 + +#### 6.2.1、添加依赖 + +```xml + + + junit + junit + 4.12 + +``` + +#### 6.2.2、测试 + +```java +import com.lxcecho.bean.User; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:beans.xml") +public class SpringJUnit4Test { + + @Autowired + private User user; + + @Test + public void testUser(){ + System.out.println(user); + } +} +``` + + + +## 7、事务 + +### 7.1、JdbcTemplate + +#### 7.1.1、简介 + +![image-20221217115515670](images\spring6\image-20221217115515670.png) + +Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作 + +#### 7.1.2、准备工作 + +**①搭建子模块** + +搭建子模块:spring-jdbc-tx + +**②加入依赖** + +```xml + + + + org.springframework + spring-jdbc + 6.0.2 + + + + mysql + mysql-connector-java + 8.0.30 + + + + com.alibaba + druid + 1.2.15 + + +``` + +**③创建jdbc.properties** + +```properties +jdbc.user=root +jdbc.password=root +jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false +jdbc.driver=com.mysql.cj.jdbc.Driver +``` + +**④配置Spring的配置文件** + +beans.xml + +```xml + + + + + + + + + + + + + + + + + + + + + +``` + +**⑤准备数据库与测试表** + +```java +CREATE DATABASE `spring`; + +use `spring`; + +CREATE TABLE `t_emp` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(20) DEFAULT NULL COMMENT '姓名', + `age` int(11) DEFAULT NULL COMMENT '年龄', + `sex` varchar(2) DEFAULT NULL COMMENT '性别', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + + + +#### 7.1.3、实现CURD + +##### ①装配 JdbcTemplate + +**创建测试类,整合JUnit,注入JdbcTemplate** + +```java +package com.lxcecho; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +@SpringJUnitConfig(locations = "classpath:beans.xml") +public class JDBCTemplateTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + +} +``` + +##### ②测试增删改功能 + +```java +@Test +//测试增删改功能 +public void testUpdate(){ + //添加功能 + String sql = "insert into t_emp values(null,?,?,?)"; + int result = jdbcTemplate.update(sql, "张三", 23, "男"); + + //修改功能 + //String sql = "update t_emp set name=? where id=?"; + //int result = jdbcTemplate.update(sql, "张三lxcecho", 1); + + //删除功能 + //String sql = "delete from t_emp where id=?"; + //int result = jdbcTemplate.update(sql, 1); +} +``` + +##### ③查询数据返回对象 + +```java +public class Emp { + + private Integer id; + private String name; + private Integer age; + private String sex; + + //生成get和set方法 + //...... + + @Override + public String toString() { + return "Emp{" + + "id=" + id + + ", name='" + name + '\'' + + ", age=" + age + + ", sex='" + sex + '\'' + + '}'; + } +} +``` + +```java +//查询:返回对象 +@Test +public void testSelectObject() { + //写法一 +// String sql = "select * from t_emp where id=?"; +// Emp empResult = jdbcTemplate.queryForObject(sql, +// (rs, rowNum) -> { +// Emp emp = new Emp(); +// emp.setId(rs.getInt("id")); +// emp.setName(rs.getString("name")); +// emp.setAge(rs.getInt("age")); +// emp.setSex(rs.getString("sex")); +// return emp; +// }, 1); +// System.out.println(empResult); + + //写法二 + String sql = "select * from t_emp where id=?"; + Emp emp = jdbcTemplate.queryForObject(sql, + new BeanPropertyRowMapper<>(Emp.class),1); + System.out.println(emp); +} +``` + +##### ④查询数据返回list集合 + +```java +@Test +//查询多条数据为一个list集合 +public void testSelectList(){ + String sql = "select * from t_emp"; + List list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class)); + System.out.println(list); +} +``` + +##### ⑤查询返回单个的值 + +```java +@Test +//查询单行单列的值 +public void selectCount(){ + String sql = "select count(id) from t_emp"; + Integer count = jdbcTemplate.queryForObject(sql, Integer.class); + System.out.println(count); +} +``` + + + +### 7.2、声明式事务概念 + +#### 7.2.1、事务基本概念 + +##### ①什么是事务 + +数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。 + +##### ②事务的特性 + +**A:原子性(Atomicity)** + +一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 + +**C:一致性(Consistency)** + +事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。 + +如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。 + +如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。 + +**I:隔离性(Isolation)** + +指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。 + +**D:持久性(Durability)** + +指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。 + + + +#### 7.2.2、编程式事务 + +事务功能的相关操作全部通过自己编写代码来实现: + +```java +Connection conn = ...; + +try { + + // 开启事务:关闭事务的自动提交 + conn.setAutoCommit(false); + + // 核心操作 + + // 提交事务 + conn.commit(); + +}catch(Exception e){ + + // 回滚事务 + conn.rollBack(); + +}finally{ + + // 释放数据库连接 + conn.close(); + +} +``` + +编程式的实现方式存在缺陷: + +- 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。 +- 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。 + +#### 7.2.3、声明式事务 + +既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。 + +封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。 + +- 好处1:提高开发效率 +- 好处2:消除了冗余的代码 +- 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化 + +所以,我们可以总结下面两个概念: + +- **编程式**:**自己写代码**实现功能 +- **声明式**:通过**配置**让**框架**实现功能 + + + +### 7.3、基于注解的声明式事务 + +#### 7.3.1、准备工作 + +**①添加配置** + +在beans.xml添加配置 + +```xml + + +``` + +**②创建表** + +```sql +CREATE TABLE `t_book` ( + `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称', + `price` int(11) DEFAULT NULL COMMENT '价格', + `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)', + PRIMARY KEY (`book_id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; +insert into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100); +CREATE TABLE `t_user` ( + `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `username` varchar(20) DEFAULT NULL COMMENT '用户名', + `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)', + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; +insert into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50); +``` + +**③创建组件** + +创建BookController: + +```java +package com.lxcecho.controller; + +@Controller +public class BookController { + + @Autowired + private BookService bookService; + + public void buyBook(Integer bookId, Integer userId){ + bookService.buyBook(bookId, userId); + } +} +``` + +创建接口BookService: + +```java +package com.lxcecho.service; +public interface BookService { + void buyBook(Integer bookId, Integer userId); +} +``` + +创建实现类BookServiceImpl: + +```java +package com.lxcecho.service.impl; +@Service +public class BookServiceImpl implements BookService { + + @Autowired + private BookDao bookDao; + + @Override + public void buyBook(Integer bookId, Integer userId) { + //查询图书的价格 + Integer price = bookDao.getPriceByBookId(bookId); + //更新图书的库存 + bookDao.updateStock(bookId); + //更新用户的余额 + bookDao.updateBalance(userId, price); + } +} +``` + +创建接口BookDao: + +```java +package com.lxcecho.dao; +public interface BookDao { + Integer getPriceByBookId(Integer bookId); + + void updateStock(Integer bookId); + + void updateBalance(Integer userId, Integer price); +} +``` + +创建实现类BookDaoImpl: + +```java +package com.lxcecho.dao.impl; +@Repository +public class BookDaoImpl implements BookDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Override + public Integer getPriceByBookId(Integer bookId) { + String sql = "select price from t_book where book_id = ?"; + return jdbcTemplate.queryForObject(sql, Integer.class, bookId); + } + + @Override + public void updateStock(Integer bookId) { + String sql = "update t_book set stock = stock - 1 where book_id = ?"; + jdbcTemplate.update(sql, bookId); + } + + @Override + public void updateBalance(Integer userId, Integer price) { + String sql = "update t_user set balance = balance - ? where user_id = ?"; + jdbcTemplate.update(sql, price, userId); + } +} +``` + + + +#### 7.3.2、测试无事务情况 + +**①创建测试类** + +```java +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +@SpringJUnitConfig(locations = "classpath:beans.xml") +public class TxByAnnotationTest { + + @Autowired + private BookController bookController; + + @Test + public void testBuyBook(){ + bookController.buyBook(1, 1); + } + +} +``` + +**②模拟场景** + +用户购买图书,先查询图书的价格,再更新图书的库存和用户的余额 + +假设用户id为1的用户,购买id为1的图书 + +用户余额为50,而图书价格为80 + +购买图书之后,用户的余额为-30,数据库中余额字段设置了无符号,因此无法将-30插入到余额字段 + +此时执行sql语句会抛出SQLException + +**③观察结果** + +因为没有添加事务,图书的库存更新了,但是用户的余额没有更新 + +显然这样的结果是错误的,购买图书是一个完整的功能,更新库存和更新余额要么都成功要么都失败 + + + +#### 7.3.3、加入事务 + +##### ①添加事务配置 + +在spring配置文件中引入tx命名空间 + +```xml + + +``` + +在Spring的配置文件中添加配置: + +```xml + + + + + + + +``` + +##### ②添加事务注解 + +因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理 + +**在BookServiceImpl的buybook()添加注解@Transactional** + +##### ③观察结果 + +由于使用了Spring的声明式事务,更新库存和更新余额都没有执行 + + + +#### 7.3.4、@Transactional注解标识的位置 + +@Transactional标识在方法上,则只会影响该方法 + +@Transactional标识的类上,则会影响类中所有的方法 + + + +#### 7.3.5、事务属性:只读 + +**①介绍** + +对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。 + +**②使用方式** + +```java +@Transactional(readOnly = true) +public void buyBook(Integer bookId, Integer userId) { + //查询图书的价格 + Integer price = bookDao.getPriceByBookId(bookId); + //更新图书的库存 + bookDao.updateStock(bookId); + //更新用户的余额 + bookDao.updateBalance(userId, price); + //System.out.println(1/0); +} +``` + +**③注意** + +对增删改操作设置只读会抛出下面异常: + +Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed + + + +#### 7.3.6、事务属性:超时 + +**①介绍** + +事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。 + +概括来说就是一句话:超时回滚,释放资源。 + +**②使用方式** + +```java +//超时时间单位秒 +@Transactional(timeout = 3) +public void buyBook(Integer bookId, Integer userId) { + try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + e.printStackTrace(); + } + //查询图书的价格 + Integer price = bookDao.getPriceByBookId(bookId); + //更新图书的库存 + bookDao.updateStock(bookId); + //更新用户的余额 + bookDao.updateBalance(userId, price); + //System.out.println(1/0); +} +``` + +**③观察结果** + +执行过程中抛出异常: + +org.springframework.transaction.**TransactionTimedOutException**: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022 + + + +#### 7.3.7、事务属性:回滚策略 + +**①介绍** + +声明式事务默认只针对运行时异常回滚,编译时异常不回滚。 + +可以通过@Transactional中相关属性设置回滚策略 + +- rollbackFor属性:需要设置一个Class类型的对象 +- rollbackForClassName属性:需要设置一个字符串类型的全类名 + +- noRollbackFor属性:需要设置一个Class类型的对象 +- rollbackFor属性:需要设置一个字符串类型的全类名 + +**②使用方式** + +```java +@Transactional(noRollbackFor = ArithmeticException.class) +//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException") +public void buyBook(Integer bookId, Integer userId) { + //查询图书的价格 + Integer price = bookDao.getPriceByBookId(bookId); + //更新图书的库存 + bookDao.updateStock(bookId); + //更新用户的余额 + bookDao.updateBalance(userId, price); + System.out.println(1/0); +} +``` + +**③观察结果** + +虽然购买图书功能中出现了数学运算异常(ArithmeticException),但是我们设置的回滚策略是,当出现ArithmeticException不发生回滚,因此购买图书的操作正常执行 + + + +#### 7.3.8、事务属性:隔离级别 + +**①介绍** + +数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。 + +隔离级别一共有四种: + +- 读未提交:READ UNCOMMITTED + + 允许Transaction01读取Transaction02未提交的修改。 + +- 读已提交:READ COMMITTED、 + + 要求Transaction01只能读取Transaction02已提交的修改。 + +- 可重复读:REPEATABLE READ + + 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。 + +- 串行化:SERIALIZABLE + + 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。 + +各个隔离级别解决并发问题的能力见下表: + +| 隔离级别 | 脏读 | 不可重复读 | 幻读 | +| ---------------- | ---- | ---------- | ---- | +| READ UNCOMMITTED | 有 | 有 | 有 | +| READ COMMITTED | 无 | 有 | 有 | +| REPEATABLE READ | 无 | 无 | 有 | +| SERIALIZABLE | 无 | 无 | 无 | + +各种数据库产品对事务隔离级别的支持程度: + +| 隔离级别 | Oracle | MySQL | +| ---------------- | ------- | ------- | +| READ UNCOMMITTED | × | √ | +| READ COMMITTED | √(默认) | √ | +| REPEATABLE READ | × | √(默认) | +| SERIALIZABLE | √ | √ | + +**②使用方式** + +```java +@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别 +@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交 +@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交 +@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读 +@Transactional(isolation = Isolation.SERIALIZABLE)//串行化 +``` + + + +#### 7.3.9、事务属性:传播行为 + +**①介绍** + +什么是事务的传播行为? + +在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。 + +一共有七种传播行为: + +- REQUIRED:支持当前事务,如果不存在就新建一个(默认)**【没有就新建,有就加入】** +- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】** +- MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】** +- REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】** +- NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】** +- NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】** +- NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。**【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】** + +**②测试** + +创建接口CheckoutService: + +```java +package com.lxcecho.service; + +public interface CheckoutService { + void checkout(Integer[] bookIds, Integer userId); +} +``` + +创建实现类CheckoutServiceImpl: + +```java +package com.lxcecho.service.impl; + +@Service +public class CheckoutServiceImpl implements CheckoutService { + + @Autowired + private BookService bookService; + + @Override + @Transactional + //一次购买多本图书 + public void checkout(Integer[] bookIds, Integer userId) { + for (Integer bookId : bookIds) { + bookService.buyBook(bookId, userId); + } + } +} +``` + +在BookController中添加方法: + +```java +@Autowired +private CheckoutService checkoutService; + +public void checkout(Integer[] bookIds, Integer userId){ + checkoutService.checkout(bookIds, userId); +} +``` + +在数据库中将用户的余额修改为100元 + +**③观察结果** + +可以通过@Transactional中的propagation属性设置事务传播行为 + +修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性 + +@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了 + +@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。 + + + +#### 7.3.10、全注解配置事务 + +**①添加配置类** + +```java +package com.lxcecho.config; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import javax.sql.DataSource; + +@Configuration +@ComponentScan("com.lxcecho") +@EnableTransactionManagement +public class SpringConfig { + + @Bean + public DataSource getDataSource(){ + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false"); + dataSource.setUsername("root"); + dataSource.setPassword("root"); + return dataSource; + } + + @Bean(name = "jdbcTemplate") + public JdbcTemplate getJdbcTemplate(DataSource dataSource){ + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + @Bean + public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){ + DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); + dataSourceTransactionManager.setDataSource(dataSource); + return dataSourceTransactionManager; + } +} +``` + +**②测试** + +```java +import com.lxcecho.config.SpringConfig; +import com.lxcecho.controller.BookController; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +public class TxByAllAnnotationTest { + + @Test + public void testTxAllAnnotation(){ + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); + BookController accountService = applicationContext.getBean("bookController", BookController.class); + accountService.buyBook(1, 1); + } +} +``` + + + +### 7.4、基于XML的声明式事务 + +#### 7.3.1、场景模拟 + +参考基于注解的声明式事务 + +#### 7.3.2、修改Spring配置文件 + +将Spring配置文件中去掉tx:annotation-driven 标签,并添加配置: + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + +> 注意:基于xml实现的声明式事务,必须引入aspectJ的依赖 +> +> ```xml +> +> org.springframework +> spring-aspects +> 6.0.2 +> +> ``` + + + +## 8、资源操作:Resources + +### 8.1、Spring Resources概述 + +![image-20221218154945878](images\spring6\image-20221218154945878.png) + +![image-20221206231535991](images\spring6\image-20221206231535991.png) + +Java的标准java.net.URL类和各种URL前缀的标准处理程序无法满足所有对low-level资源的访问,比如:没有标准化的 URL 实现可用于访问需要从类路径或相对于 ServletContext 获取的资源。并且缺少某些Spring所需要的功能,例如检测某资源是否存在等。**而Spring的Resource声明了访问low-level资源的能力。** + + + +### 8.2、Resource接口 + +Spring 的 Resource 接口位于 org.springframework.core.io 中。 旨在成为一个更强大的接口,用于抽象对低级资源的访问。以下显示了Resource接口定义的方法 + +```java +public interface Resource extends InputStreamSource { + + boolean exists(); + + boolean isReadable(); + + boolean isOpen(); + + boolean isFile(); + + URL getURL() throws IOException; + + URI getURI() throws IOException; + + File getFile() throws IOException; + + ReadableByteChannel readableChannel() throws IOException; + + long contentLength() throws IOException; + + long lastModified() throws IOException; + + Resource createRelative(String relativePath) throws IOException; + + String getFilename(); + + String getDescription(); +} +``` + +Resource接口继承了InputStreamSource接口,提供了很多InputStreamSource所没有的方法。InputStreamSource接口,只有一个方法: + +```java +public interface InputStreamSource { + + InputStream getInputStream() throws IOException; + +} +``` + +**其中一些重要的方法:** + +getInputStream(): 找到并打开资源,返回一个InputStream以从资源中读取。预计每次调用都会返回一个新的InputStream(),调用者有责任关闭每个流 +exists(): 返回一个布尔值,表明某个资源是否以物理形式存在 +isOpen: 返回一个布尔值,指示此资源是否具有开放流的句柄。如果为true,InputStream就不能够多次读取,只能够读取一次并且及时关闭以避免内存泄漏。对于所有常规资源实现,返回false,但是InputStreamResource除外。 +getDescription(): 返回资源的描述,用来输出错误的日志。这通常是完全限定的文件名或资源的实际URL。 + +**其他方法:** + +isReadable(): 表明资源的目录读取是否通过getInputStream()进行读取。 +isFile(): 表明这个资源是否代表了一个文件系统的文件。 +getURL(): 返回一个URL句柄,如果资源不能够被解析为URL,将抛出IOException +getURI(): 返回一个资源的URI句柄 +getFile(): 返回某个文件,如果资源不能够被解析称为绝对路径,将会抛出FileNotFoundException +lastModified(): 资源最后一次修改的时间戳 +createRelative(): 创建此资源的相关资源 +getFilename(): 资源的文件名是什么 例如:最后一部分的文件名 myfile.txt + + + +### 8.3、Resource的实现类 + +Resource 接口是 Spring 资源访问策略的抽象,它本身并不提供任何资源访问实现,具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource一般包括这些实现类:UrlResource、ClassPathResource、FileSystemResource、ServletContextResource、InputStreamResource、ByteArrayResource + +#### 8.3.1、UrlResource访问网络资源 + +Resource的一个实现类,用来访问网络资源,它支持URL的绝对路径。 + +http:------该前缀用于访问基于HTTP协议的网络资源。 + +ftp:------该前缀用于访问基于FTP协议的网络资源 + +file: ------该前缀用于从文件系统中读取资源 + +**实验:访问基于HTTP协议的网络资源** + +**创建一个maven子模块spring6-resources,配置Spring依赖(参考前面)** + +![image-20221207102315185](images\spring6\image-20221207102315185.png) + +```java +package com.lxcecho.resources; + +import org.springframework.core.io.UrlResource; + +public class UrlResourceDemo { + + public static void loadAndReadUrlResource(String path){ + // 创建一个 Resource 对象 + UrlResource url = null; + try { + url = new UrlResource(path); + // 获取资源名 + System.out.println(url.getFilename()); + System.out.println(url.getURI()); + // 获取资源描述 + System.out.println(url.getDescription()); + //获取资源内容 + System.out.println(url.getInputStream().read()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String[] args) { + //访问网络资源 + loadAndReadUrlResource("http://www.baidu.com"); + } +} +``` + + + +**实验二:在项目根路径下创建文件,从文件系统中读取资源** + +方法不变,修改调用传递路径 + +```java +public static void main(String[] args) { + //1 访问网络资源 + //loadAndReadUrlResource("http://www.lxcecho.com"); + + //2 访问文件系统资源 + loadAndReadUrlResource("file:lxcecho.txt"); +} +``` + + + +#### 8.3.2、ClassPathResource 访问类路径下资源 + +ClassPathResource 用来访问类加载路径下的资源,相对于其他的 Resource 实现类,其主要优势是方便访问类加载路径里的资源,尤其对于 Web 应用,ClassPathResource 可自动搜索位于 classes 下的资源文件,无须使用绝对路径访问。 + +**实验:在类路径下创建文件lxcecho.txt,使用ClassPathResource 访问** + +![image-20221207103020854](images\spring6\image-20221207103020854.png) + +```java +package com.lxcecho.resources; + +import org.springframework.core.io.ClassPathResource; +import java.io.InputStream; + +public class ClassPathResourceDemo { + + public static void loadAndReadUrlResource(String path) throws Exception{ + // 创建一个 Resource 对象 + ClassPathResource resource = new ClassPathResource(path); + // 获取文件名 + System.out.println("resource.getFileName = " + resource.getFilename()); + // 获取文件描述 + System.out.println("resource.getDescription = "+ resource.getDescription()); + //获取文件内容 + InputStream in = resource.getInputStream(); + byte[] b = new byte[1024]; + while(in.read(b)!=-1) { + System.out.println(new String(b)); + } + } + + public static void main(String[] args) throws Exception { + loadAndReadUrlResource("lxcecho.txt"); + } +} +``` + +ClassPathResource实例可使用ClassPathResource构造器显式地创建,但更多的时候它都是隐式地创建的。当执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含classpath:前缀后,系统会自动创建ClassPathResource对象。 + + + +#### 8.3.3、FileSystemResource 访问文件系统资源 + +Spring 提供的 FileSystemResource 类用于访问文件系统资源,使用 FileSystemResource 来访问文件系统资源并没有太大的优势,因为 Java 提供的 File 类也可用于访问文件系统资源。 + +**实验:使用FileSystemResource 访问文件系统资源** + +```java +package com.lxcecho.resources; + +import org.springframework.core.io.FileSystemResource; + +import java.io.InputStream; + +public class FileSystemResourceDemo { + + public static void loadAndReadUrlResource(String path) throws Exception{ + //相对路径 + FileSystemResource resource = new FileSystemResource("lxcecho.txt"); + //绝对路径 + //FileSystemResource resource = new FileSystemResource("C:\\lxcecho.txt"); + // 获取文件名 + System.out.println("resource.getFileName = " + resource.getFilename()); + // 获取文件描述 + System.out.println("resource.getDescription = "+ resource.getDescription()); + //获取文件内容 + InputStream in = resource.getInputStream(); + byte[] b = new byte[1024]; + while(in.read(b)!=-1) { + System.out.println(new String(b)); + } + } + + public static void main(String[] args) throws Exception { + loadAndReadUrlResource("lxcecho.txt"); + } +} +``` + +FileSystemResource实例可使用FileSystemResource构造器显示地创建,但更多的时候它都是隐式创建。执行Spring的某个方法时,该方法接受一个代表资源路径的字符串参数,当Spring识别该字符串参数中包含file:前缀后,系统将会自动创建FileSystemResource对象。 + + + +#### 8.3.4、ServletContextResource + +这是ServletContext资源的Resource实现,它解释相关Web应用程序根目录中的相对路径。它始终支持流(stream)访问和URL访问,但只有在扩展Web应用程序存档且资源实际位于文件系统上时才允许java.io.File访问。无论它是在文件系统上扩展还是直接从JAR或其他地方(如数据库)访问,实际上都依赖于Servlet容器。 + + + +#### 8.3.5、InputStreamResource + +InputStreamResource 是给定的输入流(InputStream)的Resource实现。它的使用场景在没有特定的资源实现的时候使用(感觉和@Component 的适用场景很相似)。与其他Resource实现相比,这是已打开资源的描述符。 因此,它的isOpen()方法返回true。如果需要将资源描述符保留在某处或者需要多次读取流,请不要使用它。 + + + +#### 8.3.6、ByteArrayResource + +字节数组的Resource实现类。通过给定的数组创建了一个ByteArrayInputStream。它对于从任何给定的字节数组加载内容非常有用,而无需求助于单次使用的InputStreamResource。 + + + +### 8.4、Resource类图 + +上述Resource实现类与Resource顶级接口之间的关系可以用下面的UML关系模型来表示 + +![image-20221206232920494](images\spring6\image-20221206232920494.png) + + + +### 8.5、ResourceLoader 接口 + +#### 8.5.1、ResourceLoader 概述 + +Spring 提供如下两个标志性接口: + +**(1)ResourceLoader :** 该接口实现类的实例可以获得一个Resource实例。 + +**(2) ResourceLoaderAware :** 该接口实现类的实例将获得一个ResourceLoader的引用。 + +在ResourceLoader接口里有如下方法: + +(1)**Resource getResource(String location)** : 该接口仅有这个方法,用于返回一个Resource实例。ApplicationContext实现类都实现ResourceLoader接口,因此ApplicationContext可直接获取Resource实例。 + + + +#### 8.5.2、使用演示 + +**实验一:ClassPathXmlApplicationContext获取Resource实例** + +```java +package com.lxcecho.resouceloader; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.Resource; + +public class Demo1 { + + public static void main(String[] args) { + ApplicationContext ctx = new ClassPathXmlApplicationContext(); +// 通过ApplicationContext访问资源 +// ApplicationContext实例获取Resource实例时, +// 默认采用与ApplicationContext相同的资源访问策略 + Resource res = ctx.getResource("lxcecho.txt"); + System.out.println(res.getFilename()); + } +} +``` + + + +**实验二:FileSystemApplicationContext获取Resource实例** + +```java +package com.lxcecho.resouceloader; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; +import org.springframework.core.io.Resource; + +public class Demo2 { + + public static void main(String[] args) { + ApplicationContext ctx = new FileSystemXmlApplicationContext(); + Resource res = ctx.getResource("lxcecho.txt"); + System.out.println(res.getFilename()); + } +} +``` + + + +#### 8.5.3、ResourceLoader 总结 + +Spring将采用和ApplicationContext相同的策略来访问资源。也就是说,如果ApplicationContext是FileSystemXmlApplicationContext,res就是FileSystemResource实例;如果ApplicationContext是ClassPathXmlApplicationContext,res就是ClassPathResource实例 + +当Spring应用需要进行资源访问时,实际上并不需要直接使用Resource实现类,而是调用ResourceLoader实例的getResource()方法来获得资源,ReosurceLoader将会负责选择Reosurce实现类,也就是确定具体的资源访问策略,从而将应用程序和具体的资源访问策略分离开来 + +另外,使用ApplicationContext访问资源时,可通过不同前缀指定强制使用指定的ClassPathResource、FileSystemResource等实现类 + +```java +Resource res = ctx.getResource("calsspath:bean.xml"); +Resrouce res = ctx.getResource("file:bean.xml"); +Resource res = ctx.getResource("http://localhost:8080/beans.xml"); +``` + + + +### 8.6、ResourceLoaderAware 接口 + +ResourceLoaderAware接口实现类的实例将获得一个ResourceLoader的引用,ResourceLoaderAware接口也提供了一个setResourceLoader()方法,该方法将由Spring容器负责调用,Spring容器会将一个ResourceLoader对象作为该方法的参数传入。 + +如果把实现ResourceLoaderAware接口的Bean类部署在Spring容器中,Spring容器会将自身当成ResourceLoader作为setResourceLoader()方法的参数传入。由于ApplicationContext的实现类都实现了ResourceLoader接口,Spring容器自身完全可作为ResorceLoader使用。 + +**实验:演示ResourceLoaderAware使用** + +**第一步 创建类,实现ResourceLoaderAware接口** + +```java +package com.lxcecho.resouceloader; + +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ResourceLoader; + +public class TestBean implements ResourceLoaderAware { + + private ResourceLoader resourceLoader; + + //实现ResourceLoaderAware接口必须实现的方法 + //如果把该Bean部署在Spring容器中,该方法将会有Spring容器负责调用。 + //SPring容器调用该方法时,Spring会将自身作为参数传给该方法。 + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + //返回ResourceLoader对象的应用 + public ResourceLoader getResourceLoader(){ + return this.resourceLoader; + } + +} +``` + +**第二步 创建bean.xml文件,配置TestBean** + +```xml + + + + + +``` + +**第三步 测试** + +```java +package com.lxcecho.resouceloader; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +public class Demo3 { + + public static void main(String[] args) { + //Spring容器会将一个ResourceLoader对象作为该方法的参数传入 + ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml"); + TestBean testBean = ctx.getBean("testBean",TestBean.class); + //获取ResourceLoader对象 + ResourceLoader resourceLoader = testBean.getResourceLoader(); + System.out.println("Spring容器将自身注入到ResourceLoaderAware Bean 中 ? :" + (resourceLoader == ctx)); + //加载其他资源 + Resource resource = resourceLoader.getResource("lxcecho.txt"); + System.out.println(resource.getFilename()); + System.out.println(resource.getDescription()); + } +} +``` + + + +### 8.7、使用Resource 作为属性 + +前面介绍了 Spring 提供的资源访问策略,但这些依赖访问策略要么需要使用 Resource 实现类,要么需要使用 ApplicationContext 来获取资源。实际上,当应用程序中的 Bean 实例需要访问资源时,Spring 有更好的解决方法:直接利用依赖注入。从这个意义上来看,Spring 框架不仅充分利用了策略模式来简化资源访问,而且还将策略模式和 IoC 进行充分地结合,最大程度地简化了 Spring 资源访问。 + +归纳起来,**如果 Bean 实例需要访问资源,有如下两种解决方案:** + +- **代码中获取 Resource 实例。** +- **使用依赖注入。** + +对于第一种方式,当程序获取 Resource 实例时,总需要提供 Resource 所在的位置,不管通过 FileSystemResource 创建实例,还是通过 ClassPathResource 创建实例,或者通过 ApplicationContext 的 getResource() 方法获取实例,都需要提供资源位置。这意味着:资源所在的物理位置将被耦合到代码中,如果资源位置发生改变,则必须改写程序。因此,通常建议采用第二种方法,让 Spring 为 Bean 实例**依赖注入**资源。 + +**实验:让Spring为Bean实例依赖注入资源** + +**第一步 创建依赖注入类,定义属性和方法** + +```java +package com.lxcecho.resouceloader; + +import org.springframework.core.io.Resource; + +public class ResourceBean { + + private Resource res; + + public void setRes(Resource res) { + this.res = res; + } + public Resource getRes() { + return res; + } + + public void parse(){ + System.out.println(res.getFilename()); + System.out.println(res.getDescription()); + } +} +``` + +**第二步 创建spring配置文件,配置依赖注入** + +```xml + + + + + + + + + +``` + +**第三步 测试** + +```java +package com.lxcecho.resouceloader; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +public class Demo4 { + + public static void main(String[] args) { + ApplicationContext ctx = + new ClassPathXmlApplicationContext("bean.xml"); + ResourceBean resourceBean = ctx.getBean("resourceBean",ResourceBean.class); + resourceBean.parse(); + } +} +``` + + + +### 8.8、应用程序上下文和资源路径 + +#### 8.8.1、概述 + +不管以怎样的方式创建ApplicationContext实例,都需要为ApplicationContext指定配置文件,Spring允许使用一份或多分XML配置文件。当程序创建ApplicationContext实例时,通常也是以Resource的方式来访问配置文件的,所以ApplicationContext完全支持ClassPathResource、FileSystemResource、ServletContextResource等资源访问方式。 + +**ApplicationContext确定资源访问策略通常有两种方法:** + +**(1)使用ApplicationContext实现类指定访问策略。** + +**(2)使用前缀指定访问策略。** + + + +#### 8.8.2、ApplicationContext实现类指定访问策略 + +创建ApplicationContext对象时,通常可以使用如下实现类: + +(1) ClassPathXMLApplicationContext : 对应使用ClassPathResource进行资源访问。 + +(2)FileSystemXmlApplicationContext : 对应使用FileSystemResource进行资源访问。 + +(3)XmlWebApplicationContext : 对应使用ServletContextResource进行资源访问。 + +当使用ApplicationContext的不同实现类时,就意味着Spring使用响应的资源访问策略。 + +效果前面已经演示 + + + +#### 8.8.3、使用前缀指定访问策略 + +**实验一:classpath前缀使用** + +```java +package com.lxcecho.context; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; +import org.springframework.core.io.Resource; + +public class Demo1 { + + public static void main(String[] args) { + /* + * 通过搜索文件系统路径下的xml文件创建ApplicationContext, + * 但通过指定classpath:前缀强制搜索类加载路径 + * classpath:bean.xml + * */ + ApplicationContext ctx = + new ClassPathXmlApplicationContext("classpath:bean.xml"); + System.out.println(ctx); + Resource resource = ctx.getResource("lxcecho.txt"); + System.out.println(resource.getFilename()); + System.out.println(resource.getDescription()); + } +} +``` + + + +**实验二:classpath通配符使用** + +classpath * :前缀提供了加载多个XML配置文件的能力,当使用classpath*:前缀来指定XML配置文件时,系统将搜索类加载路径,找到所有与文件名匹配的文件,分别加载文件中的配置定义,最后合并成一个ApplicationContext。 + +```java +ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean.xml"); +System.out.println(ctx); +``` + +当使用classpath * :前缀时,Spring将会搜索类加载路径下所有满足该规则的配置文件。 + +如果不是采用classpath * :前缀,而是改为使用classpath:前缀,Spring则只加载第一个符合条件的XML文件 + +**注意 :** + +classpath * : 前缀仅对ApplicationContext有效。实际情况是,创建ApplicationContext时,分别访问多个配置文件(通过ClassLoader的getResource方法实现)。因此,classpath * :前缀不可用于Resource。 + + + +**使用三:通配符其他使用** + +一次性加载多个配置文件的方式:指定配置文件时使用通配符 + +```java +ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:bean*.xml"); +``` + +Spring允许将classpath*:前缀和通配符结合使用: + +```java +ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean*.xml"); +``` + + + +## 9、国际化:i18n + +![image-20221218154728062](images\spring6\image-20221218154728062.png) + +### 9.1、i18n概述 + +国际化也称作i18n,其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数。由于软件发行可能面向多个国家,对于不同国家的用户,软件显示不同语言的过程就是国际化。通常来讲,软件中的国际化是通过配置文件来实现的,假设要支撑两种语言,那么就需要两个版本的配置文件。 + + + +### 9.2、Java国际化 + +(1)Java自身是支持国际化的,java.util.Locale用于指定当前用户所属的语言环境等信息,java.util.ResourceBundle用于查找绑定对应的资源文件。Locale包含了language信息和country信息,Locale创建默认locale对象时使用的静态方法: + +```java + /** + * This method must be called only for creating the Locale.* + * constants due to making shortcuts. + */ + private static Locale createConstant(String lang, String country) { + BaseLocale base = BaseLocale.createInstance(lang, country); + return getInstance(base, null); + } +``` + +(2)配置文件命名规则: + **basename_language_country.properties** + 必须遵循以上的命名规则,java才会识别。其中,basename是必须的,语言和国家是可选的。这里存在一个优先级概念,如果同时提供了messages.properties和messages_zh_CN.propertes两个配置文件,如果提供的locale符合en_CN,那么优先查找messages_en_CN.propertes配置文件,如果没查找到,再查找messages.properties配置文件。最后,提示下,所有的配置文件必须放在classpath中,一般放在resources目录下 + +**(3)实验:演示Java国际化** + +**第一步 创建子模块spring6-i18n,引入spring依赖** + +![image-20221207122500801](images\spring6\image-20221207122500801.png) + +**第二步 在resource目录下创建两个配置文件:messages_zh_CN.propertes和messages_en_GB.propertes** + +![image-20221207124839565](images\spring6\image-20221207124839565.png) + +**第三步 测试** + +```java +package com.lxcecho.javai18n; + +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.ResourceBundle; + +public class Demo1 { + + public static void main(String[] args) { + System.out.println(ResourceBundle.getBundle("messages", + new Locale("en","GB")).getString("test")); + + System.out.println(ResourceBundle.getBundle("messages", + new Locale("zh","CN")).getString("test")); + } +} +``` + + + +### 9.3、Spring6国际化 + +#### 9.3.1、MessageSource接口 + +spring中国际化是通过MessageSource这个接口来支持的 + +**常见实现类** + +**ResourceBundleMessageSource** + +这个是基于Java的ResourceBundle基础类实现,允许仅通过资源名加载国际化资源 + +**ReloadableResourceBundleMessageSource** + +这个功能和第一个类的功能类似,多了定时刷新功能,允许在不重启系统的情况下,更新资源的信息 + +**StaticMessageSource** + +它允许通过编程的方式提供国际化信息,一会我们可以通过这个来实现db中存储国际化信息的功能。 + + + +#### 9.3.2、使用Spring6国际化 + +**第一步 创建资源文件** + +**国际化文件命名格式:基本名称 _ 语言 _ 国家.properties** + +**{0},{1}这样内容,就是动态参数** + +![image-20221207140024056](images\spring6\image-20221207140024056.png) + +**(1)创建lxcecho_en_US.properties** + +```properties +www.lxcecho.com=welcome {0},时间:{1} +``` + +**(2)创建lxcecho_zh_CN.properties** + +```properties +www.lxcecho.com=欢迎 {0},时间:{1} +``` + + + +**第二步 创建spring配置文件,配置MessageSource** + +```xml + + + + + + + lxcecho + + + + utf-8 + + + +``` + + + +**第三步 创建测试类** + +```java +package com.lxcecho.javai18n; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import java.util.Date; +import java.util.Locale; + +public class Demo2 { + + public static void main(String[] args) { + + ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); + + //传递动态参数,使用数组形式对应{0} {1}顺序 + Object[] objs = new Object[]{"lxcecho",new Date().toString()}; + + //www.lxcecho.com为资源文件的key值, + //objs为资源文件value值所需要的参数,Local.CHINA为国际化为语言 + String str=context.getMessage("www.lxcecho.com", objs, Locale.CHINA); + System.out.println(str); + } +} +``` + + + +## 10、数据校验:Validation + +![image-20221218154808754](images\spring6\image-20221218154808754.png) + +### 10.1、Spring Validation概述 + +![image-20221206220207266](images\spring6\image-20221206220207266.png) + +在开发中,我们经常遇到参数校验的需求,比如用户注册的时候,要校验用户名不能为空、用户名长度不超过20个字符、手机号是合法的手机号格式等等。如果使用普通方式,我们会把校验的代码和真正的业务处理逻辑耦合在一起,而且如果未来要新增一种校验逻辑也需要在修改多个地方。而spring validation允许通过注解的方式来定义对象校验规则,把校验和业务逻辑分离开,让代码编写更加方便。Spring Validation其实就是对Hibernate Validator进一步的封装,方便在Spring中使用。 + +在Spring中有多种校验的方式 + +**第一种是通过实现org.springframework.validation.Validator接口,然后在代码中调用这个类** + +**第二种是按照Bean Validation方式来进行校验,即通过注解的方式。** + +**第三种是基于方法实现校验** + +**除此之外,还可以实现自定义校验** + + + +### 10.2、实验一:通过Validator接口实现 + +**第一步 创建子模块 spring6-validator** + +![image-20221206221002615](images\spring6\image-20221206221002615.png) + + + +**第二步 引入相关依赖** + +```xml + + + org.hibernate.validator + hibernate-validator + 7.0.5.Final + + + + org.glassfish + jakarta.el + 4.0.1 + + +``` + + + +**第三步 创建实体类,定义属性和方法** + +```java +package com.lxcecho.validation.method1; + +public class Person { + private String name; + private int age; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } +} +``` + + + +**第四步 创建类实现Validator接口,实现接口方法指定校验规则** + +```java +package com.lxcecho.validation.method1; + +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + +public class PersonValidator implements Validator { + + @Override + public boolean supports(Class clazz) { + return Person.class.equals(clazz); + } + + @Override + public void validate(Object object, Errors errors) { + ValidationUtils.rejectIfEmpty(errors, "name", "name.empty"); + Person p = (Person) object; + if (p.getAge() < 0) { + errors.rejectValue("age", "error value < 0"); + } else if (p.getAge() > 110) { + errors.rejectValue("age", "error value too old"); + } + } +} +``` + +上面定义的类,其实就是实现接口中对应的方法, + +supports方法用来表示此校验用在哪个类型上, + +validate是设置校验逻辑的地点,其中ValidationUtils,是Spring封装的校验工具类,帮助快速实现校验。 + + + +**第五步 使用上述Validator进行测试** + +```java +package com.lxcecho.validation.method1; + +import org.springframework.validation.BindingResult; +import org.springframework.validation.DataBinder; + +public class TestMethod1 { + + public static void main(String[] args) { + //创建person对象 + Person person = new Person(); + person.setName("lucy"); + person.setAge(-1); + + // 创建Person对应的DataBinder + DataBinder binder = new DataBinder(person); + + // 设置校验 + binder.setValidator(new PersonValidator()); + + // 由于Person对象中的属性为空,所以校验不通过 + binder.validate(); + + //输出结果 + BindingResult results = binder.getBindingResult(); + System.out.println(results.getAllErrors()); + } +} +``` + + + +### 10.3、实验二:Bean Validation注解实现 + +使用Bean Validation校验方式,就是如何将Bean Validation需要使用的javax.validation.ValidatorFactory 和javax.validation.Validator注入到容器中。spring默认有一个实现类LocalValidatorFactoryBean,它实现了上面Bean Validation中的接口,并且也实现了org.springframework.validation.Validator接口。 + +**第一步 创建配置类,配置LocalValidatorFactoryBean** + +```java +@Configuration +@ComponentScan("com.lxcecho.validation.method2") +public class ValidationConfig { + + @Bean + public LocalValidatorFactoryBean validator() { + return new LocalValidatorFactoryBean(); + } +} +``` + + + +**第二步 创建实体类,使用注解定义校验规则** + +```java +package com.lxcecho.validation.method2; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public class User { + + @NotNull + private String name; + + @Min(0) + @Max(120) + private int age; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } +} +``` + +**常用注解说明** +@NotNull 限制必须不为null +@NotEmpty 只作用于字符串类型,字符串不为空,并且长度不为0 +@NotBlank 只作用于字符串类型,字符串不为空,并且trim()后不为空串 +@DecimalMax(value) 限制必须为一个不大于指定值的数字 +@DecimalMin(value) 限制必须为一个不小于指定值的数字 +@Max(value) 限制必须为一个不大于指定值的数字 +@Min(value) 限制必须为一个不小于指定值的数字 +@Pattern(value) 限制必须符合指定的正则表达式 +@Size(max,min) 限制字符长度必须在min到max之间 +@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 + + + +**第三步 使用两种不同的校验器实现** + +**(1)使用jakarta.validation.Validator校验** + +```java +package com.lxcecho.validation.method2; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.Set; + +@Service +public class MyService1 { + + @Autowired + private Validator validator; + + public boolean validator(User user){ + Set> sets = validator.validate(user); + return sets.isEmpty(); + } + +} +``` + +**(2)使用org.springframework.validation.Validator校验** + +```java +package com.lxcecho.validation.method2; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.validation.BindException; +import org.springframework.validation.Validator; + +@Service +public class MyService2 { + + @Autowired + private Validator validator; + + public boolean validaPersonByValidator(User user) { + BindException bindException = new BindException(user, user.getName()); + validator.validate(user, bindException); + return bindException.hasErrors(); + } +} +``` + + + +**第四步 测试** + +```java +package com.lxcecho.validation.method2; + +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class TestMethod2 { + + @Test + public void testMyService1() { + ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); + MyService1 myService = context.getBean(MyService1.class); + User user = new User(); + user.setAge(-1); + boolean validator = myService.validator(user); + System.out.println(validator); + } + + @Test + public void testMyService2() { + ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); + MyService2 myService = context.getBean(MyService2.class); + User user = new User(); + user.setName("lucy"); + user.setAge(130); + user.setAge(-1); + boolean validator = myService.validaPersonByValidator(user); + System.out.println(validator); + } +} +``` + + + +### 10.4、实验三:基于方法实现校验 + +**第一步 创建配置类,配置MethodValidationPostProcessor** + +```java +package com.lxcecho.validation.method3; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + +@Configuration +@ComponentScan("com.lxcecho.validation.method3") +public class ValidationConfig { + + @Bean + public MethodValidationPostProcessor validationPostProcessor() { + return new MethodValidationPostProcessor(); + } +} +``` + +**第二步 创建实体类,使用注解设置校验规则** + +```java +package com.lxcecho.validation.method3; + +import jakarta.validation.constraints.*; + +public class User { + + @NotNull + private String name; + + @Min(0) + @Max(120) + private int age; + + @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误") + @NotBlank(message = "手机号码不能为空") + private String phone; + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + public int getAge() { + return age; + } + public void setAge(int age) { + this.age = age; + } + public String getPhone() { + return phone; + } + public void setPhone(String phone) { + this.phone = phone; + } +} +``` + +**第三步 定义Service类,通过注解操作对象** + +```java +package com.lxcecho.validation.method3; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +@Service +@Validated +public class MyService { + + public String testParams(@NotNull @Valid User user) { + return user.toString(); + } + +} +``` + +**第四步 测试** + +```java +package com.lxcecho.validation.method3; + +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class TestMethod3 { + + @Test + public void testMyService1() { + ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); + MyService myService = context.getBean(MyService.class); + User user = new User(); + user.setAge(-1); + myService.testParams(user); + } +} +``` + + + +### 10.5、实验四:实现自定义校验 + +**第一步 自定义校验注解** + +```java +package com.lxcecho.validation.method4; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.*; + +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = {CannotBlankValidator.class}) +public @interface CannotBlank { + //默认错误消息 + String message() default "不能包含空格"; + + //分组 + Class[] groups() default {}; + + //负载 + Class[] payload() default {}; + + //指定多个时使用 + @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + @interface List { + CannotBlank[] value(); + } +} +``` + +**第二步 编写真正的校验类** + +```java +package com.lxcecho.validation.method4; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +public class CannotBlankValidator implements ConstraintValidator { + + @Override + public void initialize(CannotBlank constraintAnnotation) { + } + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + //null时不进行校验 + if (value != null && value.contains(" ")) { + //获取默认提示信息 + String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); + System.out.println("default message :" + defaultConstraintMessageTemplate); + //禁用默认提示信息 + context.disableDefaultConstraintViolation(); + //设置提示语 + context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); + return false; + } + return true; + } +} +``` + + + +## 11、提前编译:AOT + +![image-20221218154841001](images\spring6\image-20221218154841001.png) + +### 11.1、AOT概述 + +#### 11.1.1、JIT与AOT的区别 + +JIT和AOT 这个名词是指两种不同的编译方式,这两种编译方式的主要区别在于是否在“运行时”进行编译 + +**(1)JIT, Just-in-time,动态(即时)编译,边运行边编译;** + +在程序运行时,根据算法计算出热点代码,然后进行 JIT 实时编译,这种方式吞吐量高,有运行时性能加成,可以跑得更快,并可以做到动态生成代码等,但是相对启动速度较慢,并需要一定时间和调用频率才能触发 JIT 的分层机制。JIT 缺点就是编译需要占用运行时资源,会导致进程卡顿。 + +**(2)AOT,Ahead Of Time,指运行前编译,预先编译。** + +AOT 编译能直接将源代码转化为机器码,内存占用低,启动速度快,可以无需 runtime 运行,直接将 runtime 静态链接至最终的程序中,但是无运行时性能加成,不能根据程序运行情况做进一步的优化,AOT 缺点就是在程序运行前编译会使程序安装的时间增加。 + +**简单来讲:**JIT即时编译指的是在程序的运行过程中,将字节码转换为可在硬件上直接运行的机器码,并部署至托管环境中的过程。而 AOT 编译指的则是,在程序运行之前,便将字节码转换为机器码的过程。 + +``` +.java -> .class -> (使用jaotc编译工具) -> .so(程序函数库,即编译好的可以供其他程序使用的代码和数据) +``` + +![image-20221207113544080](images\spring6\image-20221207113544080.png) + +**(3)AOT的优点** + +**简单来讲,**Java 虚拟机加载已经预编译成二进制库,可以直接执行。不必等待及时编译器的预热,减少 Java 应用给人带来“第一次运行慢” 的不良体验。 + +在程序运行前编译,可以避免在运行时的编译性能消耗和内存消耗 +可以在程序运行初期就达到最高性能,程序启动速度快 +运行产物只有机器码,打包体积小 + +**AOT的缺点** + +由于是静态提前编译,不能根据硬件情况或程序运行情况择优选择机器指令序列,理论峰值性能不如JIT +没有动态能力,同一份产物不能跨平台运行 + +第一种即时编译 (JIT) 是默认模式,Java Hotspot 虚拟机使用它在运行时将字节码转换为机器码。后者提前编译 (AOT)由新颖的 GraalVM 编译器支持,并允许在构建时将字节码直接静态编译为机器码。 + +现在正处于云原生,降本增效的时代,Java 相比于 Go、Rust 等其他编程语言非常大的弊端就是启动编译和启动进程非常慢,这对于根据实时计算资源,弹性扩缩容的云原生技术相冲突,Spring6 借助 AOT 技术在运行时内存占用低,启动速度快,逐渐的来满足 Java 在云原生时代的需求,对于大规模使用 Java 应用的商业公司可以考虑尽早调研使用 JDK17,通过云原生技术为公司实现降本增效。 + + + +#### 11.1.2、Graalvm + +Spring6 支持的 AOT 技术,这个 GraalVM 就是底层的支持,Spring 也对 GraalVM 本机映像提供了一流的支持。GraalVM 是一种高性能 JDK,旨在加速用 Java 和其他 JVM 语言编写的应用程序的执行,同时还为 JavaScript、Python 和许多其他流行语言提供运行时。 GraalVM 提供两种运行 Java 应用程序的方法:在 HotSpot JVM 上使用 Graal 即时 (JIT) 编译器或作为提前 (AOT) 编译的本机可执行文件。 GraalVM 的多语言能力使得在单个应用程序中混合多种编程语言成为可能,同时消除了外语调用成本。GraalVM 向 HotSpot Java 虚拟机添加了一个用 Java 编写的高级即时 (JIT) 优化编译器。 + +GraalVM 具有以下特性: + +(1)一种高级优化编译器,它生成更快、更精简的代码,需要更少的计算资源 + +(2)AOT 本机图像编译提前将 Java 应用程序编译为本机二进制文件,立即启动,无需预热即可实现最高性能 + +(3)Polyglot 编程在单个应用程序中利用流行语言的最佳功能和库,无需额外开销 + +(4)高级工具在 Java 和多种语言中调试、监视、分析和优化资源消耗 + +总的来说对云原生的要求不算高短期内可以继续使用 2.7.X 的版本和 JDK8,不过 Spring 官方已经对 Spring6 进行了正式版发布。 + + + +#### 11.1.3、Native Image + +目前业界除了这种在JVM中进行AOT的方案,还有另外一种实现Java AOT的思路,那就是直接摒弃JVM,和C/C++一样通过编译器直接将代码编译成机器代码,然后运行。这无疑是一种直接颠覆Java语言设计的思路,那就是GraalVM Native Image。它通过C语言实现了一个超微缩的运行时组件 —— Substrate VM,基本实现了JVM的各种特性,但足够轻量、可以被轻松内嵌,这就让Java语言和工程摆脱JVM的限制,能够真正意义上实现和C/C++一样的AOT编译。这一方案在经过长时间的优化和积累后,已经拥有非常不错的效果,基本上成为Oracle官方首推的Java AOT解决方案。 +Native Image 是一项创新技术,可将 Java 代码编译成独立的本机可执行文件或本机共享库。在构建本机可执行文件期间处理的 Java 字节码包括所有应用程序类、依赖项、第三方依赖库和任何所需的 JDK 类。生成的自包含本机可执行文件特定于不需要 JVM 的每个单独的操作系统和机器体系结构。 + + + + +### 11.2、演示Native Image构建过程 + +#### 11.2.1、GraalVM安装 + +##### (1)下载GraalVM + +进入官网下载:https://www.graalvm.org/downloads/ + +![image-20221207153944132](images\spring6\image-20221207153944132.png) + +![image-20221207152841304](images\spring6\image-20221207152841304.png) + +##### (2)配置环境变量 + +**添加GRAALVM_HOME** + +![image-20221207110539954](images\spring6\image-20221207110539954.png) + +**把JAVA_HOME修改为graalvm的位置** + +![image-20221207153724340](images\spring6\image-20221207153724340.png) + +**把Path修改位graalvm的bin位置** + +![image-20221207153755732](images\spring6\image-20221207153755732.png) + +**使用命令查看是否安装成功** + +![image-20221207153642253](images\spring6\image-20221207153642253.png) + +##### (3)安装native-image插件 + +**使用命令 gu install native-image下载安装** + +![image-20221207155009832](images\spring6\image-20221207155009832.png) + + + +#### 11.2.2、安装C++的编译环境 + +##### (1)下载Visual Studio安装软件 + +https://visualstudio.microsoft.com/zh-hans/downloads/ + +![image-20221219112426052](images\spring6\image-20221219112426052.png) + +##### (2)安装Visual Studio + +![image-20221207155726572](images\spring6\image-20221207155726572.png) + +![image-20221207155756512](images\spring6\image-20221207155756512.png) + +##### (3)添加Visual Studio环境变量 + +配置INCLUDE、LIB和Path + +![image-20221207110947997](images\spring6\image-20221207110947997.png) + + + +![image-20221207111012582](images\spring6\image-20221207111012582.png) + + + +![image-20221207111105569](images\spring6\image-20221207111105569.png) + + + +##### (4)打开工具,在工具中操作 + +![image-20221207111206279](images\spring6\image-20221207111206279.png) + + + +#### 11.2.3、编写代码,构建Native Image + +##### (1)编写Java代码 + +```java +public class Hello { + + public static void main(String[] args) { + System.out.println("hello world"); + } +} +``` + +##### (2)复制文件到目录,执行编译 + +![image-20221207111420056](images\spring6\image-20221207111420056.png) + +##### (3)Native Image 进行构建 + +![image-20221207111509837](images\spring6\image-20221207111509837.png) + +![image-20221207111609878](images\spring6\image-20221207111609878.png) + +##### (4)查看构建的文件 + +![image-20221207111644950](images\spring6\image-20221207111644950.png) + +##### (5)执行构建的文件 + +![image-20221207111731150](images\spring6\image-20221207111731150.png) + +可以看到这个Hello最终打包产出的二进制文件大小为11M,这是包含了SVM和JDK各种库后的大小,虽然相比C/C++的二进制文件来说体积偏大,但是对比完整JVM来说,可以说是已经是非常小了。 + +相比于使用JVM运行,Native Image的速度要快上不少,cpu占用也更低一些,从官方提供的各类实验数据也可以看出Native Image对于启动速度和内存占用带来的提升是非常显著的: + +![image-20221207111947283](images\spring6\image-20221207111947283.png) + + + +![image-20221207112009852](images\spring6\image-20221207112009852.png) + diff --git a/spring-lxcecho-sample/build.gradle b/spring-lxcecho-sample/build.gradle new file mode 100644 index 000000000000..d539e43d91f6 --- /dev/null +++ b/spring-lxcecho-sample/build.gradle @@ -0,0 +1,38 @@ +description "Spring Echo Sample" + +group = 'com.lxcecho' +version = '6.0.15-SNAPSHOT' + +dependencies { + /*当你引入 Spring Context 依赖之后,表示将 Spring 的基础依赖引入了*/ + api(project(":spring-context")) + + api(project(":spring-aop")) + api(project(":spring-aspects")) + + api(project(":spring-jdbc")) + implementation 'com.alibaba:druid:1.2.15' + implementation 'mysql:mysql-connector-java:8.0.30' + + optional("org.hibernate:hibernate-validator") + testRuntimeOnly("org.glassfish:jakarta.el") + + optional("org.apache.logging.log4j:log4j-api") + optional("org.slf4j:slf4j-api") + optional("org.apache.logging.log4j:log4j-core") + optional("org.apache.logging.log4j:log4j-jul") + optional("org.apache.logging.log4j:log4j-slf4j2-impl") + + optional("jakarta.servlet:jakarta.servlet-api") + optional("jakarta.annotation:jakarta.annotation-api") + + api(project(":spring-test")) + + api("junit:junit:4.13.2") + testFixturesApi("org.junit.jupiter:junit-jupiter-api") + testFixturesApi("org.junit.jupiter:junit-jupiter-params") +} + +//test { +// useJUnitPlatform() +//} \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/Calculator.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/Calculator.java new file mode 100644 index 000000000000..ea7f91f9e464 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/Calculator.java @@ -0,0 +1,16 @@ +package com.lxcecho.aop.annoaop; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface Calculator { + + int add(int i, int j); + + int sub(int i, int j); + + int mul(int i, int j); + + int div(int i, int j); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/CalculatorImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/CalculatorImpl.java new file mode 100644 index 000000000000..27b299c15a24 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/CalculatorImpl.java @@ -0,0 +1,55 @@ +package com.lxcecho.aop.annoaop; + +import org.springframework.stereotype.Component; + +/** + * 基本实现类 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Component +public class CalculatorImpl implements Calculator { + + @Override + public int add(int i, int j) { + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + // 为了测试,模拟异常出现 + // int a = 1/0; + return result; + } + + @Override + public int sub(int i, int j) { + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int div(int i, int j) { + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + return result; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/LogAspect.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/LogAspect.java new file mode 100644 index 000000000000..9c5ff39138fe --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/LogAspect.java @@ -0,0 +1,120 @@ +package com.lxcecho.aop.annoaop; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +/** + * 切面类 + * 执行顺序: + * 1,@Around 环绕通知 + * 2. @Before 前置通知 + * 3. 调用 方法 + * 4. @Around 方法执行耗时 + * 5. @After 后置通知 + * 6. @AfterReturning 返回通知 + * 7. @AfterThrowing 异常通知 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Aspect // 切面类 +@Component // ioc 容器 +public class LogAspect { + + /** + * 设置切入点和通知类型: + * 切入点表达式: execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数)) + * execution:用于匹配方法执行的连接点 + * + * 定义一个切入点,后面的通知直接引入切入点方法 pointCut() 即可 + * 第一个 *:表示返回值任意类型 + * 第二个 .*(..):表示任何方法名,括号表示参数,两个点 .. 表示任何参数类型 + */ + @Pointcut(value = "execution(* com.lxcecho.aop.annoaop.CalculatorImpl.*(..))") + public void pointCut() { + } + + /** + * 前置 @Before(value="切入点表达式配置切入点"):进入环绕后执行,下一步执行方法 + * + * @param joinPoint + */ + @Before(value = "pointCut()") +// @Before("execution(* com.lxcecho.aop.annoaop.CalculatorImpl.*(..))") + public void beforeMethod(JoinPoint joinPoint) { + String methodName = joinPoint.getSignature().getName(); + Object[] args = joinPoint.getArgs(); + System.out.println("Logger-->前置通知,方法名称:" + methodName + ",参数:" + Arrays.toString(args)); + } + + /** + * 后置 @After():返回之前执行 + * + * @param joinPoint + */ + @After(value = "pointCut()") +// @After(value = "execution(* com.lxcecho.aop.annoaop.CalculatorImpl.*(..))") + public void afterMethod(JoinPoint joinPoint) { + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->后置通知,方法名称:" + methodName); + } + + /** + * 返回 @AfterReturning:正常返回执行,最后执行 + * + * @param joinPoint + * @param result + */ + @AfterReturning(value = "pointCut()", returning = "result") +// @AfterReturning(value = "execution(* com.lxcecho.aop.annoaop.CalculatorImpl.*(..))", returning = "result") + public void afterReturningMethod(JoinPoint joinPoint, Object result) { + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->返回通知,方法名称:" + methodName + ",返回结果:" + result); + } + + /** + * 异常 @AfterThrowing 获取到目标方法异常信息:目标方法出现异常,这个通知执行 + * + * @param joinPoint + * @param ex + */ + @AfterThrowing(value = "pointCut()", throwing = "ex") +// @AfterThrowing(value = "execution(* com.lxcecho.aop.annoaop.CalculatorImpl.*(..))", throwing = "ex") + public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) { + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->异常通知,方法名称:" + methodName + ",异常信息:" + ex); + } + + /** + * 环绕 @Around():连接到切入点开始执行,下一步进入前置通知,再下一步才是执行操作方法 + * + * @param joinPoint + * @return + */ + /*@Around("pointCut()") + @Around("execution(* com.lxcecho.aop.annoaop.CalculatorImpl.*(..))") + public Object aroundMethod(ProceedingJoinPoint joinPoint) { + String methodName = joinPoint.getSignature().getName(); + Object[] args = joinPoint.getArgs(); + String argString = Arrays.toString(args); + Object result = null; + try { + System.out.println("环绕通知==目标方法之前执行"); + // 调用目标方法 + result = joinPoint.proceed(); + System.out.println("环绕通知==目标方法返回值之后"); + } catch (Throwable throwable) { + throwable.printStackTrace(); + System.out.println("环绕通知==目标方法出现异常执行"); + } finally { + System.out.println("环绕通知==目标方法执行完毕执行"); + } + return result; + }*/ + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/SpringAopConfig.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/SpringAopConfig.java new file mode 100644 index 000000000000..4bfc1800ff3f --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/annoaop/SpringAopConfig.java @@ -0,0 +1,15 @@ +package com.lxcecho.aop.annoaop; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/24 + */ +@Configuration +@EnableAspectJAutoProxy +@ComponentScan("com.lxcecho.aop.annoaop") +public class SpringAopConfig { +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/Calculator.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/Calculator.java new file mode 100644 index 000000000000..912e33636a15 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/Calculator.java @@ -0,0 +1,16 @@ +package com.lxcecho.aop.example; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface Calculator { + + int add(int i, int j); + + int sub(int i, int j); + + int mul(int i, int j); + + int div(int i, int j); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorImpl.java new file mode 100644 index 000000000000..251253b72635 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorImpl.java @@ -0,0 +1,50 @@ +package com.lxcecho.aop.example; + +/** + * 基本实现类 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class CalculatorImpl implements Calculator { + + @Override + public int add(int i, int j) { + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int sub(int i, int j) { + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int div(int i, int j) { + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + return result; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorLogImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorLogImpl.java new file mode 100644 index 000000000000..79f78d3299b3 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorLogImpl.java @@ -0,0 +1,66 @@ +package com.lxcecho.aop.example; + +/** + * 带日志 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class CalculatorLogImpl implements Calculator { + + @Override + public int add(int i, int j) { + + System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] add 方法结束了,结果是:" + result); + + return result; + } + + @Override + public int sub(int i, int j) { + + System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j); + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] sub 方法结束了,结果是:" + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j); + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] mul 方法结束了,结果是:" + result); + + return result; + } + + @Override + public int div(int i, int j) { + + System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j); + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + System.out.println("[日志] div 方法结束了,结果是:" + result); + + return result; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorStaticProxy.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorStaticProxy.java new file mode 100644 index 000000000000..55bf433ba516 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/CalculatorStaticProxy.java @@ -0,0 +1,41 @@ +package com.lxcecho.aop.example; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class CalculatorStaticProxy implements Calculator{ + + //被代理目标对象传递过来 + private Calculator calculator; + public CalculatorStaticProxy(Calculator calculator) { + this.calculator = calculator; + } + + @Override + public int add(int i, int j) { + //输出日志 + System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j); + + //调用目标对象的方法实现核心业务 + int addResult = calculator.add(i, j); + + System.out.println("[日志] add 方法结束了,结果是:" + addResult); + return addResult; + } + + @Override + public int sub(int i, int j) { + return 0; + } + + @Override + public int mul(int i, int j) { + return 0; + } + + @Override + public int div(int i, int j) { + return 0; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/ProxyFactory.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/ProxyFactory.java new file mode 100644 index 000000000000..765dfc56620f --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/ProxyFactory.java @@ -0,0 +1,57 @@ +package com.lxcecho.aop.example; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.Arrays; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class ProxyFactory { + + // 目标对象 + private Object target; + public ProxyFactory(Object target) { + this.target = target; + } + + //返回代理对象 + public Object getProxy() { + /** + * Proxy.newProxyInstance()方法 + * 有三个参数 + * 第一个参数:ClassLoader: 加载动态生成代理类的来加载器 + * 第二个参数: Class[] interfaces:目录对象实现的所有接口的class类型数组 + * 第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程 + */ + //第一个参数:ClassLoader: 加载动态生成代理类的来加载器 + ClassLoader classLoader = target.getClass().getClassLoader(); + //第二个参数: Class[] interfaces:目录对象实现的所有接口的class类型数组 + Class[] interfaces = target.getClass().getInterfaces(); + //第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程 + InvocationHandler invocationHandler =new InvocationHandler() { + + //第一个参数:代理对象 + //第二个参数:需要重写目标对象的方法 + //第三个参数:method方法里面参数 + @Override + public Object invoke(Object proxy, + Method method, + Object[] args) throws Throwable { + + //方法调用之前输出 + System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args)); + + //调用目标的方法 + Object result = method.invoke(target, args); + + //方法调用之后输出 + System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result); + return result; + } + }; + return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/TestCal.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/TestCal.java new file mode 100644 index 000000000000..3d6dcaeb21d9 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/example/TestCal.java @@ -0,0 +1,12 @@ +package com.lxcecho.aop.example; + +public class TestCal { + + public static void main(String[] args) { + // 创建代理对象(动态) + ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl()); + Calculator proxy = (Calculator) proxyFactory.getProxy(); + // proxy.add(1,2); + proxy.mul(2, 4); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/Calculator.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/Calculator.java new file mode 100644 index 000000000000..81a981729bc2 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/Calculator.java @@ -0,0 +1,16 @@ +package com.lxcecho.aop.xmlaop; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface Calculator { + + int add(int i, int j); + + int sub(int i, int j); + + int mul(int i, int j); + + int div(int i, int j); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/CalculatorImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/CalculatorImpl.java new file mode 100644 index 000000000000..a2180cbcea5e --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/CalculatorImpl.java @@ -0,0 +1,55 @@ +package com.lxcecho.aop.xmlaop; + +import org.springframework.stereotype.Component; + +/** + * 基本实现类 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Component +public class CalculatorImpl implements Calculator { + + @Override + public int add(int i, int j) { + + int result = i + j; + + System.out.println("方法内部 result = " + result); + + //为了测试,模拟异常出现 + // int a = 1/0; + return result; + } + + @Override + public int sub(int i, int j) { + + int result = i - j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int mul(int i, int j) { + + int result = i * j; + + System.out.println("方法内部 result = " + result); + + return result; + } + + @Override + public int div(int i, int j) { + + int result = i / j; + + System.out.println("方法内部 result = " + result); + + return result; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/LogAspect.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/LogAspect.java new file mode 100644 index 000000000000..a0bef842d9c1 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/aop/xmlaop/LogAspect.java @@ -0,0 +1,71 @@ +package com.lxcecho.aop.xmlaop; + +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.*; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +/** + * 切面类 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Component // ioc 容器 +public class LogAspect { + + //前置通知 + public void beforeMethod(JoinPoint joinPoint) { + String methodName = joinPoint.getSignature().getName(); + Object[] args = joinPoint.getArgs(); + System.out.println("Logger-->前置通知,方法名称:" + methodName + ",参数:" + Arrays.toString(args)); + } + + // 后置通知 + public void afterMethod(JoinPoint joinPoint) { + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->后置通知,方法名称:" + methodName); + } + + // 返回通知,获取目标方法的返回值 + public void afterReturningMethod(JoinPoint joinPoint, Object result) { + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->返回通知,方法名称:" + methodName + ",返回结果:" + result); + } + + // 异常通知 获取到目标方法异常信息 + //目标方法出现异常,这个通知执行 + public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex) { + String methodName = joinPoint.getSignature().getName(); + System.out.println("Logger-->异常通知,方法名称:" + methodName + ",异常信息:" + ex); + } + + // 环绕通知 + public Object aroundMethod(ProceedingJoinPoint joinPoint) { + String methodName = joinPoint.getSignature().getName(); + Object[] args = joinPoint.getArgs(); + String argString = Arrays.toString(args); + Object result = null; + try { + System.out.println("环绕通知==目标方法之前执行"); + + //调用目标方法 + result = joinPoint.proceed(); + + System.out.println("环绕通知==目标方法返回值之后"); + } catch (Throwable throwable) { + throwable.printStackTrace(); + System.out.println("环绕通知==目标方法出现异常执行"); + } finally { + System.out.println("环绕通知==目标方法执行完毕执行"); + } + return result; + } + + //重用切入点表达式 + @Pointcut(value = "execution(* com.lxcecho.aop.xmlaop.CalculatorImpl.*(..))") + public void pointCut() { + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/i18n/javai18n/ResourceI18n.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/i18n/javai18n/ResourceI18n.java new file mode 100644 index 000000000000..f22b28cb1355 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/i18n/javai18n/ResourceI18n.java @@ -0,0 +1,21 @@ +package com.lxcecho.i18n.javai18n; + +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class ResourceI18n { + + public static void main(String[] args) { + ResourceBundle bundle1 = ResourceBundle.getBundle("messages", new Locale("zh", "CN")); + String value1 = bundle1.getString("test"); + System.out.println(value1); + + ResourceBundle bundle2 = ResourceBundle.getBundle("messages", new Locale("en", "GB")); + String value2 = bundle2.getString("test"); + System.out.println(value2); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/i18n/springi18n/ResourceI18n.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/i18n/springi18n/ResourceI18n.java new file mode 100644 index 000000000000..654471ad7ed4 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/i18n/springi18n/ResourceI18n.java @@ -0,0 +1,27 @@ +package com.lxcecho.i18n.springi18n; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +import java.util.Date; +import java.util.Locale; +import java.util.ResourceBundle; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class ResourceI18n { + + public static void main(String[] args) { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-i18n.xml"); + + // 传递动态参数,使用数组形式对应 {0} {1} 顺序 + Object[] objs = new Object[]{"lxcecho", new Date().toString()}; + + // www.lxcecho.com 为资源文件的 key 值, + //objs 为资源文件 value 值所需要的参数,Local.CHINA 为国际化为语言 + String str=context.getMessage("echo", objs, Locale.CHINA); + System.out.println(str); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/bean/User.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/bean/User.java new file mode 100644 index 000000000000..385fc75df8b7 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/bean/User.java @@ -0,0 +1,14 @@ +package com.lxcecho.ioc.iocanno.bean; + +import org.springframework.stereotype.Controller; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +//@Component(value = "user") // +//@Repository +//@Service +@Controller +public class User { +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/config/SpringConfig.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/config/SpringConfig.java new file mode 100644 index 000000000000..fceb82ecfc93 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/config/SpringConfig.java @@ -0,0 +1,13 @@ +package com.lxcecho.ioc.iocanno.config; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Configuration // 配置类 +@ComponentScan("com.lxcecho.ioc.iocanno") // 开启组件扫描 +public class SpringConfig { +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/controller/AutowiredBaseController.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/controller/AutowiredBaseController.java new file mode 100644 index 000000000000..ac16514ecf66 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/controller/AutowiredBaseController.java @@ -0,0 +1,50 @@ +package com.lxcecho.ioc.iocanno.controller; + +import com.lxcecho.ioc.iocanno.service.BaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Controller +public class AutowiredBaseController { + + // 注入 service + + // 第一种方式 属性注入 + @Autowired // 根据类型找到对应对象,完成注入 + private BaseService baseService; + + // 第二种方式 set方法注入 + /*private BaseService baseService; + @Autowired + public void setBaseService(BaseService baseService) { + this.baseService = baseService; + }*/ + + // 第三种方式 构造方法注入 + /*private BaseService baseService; + @Autowired + public AutowiredUserController(BaseService baseService) { + this.baseService = baseService; + }*/ + + // 第四种方式 形参上注入 + /*private BaseService baseService; + public AutowiredUserController(@Autowired BaseService baseService) { + this.baseService = baseService; + }*/ + + // 第五种方式 只有一个有参数构造函数,无注解 + /*private final BaseService baseService; + public AutowiredUserController(BaseService baseService) { + this.baseService = baseService; + }*/ + + public void add() { + System.out.println("AutowiredBaseController........"); + baseService.add(); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/controller/ResourceUserController.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/controller/ResourceUserController.java new file mode 100644 index 000000000000..05b7bea7d685 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/controller/ResourceUserController.java @@ -0,0 +1,28 @@ +package com.lxcecho.ioc.iocanno.controller; + +import com.lxcecho.ioc.iocanno.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Controller; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Controller +public class ResourceUserController { + + /** + * 根据名称进行注入 + */ + @Resource(name = "myUserService") + private UserService userService; + + // 根据类型配置 + /*@Resource + private UserService userService;*/ + + public void add() { + System.out.println("ResourceUserController........"); + userService.add(); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/BaseDao.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/BaseDao.java new file mode 100644 index 000000000000..449ebaf309a2 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/BaseDao.java @@ -0,0 +1,11 @@ +package com.lxcecho.ioc.iocanno.dao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface BaseDao { + + void add(); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/UserDao.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/UserDao.java new file mode 100644 index 000000000000..529972949599 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/UserDao.java @@ -0,0 +1,11 @@ +package com.lxcecho.ioc.iocanno.dao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface UserDao { + + void add(); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/impl/BaseDaoImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/impl/BaseDaoImpl.java new file mode 100644 index 000000000000..7567548938c1 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/impl/BaseDaoImpl.java @@ -0,0 +1,18 @@ +package com.lxcecho.ioc.iocanno.dao.impl; + +import com.lxcecho.ioc.iocanno.dao.BaseDao; +import org.springframework.stereotype.Repository; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Repository("redisDaoImpl") +public class BaseDaoImpl implements BaseDao { + + @Override + public void add() { + System.out.println("BaseDaoImpl redis........."); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/impl/UserDaoImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/impl/UserDaoImpl.java new file mode 100644 index 000000000000..63ef15b60ac4 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/dao/impl/UserDaoImpl.java @@ -0,0 +1,18 @@ +package com.lxcecho.ioc.iocanno.dao.impl; + +import com.lxcecho.ioc.iocanno.dao.UserDao; +import org.springframework.stereotype.Repository; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Repository +public class UserDaoImpl implements UserDao { + + @Override + public void add() { + System.out.println("UserDaoImpl........"); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/BaseService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/BaseService.java new file mode 100644 index 000000000000..01b29b276313 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/BaseService.java @@ -0,0 +1,11 @@ +package com.lxcecho.ioc.iocanno.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface BaseService { + + void add(); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/UserService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/UserService.java new file mode 100644 index 000000000000..d3fd45c01770 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/UserService.java @@ -0,0 +1,11 @@ +package com.lxcecho.ioc.iocanno.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface UserService { + + void add(); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/impl/AutowiredBaseServiceImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/impl/AutowiredBaseServiceImpl.java new file mode 100644 index 000000000000..c2c5e80927d5 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/impl/AutowiredBaseServiceImpl.java @@ -0,0 +1,61 @@ +package com.lxcecho.ioc.iocanno.service.impl; + +import com.lxcecho.ioc.iocanno.dao.BaseDao; +import com.lxcecho.ioc.iocanno.service.BaseService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Service +public class AutowiredBaseServiceImpl implements BaseService { + + // 注入 dao + + /** + * 第一种方式 属性注入 + */ + @Autowired //根 据类型找到对应对象,完成注入 + private BaseDao baseDao; + + /** + * 第二种方式 set 方法注入 + */ + /*private BaseDao baseDao; + @Autowired + public void setBaseDao(BaseDao baseDao) { + this.baseDao = baseDao; + }*/ + + /** + * 第三种方式 构造方法注入 + */ + /*private BaseDao baseDao; + @Autowired + public AutowiredBaseServiceImpl(BaseDao baseDao) { + this.baseDao = baseDao; + }*/ + + /** + * 第四种方式 形参上注入 + */ + /*private BaseDao baseDao; + public AutowiredBaseServiceImpl(@Autowired BaseDao baseDao) { + this.baseDao = baseDao; + }*/ + + /** + * 最后方式: 两个注解,根据名称注入 + */ + /*@Autowired + @Qualifier(value = "redisDaoImpl") + private BaseDao baseDao;*/ + + @Override + public void add() { + System.out.println("AutowiredBaseServiceImpl....."); + baseDao.add(); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/impl/ResourceUserServiceImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/impl/ResourceUserServiceImpl.java new file mode 100644 index 000000000000..486f31b33343 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocanno/service/impl/ResourceUserServiceImpl.java @@ -0,0 +1,26 @@ +package com.lxcecho.ioc.iocanno.service.impl; + +import com.lxcecho.ioc.iocanno.dao.UserDao; +import com.lxcecho.ioc.iocanno.service.UserService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +@Service("myUserService") +public class ResourceUserServiceImpl implements UserService { + + /** + * 不 指定名称,根据属性名称进行注入 + */ + @Resource + private UserDao myUserDao; + + @Override + public void add() { + System.out.println("ResourceUserServiceImpl....."); + myUserDao.add(); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Book.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Book.java new file mode 100644 index 000000000000..f1fb8e03feda --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Book.java @@ -0,0 +1,69 @@ +package com.lxcecho.ioc.iocxml.bean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Book { + + private String bname; + + private String author; + + private String others; + + public Book() { + System.out.println("无参数构造执行了..."); + } + + /** + * 有参数构造方法 + * + * @param bname + * @param author + */ + public Book(String bname, String author) { + System.out.println("有参数构造执行了..."); + this.bname = bname; + this.author = author; + } + + /** + * 生成 set 方法 + * + * @return + */ + public String getBname() { + return bname; + } + + public String getOthers() { + return others; + } + + public void setOthers(String others) { + this.others = others; + } + + public void setBname(String bname) { + this.bname = bname; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + @Override + public String toString() { + return "Book{" + + "bname='" + bname + '\'' + + ", author='" + author + '\'' + + ", others='" + others + '\'' + + '}'; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Dept.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Dept.java new file mode 100644 index 000000000000..9e58d33c6b5c --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Dept.java @@ -0,0 +1,42 @@ +package com.lxcecho.ioc.iocxml.bean; + +import java.util.List; + +/** + * 部门类 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Dept { + + /** + * 一个部门有很多员工 + */ + private List empList; + + private String dname; + + public String getDname() { + return dname; + } + + public void setDname(String dname) { + this.dname = dname; + } + + public List getEmpList() { + return empList; + } + + public void setEmpList(List empList) { + this.empList = empList; + } + + public void info() { + System.out.println("部门名称:"+dname); + for (Emp emp:empList) { + System.out.println(emp.getEname()); + } + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Emp.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Emp.java new file mode 100644 index 000000000000..83685f0fa174 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Emp.java @@ -0,0 +1,71 @@ +package com.lxcecho.ioc.iocxml.bean; + +import java.util.Arrays; + +/** + * 员工类 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Emp { + + /** + * 对象类型属性:员工属于某个部门 + */ + private Dept dept; + + /** + * 员工名称 + */ + private String ename; + + /** + * 员工年龄 + */ + private Integer age; + + /** + * 爱好 + */ + private String[] loves; + + public void work() { + System.out.println(ename+"emp work....."+age); + dept.info(); + System.out.println(Arrays.toString(loves)); + } + + public String[] getLoves() { + return loves; + } + + public void setLoves(String[] loves) { + this.loves = loves; + } + + public Dept getDept() { + return dept; + } + + public void setDept(Dept dept) { + this.dept = dept; + } + + public String getEname() { + return ename; + } + + public void setEname(String ename) { + this.ename = ename; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Lesson.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Lesson.java new file mode 100644 index 000000000000..54f9bdeb664d --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Lesson.java @@ -0,0 +1,25 @@ +package com.lxcecho.ioc.iocxml.bean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Lesson { + + private String lessonName; + + public String getLessonName() { + return lessonName; + } + + public void setLessonName(String lessonName) { + this.lessonName = lessonName; + } + + @Override + public String toString() { + return "Lesson{" + + "lessonName='" + lessonName + '\'' + + '}'; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/MyBeanPost.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/MyBeanPost.java new file mode 100644 index 000000000000..aefa064ce6fd --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/MyBeanPost.java @@ -0,0 +1,26 @@ +package com.lxcecho.ioc.iocxml.bean; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class MyBeanPost implements BeanPostProcessor { + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + System.out.println("3 bean 后置处理器,初始化之前执行"); + System.out.println(beanName+"::"+bean); + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + System.out.println("5 bean 后置处理器,初始化之后执行"); + System.out.println(beanName+"::"+bean); + return bean; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/MyFactoryBean.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/MyFactoryBean.java new file mode 100644 index 000000000000..4bf0cfff0268 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/MyFactoryBean.java @@ -0,0 +1,19 @@ +package com.lxcecho.ioc.iocxml.bean; + +import org.springframework.beans.factory.FactoryBean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class MyFactoryBean implements FactoryBean { + @Override + public User getObject() throws Exception { + return new User(); + } + + @Override + public Class getObjectType() { + return User.class; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Orders.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Orders.java new file mode 100644 index 000000000000..6d2323ac2122 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Orders.java @@ -0,0 +1,9 @@ +package com.lxcecho.ioc.iocxml.bean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Orders { + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Student.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Student.java new file mode 100644 index 000000000000..8d55ca10e268 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Student.java @@ -0,0 +1,58 @@ +package com.lxcecho.ioc.iocxml.bean; + +import java.util.List; +import java.util.Map; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Student { + + private List lessonList; + + private Map teacherMap; + + private String sid; + + private String sname; + + public void run() { + System.out.println("学生编号: "+sid+ " 学生名称:"+sname); + System.out.println(teacherMap); + System.out.println(lessonList); + } + + public List getLessonList() { + return lessonList; + } + + public void setLessonList(List lessonList) { + this.lessonList = lessonList; + } + + public Map getTeacherMap() { + return teacherMap; + } + + public void setTeacherMap(Map teacherMap) { + this.teacherMap = teacherMap; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + + public String getSname() { + return sname; + } + + public void setSname(String sname) { + this.sname = sname; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Teacher.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Teacher.java new file mode 100644 index 000000000000..a5b7f4d64e69 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/Teacher.java @@ -0,0 +1,36 @@ +package com.lxcecho.ioc.iocxml.bean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class Teacher { + + private String teacherId; + + private String teacherName; + + public String getTeacherId() { + return teacherId; + } + + public void setTeacherId(String teacherId) { + this.teacherId = teacherId; + } + + public String getTeacherName() { + return teacherName; + } + + public void setTeacherName(String teacherName) { + this.teacherName = teacherName; + } + + @Override + public String toString() { + return "Teacher{" + + "teacherId='" + teacherId + '\'' + + ", teacherName='" + teacherName + '\'' + + '}'; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/User.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/User.java new file mode 100644 index 000000000000..3f8f184bfa18 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/bean/User.java @@ -0,0 +1,45 @@ +package com.lxcecho.ioc.iocxml.bean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class User { + + private String name; + + private Integer age; + + public void run() { + System.out.println("run......"); + } + + //无参数构造 + public User() { + System.out.println("1 bean 对象创建,调用无参数构造"); + } + + public void add() { + System.out.println("add....."); + } + + public void setName(String name) { + System.out.println("2 给 bean 对象设置属性值"); + this.name = name; + } + + /** + * 初始化的方法 + */ + public void initMethod() { + System.out.println("4 bean 对象初始化,调用指定的初始化的方法"); + } + + /** + * 销毁的方法 + */ + public void destroyMethod() { + System.out.println("7 bean 对象销毁,调用指定的销毁的方法"); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/controller/UserController.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/controller/UserController.java new file mode 100644 index 000000000000..64a1e24ad30e --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/controller/UserController.java @@ -0,0 +1,24 @@ +package com.lxcecho.ioc.iocxml.controller; + +import com.lxcecho.ioc.iocxml.service.UserService; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class UserController { + + private UserService userService; + + public void setUserService(UserService userService) { + this.userService = userService; + } + + public void addUser() { + System.out.println("controller 方法执行了..."); + // 调用 service 的方法 + userService.addUserService(); +// UserService userService = new UserServiceImpl(); +// userService.addUserService(); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/UserDao.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/UserDao.java new file mode 100644 index 000000000000..630ce1fe1c64 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/UserDao.java @@ -0,0 +1,13 @@ +package com.lxcecho.ioc.iocxml.dao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface UserDao { + + void run(); + + void addUserDao(); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/impl/PersonDaoImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/impl/PersonDaoImpl.java new file mode 100644 index 000000000000..63fcf6b43ef7 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/impl/PersonDaoImpl.java @@ -0,0 +1,21 @@ +package com.lxcecho.ioc.iocxml.dao.impl; + +import com.lxcecho.ioc.iocxml.dao.UserDao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class PersonDaoImpl implements UserDao { + + @Override + public void run() { + System.out.println("person run...."); + } + + @Override + public void addUserDao() { + System.out.println("userDao 方法执行了..."); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/impl/UserDaoImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/impl/UserDaoImpl.java new file mode 100644 index 000000000000..fed4780f1318 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/dao/impl/UserDaoImpl.java @@ -0,0 +1,21 @@ +package com.lxcecho.ioc.iocxml.dao.impl; + +import com.lxcecho.ioc.iocxml.dao.UserDao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class UserDaoImpl implements UserDao { + + @Override + public void run() { + System.out.println("run....."); + } + + @Override + public void addUserDao() { + System.out.println("userDao 方法执行了..."); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/service/UserService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/service/UserService.java new file mode 100644 index 000000000000..99a9707459d4 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/service/UserService.java @@ -0,0 +1,11 @@ +package com.lxcecho.ioc.iocxml.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public interface UserService { + + void addUserService(); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/service/impl/UserServiceImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/service/impl/UserServiceImpl.java new file mode 100644 index 000000000000..6889b79c5f78 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/ioc/iocxml/service/impl/UserServiceImpl.java @@ -0,0 +1,26 @@ +package com.lxcecho.ioc.iocxml.service.impl; + +import com.lxcecho.ioc.iocxml.dao.UserDao; +import com.lxcecho.ioc.iocxml.service.UserService; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class UserServiceImpl implements UserService { + + private UserDao userDao; + + public void setUserDao(UserDao userDao) { + this.userDao = userDao; + } + + @Override + public void addUserService() { + System.out.println("userService 方法执行了..."); + userDao.addUserDao(); +// UserDao userDao = new UserDaoImpl(); +// userDao.addUserDao(); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/config/TxConfig.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/config/TxConfig.java new file mode 100644 index 000000000000..0f2ff8f62b76 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/config/TxConfig.java @@ -0,0 +1,45 @@ +package com.lxcecho.jdbctx.annotx.config; + +import com.alibaba.druid.pool.DruidDataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +import javax.sql.DataSource; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Configuration // 配置类 +@ComponentScan("com.lxcecho.jdbctx.annotx") +@EnableTransactionManagement // 开启事务管理 +public class TxConfig { + + @Bean + public DataSource getDataSource() { + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); + dataSource.setUsername("root"); + dataSource.setPassword("Amecho00#"); + dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true"); + return dataSource; + } + + @Bean(name = "jdbcTemplate") + public JdbcTemplate getJdbcTemplate(DataSource dataSource) { + JdbcTemplate jdbcTemplate = new JdbcTemplate(); + jdbcTemplate.setDataSource(dataSource); + return jdbcTemplate; + } + + @Bean + public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) { + DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); + dataSourceTransactionManager.setDataSource(dataSource); + return dataSourceTransactionManager; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/controller/BookController.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/controller/BookController.java new file mode 100644 index 000000000000..31506f1c0a02 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/controller/BookController.java @@ -0,0 +1,36 @@ +package com.lxcecho.jdbctx.annotx.controller; + +import com.lxcecho.jdbctx.annotx.service.BookService; +import com.lxcecho.jdbctx.annotx.service.CheckoutService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Controller +public class BookController { + + @Autowired + private BookService bookService; + + /** + * 买书的方法:图书 id 和用户 id + * + * @param bookId 書本 ID + * @param userId 用戶 ID + */ + public void buyBook(Integer bookId, Integer userId) { + // 调用 service 方法 + bookService.buyBook(bookId, userId); + } + + @Autowired + private CheckoutService checkoutService; + + public void checkout(Integer[] bookIds, Integer userId) { + checkoutService.checkout(bookIds, userId); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/dao/BookDao.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/dao/BookDao.java new file mode 100644 index 000000000000..84868e98ae9d --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/dao/BookDao.java @@ -0,0 +1,33 @@ +package com.lxcecho.jdbctx.annotx.dao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface BookDao { + + /** + * 根据图书 id 查询图书价格 + * + * @param bookId + * @return + */ + Integer getBookPriceByBookId(Integer bookId); + + /** + * 更新图书表库存量 -1 + * + * @param bookId + */ + void updateStock(Integer bookId); + + /** + * 更新用户表用户余额 -图书价格 + * + * @param userId + * @param price + */ + void updateUserBalance(Integer userId, Integer price); + + Integer getUserBalance(Integer userId); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/dao/impl/BookDaoImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/dao/impl/BookDaoImpl.java new file mode 100644 index 000000000000..f3b89bb40390 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/dao/impl/BookDaoImpl.java @@ -0,0 +1,58 @@ +package com.lxcecho.jdbctx.annotx.dao.impl; + +import com.lxcecho.jdbctx.annotx.dao.BookDao; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Repository +public class BookDaoImpl implements BookDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + /** + * 根据图书 id 查询价格 + * + * @param bookId + * @return + */ + @Override + public Integer getBookPriceByBookId(Integer bookId) { + String sql = "select price from t_book where book_id=?"; + return jdbcTemplate.queryForObject(sql, Integer.class, bookId); + } + + /** + * 更新库存 + * + * @param bookId + */ + @Override + public void updateStock(Integer bookId) { + String sql = "update t_book set stock=stock-1 where book_id=?"; + jdbcTemplate.update(sql, bookId); + } + + /** + * 更新用户表用户余额 -图书价格 + * + * @param userId + * @param price + */ + @Override + public void updateUserBalance(Integer userId, Integer price) { + String sql = "update t_user set balance=balance-? where user_id=?"; + jdbcTemplate.update(sql, price, userId); + } + + @Override + public Integer getUserBalance(Integer userId) { + String sql = "select balance from t_user where user_id=?"; + return jdbcTemplate.queryForObject(sql, Integer.class, userId); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/BookService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/BookService.java new file mode 100644 index 000000000000..6ddc9a21b4bf --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/BookService.java @@ -0,0 +1,16 @@ +package com.lxcecho.jdbctx.annotx.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface BookService { + + /** + * 买书的方法:图书 id 和用户 id + * + * @param bookId + * @param userId + */ + void buyBook(Integer bookId, Integer userId); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/CheckoutService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/CheckoutService.java new file mode 100644 index 000000000000..6aee62cc6595 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/CheckoutService.java @@ -0,0 +1,17 @@ +package com.lxcecho.jdbctx.annotx.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface CheckoutService { + + /** + * 买多本书的方法 + * + * @param bookIds + * @param userId + */ + void checkout(Integer[] bookIds, Integer userId); + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/impl/BookServiceImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/impl/BookServiceImpl.java new file mode 100644 index 000000000000..ed468bd21e71 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/impl/BookServiceImpl.java @@ -0,0 +1,70 @@ +package com.lxcecho.jdbctx.annotx.service.impl; + +import com.lxcecho.jdbctx.annotx.dao.BookDao; +import com.lxcecho.jdbctx.annotx.service.BookService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +import java.util.concurrent.TimeUnit; + +/** + * 因为 service 层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在 service 层处理 + *

+ * 1.@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法 buyBook() 在 checkout() 中被调用, + * checkout() 上有事务注解,因此在此事务中执行。所购买的两本图书的价格为 80 和 50,而用户的余额为 100,因此在购买第二本图书时余额不足失败,导致整个 checkout() 回滚,即只要有一本书买不了,就都买不了。 + *

+ * 2.@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在 buyBook() 的事务中执行,因此第一本图书购买成功, + * 事务结束,第二本图书购买失败,只在第二次的 buyBook() 中回滚,购买第一本图书不受影响,即能买几本就买几本。 + *

+ * 【注意】:@Transactional 注解也可以加在接口上,但只有在设置了基于接口的代理时才会生效,因为注解不能继承。所以该注解最好是加在类的实现上。 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +//@Transactional(propagation = Propagation.REQUIRED) // 标识的类上,则会影响类中所有的 public 方法 +@Transactional(propagation = Propagation.REQUIRES_NEW) // 标识的类上,则会影响类中所有的 public 方法 +@Service +public class BookServiceImpl implements BookService { + + @Autowired + private BookDao bookDao; + + /** + * 买书的方法:图书 id 和用户 id + * + * @param bookId + * @param userId + */ +// @Transactional(readOnly = true) // 标识在方法上,则只会影响 public 方法 +// @Transactional(timeout = 3) // 标识在方法上,则只会影响 public 方法 +// @Transactional(noRollbackFor = ArithmeticException.class) // 标识在方法上,则只会影响 public 方法 +// @Transactional(noRollbackForClassName = "java.lang.ArithmeticException") +// @Transactional(rollbackFor = Exception.class) // Spring 事务只回滚运行时异常和 Error + @Override + public void buyBook(Integer bookId, Integer userId) { + // TODO 模拟超时效果 + /*try { + TimeUnit.SECONDS.sleep(5); + } catch (InterruptedException e) { + throw new RuntimeException(e); + }*/ + + // 根据图书 id 查询图书价格 + Integer price = bookDao.getBookPriceByBookId(bookId); + + System.out.println(bookId + "'s price=" + price); + + // 更新图书表库存量 -1 + bookDao.updateStock(bookId); + + // 更新用户表用户余额 -图书价格 + bookDao.updateUserBalance(userId, price); + + System.out.println("balance=" + bookDao.getUserBalance(userId)); + + // 测试 rollback 属性 +// System.out.println(1 / 0); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/impl/CheckoutServiceImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/impl/CheckoutServiceImpl.java new file mode 100644 index 000000000000..a6f0dfa5f4fc --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/service/impl/CheckoutServiceImpl.java @@ -0,0 +1,36 @@ +package com.lxcecho.jdbctx.annotx.service.impl; + +import com.lxcecho.jdbctx.annotx.service.BookService; +import com.lxcecho.jdbctx.annotx.service.CheckoutService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Service +public class CheckoutServiceImpl implements CheckoutService { + + /** + * 注入 bookService + */ + @Autowired + private BookService bookService; + + /** + * 买多本书的方法 + * + * @param bookIds + * @param userId + */ + @Transactional + @Override + public void checkout(Integer[] bookIds, Integer userId) { + for (Integer bookId : bookIds) { + // 调用 service 的方法 + bookService.buyBook(bookId, userId); + } + } +} diff --git "a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/\345\272\224\347\224\250\346\241\210\344\276\213.txt" "b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/\345\272\224\347\224\250\346\241\210\344\276\213.txt" new file mode 100644 index 000000000000..c6d5435a0607 --- /dev/null +++ "b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/annotx/\345\272\224\347\224\250\346\241\210\344\276\213.txt" @@ -0,0 +1,20 @@ +线上日志数据丢失 + +目标:通过传播属性解决业务中日志丢失问题 + +用户---->下订单【记录日志】---->付款 + +日志插入失败,使用默认的传播属性【REQUIRED】和隔离级别【数据库默认的隔离级别,MySQL 默认的是可重复读】 +用户信息插入,调用日志插入 + +总结: +1. 现象 + 用户插入数据的时候;调用 LOG 的 service 插入;如果出现异常,两者全部回滚; + 也就是用户插入失败,日志插入失败 +2. 需求 + 正常的业务情况,都是在事务失败的时候;同时会要求日志也要插入成功 +3. 过程 + 目前,用户和日志的 Service 使用的都是默认的传播属性和隔离级别 +4. 改进 + 将日志的传播属性修改成 Propagation.NOT_SUPPORTED【如果当前存在事务,就挂起当前事务】 + diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/jdbc.sql b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/jdbc.sql new file mode 100644 index 000000000000..81b36a4f0243 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/jdbc.sql @@ -0,0 +1,14 @@ +CREATE +DATABASE `spring`; + +use +`spring`; + +CREATE TABLE `t_emp` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(20) DEFAULT NULL COMMENT '姓名', + `age` int(11) DEFAULT NULL COMMENT '年龄', + `sex` varchar(2) DEFAULT NULL COMMENT '性别', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/jdbc/Emp.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/jdbc/Emp.java new file mode 100644 index 000000000000..2626b76a6386 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/jdbc/Emp.java @@ -0,0 +1,58 @@ +package com.lxcecho.jdbctx.jdbc; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class Emp { + + private Integer id; + + private String name; + + private Integer age; + + private String sex; + + @Override + public String toString() { + return "Emp{" + + "id=" + id + + ", name='" + name + '\'' + + ", age=" + age + + ", sex='" + sex + '\'' + + '}'; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public String getSex() { + return sex; + } + + public void setSex(String sex) { + this.sex = sex; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/tx.sql b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/tx.sql new file mode 100644 index 000000000000..25da58cd9f21 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/tx.sql @@ -0,0 +1,23 @@ +CREATE TABLE `t_book` +( + `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称', + `price` int(11) DEFAULT NULL COMMENT '价格', + `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)', + PRIMARY KEY (`book_id`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; + +insert into `t_book`(`book_id`, `book_name`, `price`, `stock`) +values (1, '斗破苍穹', 80, 100), + (2, '斗罗大陆', 50, 100); + +CREATE TABLE `t_user` +( + `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', + `username` varchar(20) DEFAULT NULL COMMENT '用户名', + `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)', + PRIMARY KEY (`user_id`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +insert into `t_user`(`user_id`, `username`, `balance`) +values (1, 'admin', 50); \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/controller/BookController.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/controller/BookController.java new file mode 100644 index 000000000000..79a4531d41d9 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/controller/BookController.java @@ -0,0 +1,28 @@ +package com.lxcecho.jdbctx.xmltx.controller; + +import com.lxcecho.jdbctx.xmltx.service.BookService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Controller +public class BookController { + + @Autowired + private BookService bookService; + + /** + * 买书的方法:图书 id 和用户 id + * + * @param bookId + * @param userId + */ + public void buyBook(Integer bookId, Integer userId) { + // 调用 service 方法 + bookService.buyBook(bookId, userId); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/dao/BookDao.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/dao/BookDao.java new file mode 100644 index 000000000000..b3ff4cb1634b --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/dao/BookDao.java @@ -0,0 +1,31 @@ +package com.lxcecho.jdbctx.xmltx.dao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface BookDao { + + /** + * 根据图书 id 查询图书价格 + * + * @param bookId + * @return + */ + Integer getBookPriceByBookId(Integer bookId); + + /** + * 更新图书表库存量 -1 + * + * @param bookId + */ + void updateStock(Integer bookId); + + /** + * 更新用户表用户余额 -图书价格 + * + * @param userId + * @param price + */ + void updateUserBalance(Integer userId, Integer price); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/dao/impl/BookDaoImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/dao/impl/BookDaoImpl.java new file mode 100644 index 000000000000..f6df27eac008 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/dao/impl/BookDaoImpl.java @@ -0,0 +1,53 @@ +package com.lxcecho.jdbctx.xmltx.dao.impl; + +import com.lxcecho.jdbctx.xmltx.dao.BookDao; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Repository +public class BookDaoImpl implements BookDao { + + @Autowired + private JdbcTemplate jdbcTemplate; + + /** + * 根据图书 id 查询价格 + * + * @param bookId + * @return + */ + @Override + public Integer getBookPriceByBookId(Integer bookId) { + String sql = "select price from t_book where book_id=?"; + Integer price = jdbcTemplate.queryForObject(sql, Integer.class, bookId); + return price; + } + + /** + * 更新库存 + * + * @param bookId + */ + @Override + public void updateStock(Integer bookId) { + String sql = "update t_book set stock=stock-1 where book_id=?"; + jdbcTemplate.update(sql, bookId); + } + + /** + * 更新用户表用户余额 -图书价格 + * + * @param userId + * @param price + */ + @Override + public void updateUserBalance(Integer userId, Integer price) { + String sql = "update t_user set balance=balance-? where user_id=?"; + jdbcTemplate.update(sql, price, userId); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/service/BookService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/service/BookService.java new file mode 100644 index 000000000000..59192d6e6543 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/service/BookService.java @@ -0,0 +1,11 @@ +package com.lxcecho.jdbctx.xmltx.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface BookService { + + //买书的方法:图书id和用户id + void buyBook(Integer bookId, Integer userId); +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/service/impl/BookServiceImpl.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/service/impl/BookServiceImpl.java new file mode 100644 index 000000000000..2dededf1af8f --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/jdbctx/xmltx/service/impl/BookServiceImpl.java @@ -0,0 +1,38 @@ +package com.lxcecho.jdbctx.xmltx.service.impl; + +import com.lxcecho.jdbctx.xmltx.dao.BookDao; +import com.lxcecho.jdbctx.xmltx.service.BookService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Service +public class BookServiceImpl implements BookService { + + @Autowired + private BookDao bookDao; + + /** + * 买书的方法:图书 id 和用户 id + * + * @param bookId + * @param userId + */ + @Override + public void buyBook(Integer bookId, Integer userId) { + + // 根据图书 id 查询图书价格 + Integer price = bookDao.getBookPriceByBookId(bookId); + + // 更新图书表库存量 -1 + bookDao.updateStock(bookId); + + // 更新用户表用户余额 -图书价格 + bookDao.updateUserBalance(userId, price); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/junit/JUnitUser.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/junit/JUnitUser.java new file mode 100644 index 000000000000..480954b0c328 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/junit/JUnitUser.java @@ -0,0 +1,16 @@ +package com.lxcecho.junit; + +import org.springframework.stereotype.Component; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/12 + */ +@Component +public class JUnitUser { + + public void run() { + System.out.println("user....."); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/di/ResourceBean.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/di/ResourceBean.java new file mode 100644 index 000000000000..28e4e3bd2177 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/di/ResourceBean.java @@ -0,0 +1,25 @@ +package com.lxcecho.resources.di; + +import org.springframework.core.io.Resource; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class ResourceBean { + + private Resource resource; + + public void setResource(Resource resource) { + this.resource = resource; + } + + public Resource getResource() { + return resource; + } + + public void parse() { + System.out.println(resource.getFilename()); + System.out.println(resource.getDescription()); + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/prefix/User.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/prefix/User.java new file mode 100644 index 000000000000..26b6e7b5f84b --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/prefix/User.java @@ -0,0 +1,8 @@ +package com.lxcecho.resources.prefix; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class User { +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/ClassPathResourceDemo.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/ClassPathResourceDemo.java new file mode 100644 index 000000000000..1a14279dcfc9 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/ClassPathResourceDemo.java @@ -0,0 +1,40 @@ +package com.lxcecho.resources.resource; + +import org.springframework.core.io.ClassPathResource; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 访问类路径下资源 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class ClassPathResourceDemo { + + /** + * ClassPathResource 实例可使用 ClassPathResource 构造器显式地创建,但更多的时候它都是隐式地创建的。 + * 当执行 Spring 的某个方法时,该方法接受一个代表资源路径的字符串参数,当 Spring 识别该字符串参数中包含 classpath: 前缀后,系统会自动创建 ClassPathResource 对象。 + * + * @param path + */ + public static void loadClasspathResource(String path) { + // 创建对象 ClassPathResource + ClassPathResource resource = new ClassPathResource(path); + + System.out.println(resource.getFilename()); + System.out.println(resource.getDescription()); + // 获取文件内容 + try { + InputStream in = resource.getInputStream(); + byte[] b = new byte[1024]; + while (in.read(b) != -1) { + System.out.println(new String(b)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/FileSystemResourceDemo.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/FileSystemResourceDemo.java new file mode 100644 index 000000000000..11096d4e08cc --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/FileSystemResourceDemo.java @@ -0,0 +1,44 @@ +package com.lxcecho.resources.resource; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.io.IOException; +import java.io.InputStream; + +/** + * 访问系统资源 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class FileSystemResourceDemo { + + /** + * FileSystemResource 实例可使用 FileSystemResource 构造器显示地创建,但更多的时候它都是隐式创建。 + * 执行 Spring 的某个方法时,该方法接受一个代表资源路径的字符串参数,当 Spring 识别该字符串参数中包含 file: 前缀后,系统将会自动创建 FileSystemResource 对象。 + * + * @param path 相对路径/绝对路径 + */ + public static void loadFileResource(String path) { + // 创建对象 + FileSystemResource resource = new FileSystemResource(path); + // 获取文件名 + System.out.println(resource.getFilename()); + // 获取文件描述 + System.out.println(resource.getDescription()); + //获取文件内容 + try { + InputStream in = resource.getInputStream(); + byte[] b = new byte[1024]; + while (in.read(b) != -1) { + System.out.println(new String(b)); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/UrlResourceDemo.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/UrlResourceDemo.java new file mode 100644 index 000000000000..b003ff2093e4 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resource/UrlResourceDemo.java @@ -0,0 +1,35 @@ +package com.lxcecho.resources.resource; + +import org.springframework.core.io.UrlResource; + +import java.net.MalformedURLException; + +/** + * 演示 UrlResource 访问网络资源 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class UrlResourceDemo { + + /** + * 访问前缀 http、file + * + * @param path 路径 + */ + public static void loadUrlResource(String path) { + + try { + // 创建 Resource 实现类的对象 UrlResource + UrlResource url = new UrlResource(path); + + // 获取资源信息 + System.out.println(url.getFilename()); + System.out.println(url.getURI()); + System.out.println(url.getDescription()); + System.out.println(url.getInputStream().read()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resourceloaderaware/TestResourceLoaderAwareBean.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resourceloaderaware/TestResourceLoaderAwareBean.java new file mode 100644 index 000000000000..b2913476fbc5 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/resources/resourceloaderaware/TestResourceLoaderAwareBean.java @@ -0,0 +1,34 @@ +package com.lxcecho.resources.resourceloaderaware; + +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.ResourceLoader; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class TestResourceLoaderAwareBean implements ResourceLoaderAware { + + private ResourceLoader resourceLoader; + + /** + * 实现 ResourceLoaderAware 接口必须实现的方法 + * 如果把该 Bean 部署在 Spring 容器中,该方法将会有 Spring 容器负责调用。Spring 容器调用该方法时,Spring 会将自身作为参数传给该方法。 + * + * @param resourceLoader the ResourceLoader object to be used by this object + */ + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + + /** + * 返回 ResourceLoader 对象的应用 + * + * @return + */ + public ResourceLoader getResourceLoader() { + return this.resourceLoader; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/four/CannotBlank.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/four/CannotBlank.java new file mode 100644 index 000000000000..e709075a0180 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/four/CannotBlank.java @@ -0,0 +1,50 @@ +package com.lxcecho.validator.four; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import jakarta.validation.constraints.NotNull; + +import java.lang.annotation.*; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = {CannotBlankValidation.class}) +public @interface CannotBlank { + + /** + * 默认错误信息 + * + * @return + */ + String message() default "不能包含空格"; + + /** + * 分组 + * + * @return + */ + Class[] groups() default {}; + + /** + * 负载 + * + * @return + */ + Class[] payload() default {}; + + /** + * 指定多个时使用 + */ + @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) + @Retention(RetentionPolicy.RUNTIME) + @Documented + public @interface List { + CannotBlank[] value(); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/four/CannotBlankValidation.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/four/CannotBlankValidation.java new file mode 100644 index 000000000000..5e27d2c4b699 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/four/CannotBlankValidation.java @@ -0,0 +1,27 @@ +package com.lxcecho.validator.four; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class CannotBlankValidation implements ConstraintValidator { + + @Override + public boolean isValid(String value, ConstraintValidatorContext context) { + // null 时不进行校验 + if (value != null && value.contains(" ")) { + // 获取默认提示信息 + String defaultConstraintMessageTemplate = context.getDefaultConstraintMessageTemplate(); + System.out.println("default message :" + defaultConstraintMessageTemplate); + // 禁用默认提示信息 + context.disableDefaultConstraintViolation(); + // 设置提示语 + context.buildConstraintViolationWithTemplate("can not contains blank").addConstraintViolation(); + return false; + } + return false; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/one/Person.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/one/Person.java new file mode 100644 index 000000000000..0eeead4b7d15 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/one/Person.java @@ -0,0 +1,27 @@ +package com.lxcecho.validator.one; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class Person { + + private String name; + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/one/PersonValidator.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/one/PersonValidator.java new file mode 100644 index 000000000000..70307509ec70 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/one/PersonValidator.java @@ -0,0 +1,37 @@ +package com.lxcecho.validator.one; + +import org.springframework.validation.Errors; +import org.springframework.validation.ValidationUtils; +import org.springframework.validation.Validator; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class PersonValidator implements Validator { + + @Override + public boolean supports(Class clazz) { + return Person.class.equals(clazz); + } + + /** + * 校验规则 + * + * @param target the object that is to be validated + * @param errors contextual state about the validation process + */ + @Override + public void validate(Object target, Errors errors) { + // name 不能为空 + ValidationUtils.rejectIfEmpty(errors, "name", "name.empty", "name is null"); + + // age 不能小于 0,不能大于 200 + Person p = (Person) target; + if (p.getAge() < 0) { + errors.rejectValue("age", "age.value.error", "age < 0"); + } else if (p.getAge() > 200) { + errors.rejectValue("age", "age.value.error.old", "age > 200"); + } + } +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/MyService.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/MyService.java new file mode 100644 index 000000000000..bf34bbb29e95 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/MyService.java @@ -0,0 +1,20 @@ +package com.lxcecho.validator.three; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Service +@Validated +public class MyService { + + public String testMethod(@NotNull @Valid User user) { + return user.toString(); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/User.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/User.java new file mode 100644 index 000000000000..8ab248fc486c --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/User.java @@ -0,0 +1,58 @@ +package com.lxcecho.validator.three; + +import com.lxcecho.validator.four.CannotBlank; +import jakarta.validation.constraints.*; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class User { + + @NotNull + private String name; + + @Min(0) + @Max(150) + private int age; + + @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$", message = "手机号码格式错误") + @NotBlank(message = "手机号码不能为空") + private String phone; + + @CannotBlank + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/ValidationConfig.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/ValidationConfig.java new file mode 100644 index 000000000000..d68744ba2ee4 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/three/ValidationConfig.java @@ -0,0 +1,23 @@ +package com.lxcecho.validator.three; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.MethodValidationPostProcessor; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Configuration +@ComponentScan("com.lxcecho.validator.three") +public class ValidationConfig { + + @Bean + public MethodValidationPostProcessor validationPostProcessor() { + return new MethodValidationPostProcessor(); + } + +} + + diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/MyValidation1.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/MyValidation1.java new file mode 100644 index 000000000000..f5a1afad003b --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/MyValidation1.java @@ -0,0 +1,25 @@ +package com.lxcecho.validator.two; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Service +public class MyValidation1 { + + @Autowired + private Validator validator; + + public boolean validatorByUserOne(User user) { + Set> validate = validator.validate(user); + return validate.isEmpty(); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/MyValidation2.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/MyValidation2.java new file mode 100644 index 000000000000..58ee76c5d33a --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/MyValidation2.java @@ -0,0 +1,29 @@ +package com.lxcecho.validator.two; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.validation.BindException; +import org.springframework.validation.ObjectError; +import org.springframework.validation.Validator; + +import java.util.List; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Service +public class MyValidation2 { + + @Autowired + private Validator validator; + + public boolean validatorByUserTwo(User user) { + BindException bindException = new BindException(user, user.getName()); + validator.validate(user, bindException); + List allErrors = bindException.getAllErrors(); + System.out.println(allErrors); + return bindException.hasErrors(); + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/User.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/User.java new file mode 100644 index 000000000000..f65b0210d4ad --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/User.java @@ -0,0 +1,36 @@ +package com.lxcecho.validator.two; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class User { + + @NotNull + private String name; + + @Min(0) + @Max(150) + private int age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + +} diff --git a/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/ValidationConfig.java b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/ValidationConfig.java new file mode 100644 index 000000000000..a4c550cec890 --- /dev/null +++ b/spring-lxcecho-sample/src/main/java/com/lxcecho/validator/two/ValidationConfig.java @@ -0,0 +1,21 @@ +package com.lxcecho.validator.two; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Configuration +@ComponentScan("com.lxcecho.validator.two") +public class ValidationConfig { + + @Bean + public LocalValidatorFactoryBean validator() { + return new LocalValidatorFactoryBean(); + } + +} diff --git a/spring-lxcecho-sample/src/main/resources/bean-aopanno.xml b/spring-lxcecho-sample/src/main/resources/bean-aopanno.xml new file mode 100644 index 000000000000..2b31d9c46edb --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-aopanno.xml @@ -0,0 +1,19 @@ + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-aopxml.xml b/spring-lxcecho-sample/src/main/resources/bean-aopxml.xml new file mode 100644 index 000000000000..7b17e099dbb3 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-aopxml.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-auto.xml b/spring-lxcecho-sample/src/main/resources/bean-auto.xml new file mode 100644 index 000000000000..7f6d89dfdfad --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-auto.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-di.xml b/spring-lxcecho-sample/src/main/resources/bean-di.xml new file mode 100644 index 000000000000..45aeffdb4f15 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-di.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-diarray.xml b/spring-lxcecho-sample/src/main/resources/bean-diarray.xml new file mode 100644 index 000000000000..0bc099a93fd8 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-diarray.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + 吃饭 + 睡觉 + 敲代码 + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-dilist.xml b/spring-lxcecho-sample/src/main/resources/bean-dilist.xml new file mode 100644 index 000000000000..75eb33edd5c9 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-dilist.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-dimap.xml b/spring-lxcecho-sample/src/main/resources/bean-dimap.xml new file mode 100644 index 000000000000..d0b22f96d9ee --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-dimap.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 10010 + + + + + + 10086 + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-diref.xml b/spring-lxcecho-sample/src/main/resources/bean-diref.xml new file mode 100644 index 000000000000..dc5e7106c7a7 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-diref.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 10010 + + + + + + 10086 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-ditest.xml b/spring-lxcecho-sample/src/main/resources/bean-ditest.xml new file mode 100644 index 000000000000..7b5961e35b1b --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-ditest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-factorybean.xml b/spring-lxcecho-sample/src/main/resources/bean-factorybean.xml new file mode 100644 index 000000000000..a47d47742e9e --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-factorybean.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-i18n.xml b/spring-lxcecho-sample/src/main/resources/bean-i18n.xml new file mode 100644 index 000000000000..59e0f0491e16 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-i18n.xml @@ -0,0 +1,14 @@ + + + + + + + echo + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-iocanno.xml b/spring-lxcecho-sample/src/main/resources/bean-iocanno.xml new file mode 100644 index 000000000000..f5b70727b860 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-iocanno.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-jdbc.xml b/spring-lxcecho-sample/src/main/resources/bean-jdbc.xml new file mode 100644 index 000000000000..c696cbdac8cf --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-jdbc.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-junit.xml b/spring-lxcecho-sample/src/main/resources/bean-junit.xml new file mode 100644 index 000000000000..d259b02bcfce --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-junit.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-life.xml b/spring-lxcecho-sample/src/main/resources/bean-life.xml new file mode 100644 index 000000000000..3bf02fbb0d95 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-life.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-resource.xml b/spring-lxcecho-sample/src/main/resources/bean-resource.xml new file mode 100644 index 000000000000..d91160ca1932 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-resource.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-scope.xml b/spring-lxcecho-sample/src/main/resources/bean-scope.xml new file mode 100644 index 000000000000..5efd0a3ea9db --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-scope.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-tx-xml.xml b/spring-lxcecho-sample/src/main/resources/bean-tx-xml.xml new file mode 100644 index 000000000000..0061c1fa5e0a --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-tx-xml.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean-tx.xml b/spring-lxcecho-sample/src/main/resources/bean-tx.xml new file mode 100644 index 000000000000..e69d72882ca5 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean-tx.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/bean.xml b/spring-lxcecho-sample/src/main/resources/bean.xml new file mode 100644 index 000000000000..0a223a23b1d0 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/bean.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/echo_en_US.properties b/spring-lxcecho-sample/src/main/resources/echo_en_US.properties new file mode 100644 index 000000000000..996442fae25c --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/echo_en_US.properties @@ -0,0 +1 @@ +echo=welcome{0},\u65F6\u95F4:{1} \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/echo_zh_CN.properties b/spring-lxcecho-sample/src/main/resources/echo_zh_CN.properties new file mode 100644 index 000000000000..0af5ae79fd53 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/echo_zh_CN.properties @@ -0,0 +1 @@ +echo=\u6B22\u8FCE{0},\u65F6\u95F4:{1} \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/jdbc.properties b/spring-lxcecho-sample/src/main/resources/jdbc.properties new file mode 100644 index 000000000000..d2393ee7e082 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/jdbc.properties @@ -0,0 +1,4 @@ +jdbc.user=root +jdbc.password=Amecho00# +jdbc.url=jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true +jdbc.driver=com.mysql.cj.jdbc.Driver \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/log4j2.xml b/spring-lxcecho-sample/src/main/resources/log4j2.xml new file mode 100644 index 000000000000..e27d2f8d9e45 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/log4j2.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/lxcecho.txt b/spring-lxcecho-sample/src/main/resources/lxcecho.txt new file mode 100644 index 000000000000..093444864a0c --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/lxcecho.txt @@ -0,0 +1 @@ +Hello lxcecho. \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/messages_en_GB.properties b/spring-lxcecho-sample/src/main/resources/messages_en_GB.properties new file mode 100644 index 000000000000..9e3c2c55a68c --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/messages_en_GB.properties @@ -0,0 +1 @@ +test=GB test \ No newline at end of file diff --git a/spring-lxcecho-sample/src/main/resources/messages_zh_CN.properties b/spring-lxcecho-sample/src/main/resources/messages_zh_CN.properties new file mode 100644 index 000000000000..dc7faed263d8 --- /dev/null +++ b/spring-lxcecho-sample/src/main/resources/messages_zh_CN.properties @@ -0,0 +1 @@ +test=China test \ No newline at end of file diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/HelloTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/HelloTest.java new file mode 100644 index 000000000000..a8f66b35197a --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/HelloTest.java @@ -0,0 +1,16 @@ +package com.lxcecho; + +import org.junit.jupiter.api.Test; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class HelloTest { + + @Test + public void hello() { + System.out.println("Hello Test/////"); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/aop/AopTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/aop/AopTest.java new file mode 100644 index 000000000000..956389c41aba --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/aop/AopTest.java @@ -0,0 +1,37 @@ +package com.lxcecho.aop; + +import com.lxcecho.aop.annoaop.Calculator; +import com.lxcecho.aop.annoaop.SpringAopConfig; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class AopTest { + + @Test + public void testAnnoAop() { + ApplicationContext context = new AnnotationConfigApplicationContext(SpringAopConfig.class); + Calculator calculator = context.getBean(Calculator.class); + System.out.println(calculator.div(10, 0)); + } + + @Test + public void testAdd() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-aopanno.xml"); + Calculator calculator = context.getBean(Calculator.class); + calculator.add(2, 3); + } + + @Test + public void testAdd02() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-aopxml.xml"); + com.lxcecho.aop.xmlaop.Calculator calculator = context.getBean(com.lxcecho.aop.xmlaop.Calculator.class); + calculator.add(4, 3); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/ioc/IocByAnnotationTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/ioc/IocByAnnotationTest.java new file mode 100644 index 000000000000..4c3f39cb0616 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/ioc/IocByAnnotationTest.java @@ -0,0 +1,32 @@ +package com.lxcecho.ioc; + +import com.lxcecho.ioc.iocanno.config.SpringConfig; +import com.lxcecho.ioc.iocanno.controller.AutowiredBaseController; +import com.lxcecho.ioc.iocanno.controller.ResourceUserController; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class IocByAnnotationTest { + + @Test + public void testAnnotationXml() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-iocanno.xml"); + AutowiredBaseController controller = context.getBean(AutowiredBaseController.class); + controller.add(); + } + + @Test + public void testAnnotation() { + // 加载配置类 + ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); + ResourceUserController controller = context.getBean(ResourceUserController.class); + controller.add(); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/ioc/IocByXmlTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/ioc/IocByXmlTest.java new file mode 100644 index 000000000000..b4b681f02474 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/ioc/IocByXmlTest.java @@ -0,0 +1,166 @@ +package com.lxcecho.ioc; + +import com.alibaba.druid.pool.DruidDataSource; +import com.lxcecho.ioc.iocxml.bean.*; +import com.lxcecho.ioc.iocxml.controller.UserController; +import com.lxcecho.ioc.iocxml.dao.UserDao; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/10 + */ +public class IocByXmlTest { + + @Test + public void testBook() { + // set 方法注入 + Book book = new Book(); + book.setBname("java"); + book.setAuthor("尚硅谷"); + + // 通过构造器注入 + Book book1 = new Book("c++", "尚硅谷"); + } + + @Test + public void testBookSetter() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml"); + Book book = context.getBean("book", Book.class); + System.out.println(book); + } + + @Test + public void testBookConstructor() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-di.xml"); + Book book = context.getBean("bookCon", Book.class); + System.out.println(book); + } + + @Test + public void testDI() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-dilist.xml"); + // 员工对象 + Dept dept = context.getBean("dept", Dept.class); + dept.info(); + + /*ApplicationContext context = + new ClassPathXmlApplicationContext("bean-diarray.xml"); + // 员工对象 + Emp emp = context.getBean("emp", Emp.class); + emp.work();*/ + } + + @Test + public void testStu() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-diref.xml"); + Student student = context.getBean("studentp", Student.class); + student.run(); + } + + @Test + public void testUserDao() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); + // 根据类型获取接口对应 bean + UserDao userDao = context.getBean(UserDao.class); + System.out.println(userDao); + userDao.run(); + } + + @Test + public void testJdbc() { + /*DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=UTC"); + dataSource.setUsername("root"); + dataSource.setPassword("Amecho00#"); + dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");*/ + + ApplicationContext context = new ClassPathXmlApplicationContext("bean-jdbc.xml"); + DruidDataSource dataSource = context.getBean(DruidDataSource.class); + System.out.println(dataSource.getUrl()); + } + + // 创建 Logger 对象 + private Logger logger = LoggerFactory.getLogger(IocByXmlTest.class); + + @Test + public void testUserObject() { + // 加载 spring 配置文件,对象创建 + ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); + + // 获取创建的对象 + User user = (User) context.getBean("user"); + System.out.println("1:" + user); + + // 使用对象调用方法进行测试 + System.out.print("2:"); + user.add(); + + // 手动写日志 + logger.info("### 执行调用成功了.."); + } + + //反射创建对象 + @Test + public void testUserObject1() throws Exception { + // 获取类 Class 对象 + Class clazz = Class.forName("com.lxcecho.ioc.iocxml.bean.User"); + // 调用方法创建对象 + //Object o = clazz.newInstance(); + User user = (User) clazz.getDeclaredConstructor().newInstance(); + System.out.println(user); + } + + @Test + public void testBeanAuto() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-auto.xml"); + UserController controller = context.getBean("userController", UserController.class); + controller.addUser(); + } + + @Test + public void testBeanLife() { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean-life.xml"); + User user = context.getBean("user", User.class); + System.out.println("6 bean对象创建完成了,可以使用了"); + System.out.println(user); + context.close(); // 销毁 + } + + @Test + public void testFactoryBean() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-factorybean.xml"); + User user = (User) context.getBean("user"); + System.out.println(user); + } + + @Test + public void testBeanScope() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml"); + Orders orders = context.getBean("orders", Orders.class); + System.out.println(orders); + Orders orders1 = context.getBean("orders", Orders.class); + System.out.println(orders1); + } + + @Test + public void testUser() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml"); + //1 根据id获取bean + User user1 = (User) context.getBean("user1"); + System.out.println("1 根据 id 获取 bean: " + user1); + + //2 根据类型获取bean +// User user2 = context.getBean(User.class); +// System.out.println("2 根据类型获取bean: "+user2); + + //3 根据id和类型获取bean +// User user3 = context.getBean("user", User.class); +// System.out.println("3 根据id和类型获取bean: "+user3); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/annotx/TxByAnnoForAnnoTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/annotx/TxByAnnoForAnnoTest.java new file mode 100644 index 000000000000..f3bb4d2086d1 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/annotx/TxByAnnoForAnnoTest.java @@ -0,0 +1,19 @@ +package com.lxcecho.jdbctx.annotx; + +import com.lxcecho.jdbctx.annotx.config.TxConfig; +import com.lxcecho.jdbctx.annotx.controller.BookController; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +public class TxByAnnoForAnnoTest { + + @Test + public void testTxAllAnnotation() { + ApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class); + BookController bookController = applicationContext.getBean("bookController", BookController.class); + Integer[] bookIds = {1, 2}; + bookController.checkout(bookIds, 1); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/annotx/TxByAnnoForXmlTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/annotx/TxByAnnoForXmlTest.java new file mode 100644 index 000000000000..71f2e646768f --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/annotx/TxByAnnoForXmlTest.java @@ -0,0 +1,23 @@ +package com.lxcecho.jdbctx.annotx; + +import com.lxcecho.jdbctx.annotx.controller.BookController; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +@SpringJUnitConfig(locations = "classpath:bean-tx.xml") +public class TxByAnnoForXmlTest { + + @Autowired + private BookController bookController; + + @Test + public void testBuyBook() { +// bookController.buyBook(1,1); + + // Test propagation + Integer[] bookIds = {1, 2}; + bookController.checkout(bookIds, 1); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/jdbc/JdbcTemplateTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/jdbc/JdbcTemplateTest.java new file mode 100644 index 000000000000..6f1d6421c5b7 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/jdbc/JdbcTemplateTest.java @@ -0,0 +1,91 @@ +package com.lxcecho.jdbctx.jdbc; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.BeanPropertyRowMapper; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import java.util.List; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@SpringJUnitConfig(locations = "classpath:bean-jdbc.xml") +public class JdbcTemplateTest { + + @Autowired + private JdbcTemplate jdbcTemplate; + + /** + * 查询:返回对象 + */ + @Test + public void testSelectObject() { + // 写法一 + String sql = "select * from t_emp where id=?"; + Emp empResult = jdbcTemplate.queryForObject(sql, + (rs, rowNum) -> { + Emp emp = new Emp(); + emp.setId(rs.getInt("id")); + emp.setName(rs.getString("name")); + emp.setAge(rs.getInt("age")); + emp.setSex(rs.getString("sex")); + return emp; + }, 1); + System.out.println(empResult); + + // 写法二 + /*String sql = "select * from t_emp where id=?"; + Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1); + System.out.println(emp);*/ + } + + /** + * 查询:返回 list 集合 + */ + @Test + public void testSelectList() { + String sql = "select * from t_emp"; + List list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class)); + System.out.println(list); + } + + /** + * 查询:返回单个值 + */ + @Test + public void testSelectValue() { + String sql = "select count(*) from t_emp"; + Integer count = jdbcTemplate.queryForObject(sql, Integer.class); + System.out.println(count); + } + + /** + * 添加 修改 删除操作 + */ + @Test + public void testUpdate() { + // 1 添加操作 + /*// 第一步 编写 sql 语句 + String sql = "INSERT INTO t_emp VALUES(NULL,?,?,?)"; + // 第二步 调用 jdbcTemplate 的方法,传入相关参数 +// Object[] params = {"东方不败", 20, "未知"}; +// int rows = jdbcTemplate.update(sql,params); + int rows = jdbcTemplate.update(sql,"马克思", 20, "未知"); + System.out.println(rows);*/ + + + // 2 修改操作 + /*String sql = "update t_emp set name=? where id=?"; + int rows = jdbcTemplate.update(sql, "恩格斯", 3); + System.out.println(rows);*/ + + // 3 删除操作 + String sql = "delete from t_emp where id=?"; + int rows = jdbcTemplate.update(sql, 3); + System.out.println(rows); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/jdbc/JdbcTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/jdbc/JdbcTest.java new file mode 100644 index 000000000000..2d5cf0658f5a --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/jdbc/JdbcTest.java @@ -0,0 +1,65 @@ +package com.lxcecho.jdbctx.jdbc; + +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2024/3/26 + */ +public class JdbcTest { + + @Test + public void testJdbc() { + Connection connection = null; + PreparedStatement preparedStatement = null; + + try { + // 加载驱动类 + Class.forName("com.mysql.jdbc.Driver"); + // 数据库连接 + connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring", "root", "Amecho00#"); + // 关闭事务自动提交 + connection.setAutoCommit(false); + // 定义 sql + String sql = "update goods_stock set stock = stock - ? where id = ?"; + // 获取 sql 执行对象 + preparedStatement = connection.prepareStatement(sql); + // 设置参数 + preparedStatement.setInt(1, 10); + preparedStatement.setInt(2, 1); + // 执行 sql + preparedStatement.executeUpdate(); + // 提交事务 + connection.commit(); + } catch (Exception e) { + // 有异常则回滚事务 + try { + if (connection != null) { + connection.rollback(); + } + } catch (SQLException e1) { + e1.printStackTrace(); + } + } finally { + // 释放资源 + if (preparedStatement != null) { + try { + preparedStatement.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + try { + connection.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/xmltx/TxByXmlTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/xmltx/TxByXmlTest.java new file mode 100644 index 000000000000..43a3ef42d902 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/jdbctx/xmltx/TxByXmlTest.java @@ -0,0 +1,21 @@ +package com.lxcecho.jdbctx.xmltx; + +import com.lxcecho.jdbctx.xmltx.controller.BookController; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +@SpringJUnitConfig(locations = "classpath:bean-tx-xml.xml") +public class TxByXmlTest { + + @Autowired + private BookController bookController; + + @Test + public void testBuyBook() { + bookController.buyBook(1, 1); + // 测试事务传播行为: +// bookController.buyBook(2, 1); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/junit/SpringJunit4Test.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/junit/SpringJunit4Test.java new file mode 100644 index 000000000000..164a90ed6b24 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/junit/SpringJunit4Test.java @@ -0,0 +1,26 @@ +package com.lxcecho.junit; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration("classpath:bean-junit.xml") +public class SpringJunit4Test { + + @Autowired + private JUnitUser jUnitUser; + + @Test + public void testUser4() { + System.out.println(jUnitUser); + jUnitUser.run(); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/junit/SpringJunit5Test.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/junit/SpringJunit5Test.java new file mode 100644 index 000000000000..7287b1bbef18 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/junit/SpringJunit5Test.java @@ -0,0 +1,25 @@ +package com.lxcecho.junit; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +//@ExtendWith(SpringExtension.class) +//@ContextConfiguration("classpath:bean.xml") +@SpringJUnitConfig(locations = "classpath:bean-junit.xml") +public class SpringJunit5Test { + + @Autowired + private JUnitUser JUnitUser; + + @Test + public void testUser() { + System.out.println(JUnitUser); + JUnitUser.run(); + } + +} \ No newline at end of file diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/resources/ResourceLoaderTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/resources/ResourceLoaderTest.java new file mode 100644 index 000000000000..da406fae9c23 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/resources/ResourceLoaderTest.java @@ -0,0 +1,64 @@ +package com.lxcecho.resources; + +import com.lxcecho.resources.resourceloaderaware.TestResourceLoaderAwareBean; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +/** + * ResourceLoader:该接口实现类的实例可以获得一个 Resource 实例 + * + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class ResourceLoaderTest { + + @Test + public void testOtherResource() { + ApplicationContext ctx = new ClassPathXmlApplicationContext(); + // 通过 ApplicationContext 访问资源:ApplicationContext 实例获取 Resource 实例时,默认采用与 ApplicationContext 相同的资源访问策略 + Resource res = ctx.getResource("lxcecho.txt"); + System.out.println("==" + res); + System.out.println(res.getFilename()); + } + + /** + * ClassPathXmlApplicationContext 获取 Resource 实例 + */ + @Test + public void testResourceLoader01() { + ApplicationContext context = new ClassPathXmlApplicationContext(); + // 该接口仅有这个方法,用于返回一个 Resource 实例 + Resource resource = context.getResource("lxcecho.txt"); + System.out.println(resource.getFilename()); + } + + /** + * FileSystemApplicationContext 获取 Resource 实例 + */ + @Test + public void testResourceLoader02() { + ApplicationContext context = new FileSystemXmlApplicationContext(); + Resource resource = context.getResource("lxcecho.txt"); + System.out.println(resource.getFilename()); + } + + @Test + public void testResourceLoaderAware() { + // Spring 容器会将一个 ResourceLoader 对象作为该方法的参数传入 + ApplicationContext context = new ClassPathXmlApplicationContext("bean-resource.xml"); + TestResourceLoaderAwareBean testResourceLoaderAwareBean = context.getBean("testResourceLoaderAwareBean", TestResourceLoaderAwareBean.class); + // 获取 ResourceLoader 对象 + ResourceLoader resourceLoader = testResourceLoaderAwareBean.getResourceLoader(); + System.out.println("Spring 容器将自身注入到 ResourceLoaderAware Bean 中 ? :" + (resourceLoader == context)); + + // 加载其他资源 + Resource resource = resourceLoader.getResource("lxcecho.txt"); + System.out.println(resource.getFilename()); + System.out.println(resource.getDescription()); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/resources/ResourceTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/resources/ResourceTest.java new file mode 100644 index 000000000000..2a8851d11954 --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/resources/ResourceTest.java @@ -0,0 +1,78 @@ +package com.lxcecho.resources; + +import com.lxcecho.resources.di.ResourceBean; +import com.lxcecho.resources.prefix.User; +import com.lxcecho.resources.resource.ClassPathResourceDemo; +import com.lxcecho.resources.resource.FileSystemResourceDemo; +import com.lxcecho.resources.resource.UrlResourceDemo; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; +import org.springframework.core.io.Resource; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/17 + */ +public class ResourceTest { + + @Test + public void testUrlResource() { + // http 前缀:访问网络资源 +// UrlResourceDemo.loadUrlResource("http://www.baidu.com"); + + // file 前缀:访问文件系统资源 + UrlResourceDemo.loadUrlResource("file:url-file.txt"); // 项目根目录下的文件 + } + + @Test + public void testClassPathResource() { + ClassPathResourceDemo.loadClasspathResource("lxcecho.txt"); + } + + @Test + public void testFileSystemResource() { + FileSystemResourceDemo.loadFileResource("D:\\lxcecho.txt"); + FileSystemResourceDemo.loadFileResource("url-file.txt"); + } + + @Test + public void testResourceDI() { + ApplicationContext context = new ClassPathXmlApplicationContext("bean-resource.xml"); + ResourceBean resourceBean = context.getBean(ResourceBean.class); + resourceBean.parse(); + } + + /** + * ApplicationContext 确定资源访问策略通常有两种方法: + * (1)使用 ApplicationContext 实现类指定访问策略。 + * (2)使用前缀指定访问策略。 + */ + @Test + public void testPrefix() { + /** + * 使用 classpath: 前缀指定访问策略 + * 通过搜索文件系统路径下的 xml 文件创建 ApplicationContext,但通过指定 classpath: 前缀强制搜索类加载路径:classpath:bean-resource.xml + * */ + /*ApplicationContext context = new ClassPathXmlApplicationContext("classpath:bean-resource.xml"); + System.out.println(context); + Resource resource = context.getResource("lxcecho.txt"); + System.out.println(resource.getDescription()); + + User user = context.getBean(User.class); + System.out.println(user);*/ + + /** + * classpath 通配符使用 + * classpath*: 前缀提供了加载多个 XML 配置文件的能力,当使用 classpath*: 前缀来指定 XML 配置文件时,系统将搜索类加载路径,找到所有与文件名匹配的文件,分别加载文件中的配置定义,最后合并成一个 ApplicationContext。 + * 当使用classpath * :前缀时,Spring将会搜索类加载路径下所有满足该规则的配置文件。 + * + * 如果不是采用 classpath*: 前缀,而是改为使用 classpath: 前缀,Spring 则只加载第一个符合条件的 XML 文件 + */ + // Spring 允许将 classpath*: 前缀和通配符结合使用 + ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath*:bean-*.xml"); + System.out.println(ctx); + } + +} diff --git a/spring-lxcecho-sample/src/test/java/com/lxcecho/validator/ValidatorTest.java b/spring-lxcecho-sample/src/test/java/com/lxcecho/validator/ValidatorTest.java new file mode 100644 index 000000000000..8ee15b24fd6e --- /dev/null +++ b/spring-lxcecho-sample/src/test/java/com/lxcecho/validator/ValidatorTest.java @@ -0,0 +1,82 @@ +package com.lxcecho.validator; + +import com.lxcecho.validator.one.Person; +import com.lxcecho.validator.one.PersonValidator; +import com.lxcecho.validator.three.MyService; +import com.lxcecho.validator.two.MyValidation1; +import com.lxcecho.validator.two.MyValidation2; +import com.lxcecho.validator.two.User; +import com.lxcecho.validator.two.ValidationConfig; +import org.junit.jupiter.api.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.validation.BindingResult; +import org.springframework.validation.DataBinder; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/12 + */ +public class ValidatorTest { + + /** + * 校验测试 + */ + @Test + public void testPersonValidator() { + // 创建 person 对象 + Person person = new Person(); + person.setName("lucy"); + person.setAge(250); + + // 创建 person 对应 dataBinder + DataBinder binder = new DataBinder(person); + + // 设置校验器 + binder.setValidator(new PersonValidator()); + + // 调用方法执行校验 + binder.validate(); + + // 输出校验结果 + BindingResult result = binder.getBindingResult(); + System.out.println(result.getAllErrors()); + } + + @Test + public void testValidationOne() { + ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); + MyValidation1 validation1 = context.getBean(MyValidation1.class); + + User user = new User(); + user.setName("lucy"); + user.setAge(20); + boolean message = validation1.validatorByUserOne(user); + System.out.println(message); + } + + @Test + public void testValidationTwo() { + ApplicationContext context = new AnnotationConfigApplicationContext(ValidationConfig.class); + MyValidation2 validation2 = context.getBean(MyValidation2.class); + + User user = new User(); + user.setName("lucy"); + user.setAge(200); + + boolean message = validation2.validatorByUserTwo(user); + System.out.println(message); + } + + @Test + public void testValidationOnMethod() { + ApplicationContext context = new AnnotationConfigApplicationContext(com.lxcecho.validator.three.ValidationConfig.class); + MyService service = context.getBean(MyService.class); + com.lxcecho.validator.three.User user = new com.lxcecho.validator.three.User(); + user.setName("lucy"); + user.setPhone("13566754321"); + user.setMessage("test echo"); + service.testMethod(user); + } + +} diff --git a/spring-lxcecho-sample/url-file.txt b/spring-lxcecho-sample/url-file.txt new file mode 100644 index 000000000000..093444864a0c --- /dev/null +++ b/spring-lxcecho-sample/url-file.txt @@ -0,0 +1 @@ +Hello lxcecho. \ No newline at end of file diff --git a/spring-lxcecho/build.gradle b/spring-lxcecho/build.gradle new file mode 100644 index 000000000000..2fcaa2551217 --- /dev/null +++ b/spring-lxcecho/build.gradle @@ -0,0 +1,11 @@ +description "My Custom Spring" + +group = 'com.lxcecho' +version = '6.0.15-SNAPSHOT' + +dependencies { +// api(project(":spring-context")) + + testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation 'org.junit.jupiter:junit-jupiter' +} \ No newline at end of file diff --git a/spring-lxcecho/src/main/java/com/lxcecho/MainApp.java b/spring-lxcecho/src/main/java/com/lxcecho/MainApp.java new file mode 100644 index 000000000000..9c25af6dee7d --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/MainApp.java @@ -0,0 +1,18 @@ +package com.lxcecho; + +import com.lxcecho.bean.AnnotationApplicationContext; +import com.lxcecho.bean.ApplicationContext; +import com.lxcecho.service.UserService; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class MainApp { + public static void main(String[] args) { + ApplicationContext context = new AnnotationApplicationContext("com.lxcecho"); + UserService userService = (UserService) context.getBean(UserService.class); + System.out.println(userService); + userService.add(); + } +} \ No newline at end of file diff --git a/spring-lxcecho/src/main/java/com/lxcecho/anno/Bean.java b/spring-lxcecho/src/main/java/com/lxcecho/anno/Bean.java new file mode 100644 index 000000000000..ba7707ce451d --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/anno/Bean.java @@ -0,0 +1,15 @@ +package com.lxcecho.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface Bean { +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/anno/Di.java b/spring-lxcecho/src/main/java/com/lxcecho/anno/Di.java new file mode 100644 index 000000000000..5af8adc875e7 --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/anno/Di.java @@ -0,0 +1,15 @@ +package com.lxcecho.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Target({ElementType.FIELD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Di { +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/bean/AnnotationApplicationContext.java b/spring-lxcecho/src/main/java/com/lxcecho/bean/AnnotationApplicationContext.java new file mode 100644 index 000000000000..a5a7ef889116 --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/bean/AnnotationApplicationContext.java @@ -0,0 +1,157 @@ +package com.lxcecho.bean; + +import com.lxcecho.anno.Bean; +import com.lxcecho.anno.Di; + +import java.io.File; +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class AnnotationApplicationContext implements ApplicationContext { + + /** + * 创建 map 集合,放 bean 对象 + */ + private Map, Object> beanFactory = new HashMap<>(); + + private static String rootPath; + + // 返回对象 + @Override + public Object getBean(Class clazz) { + return beanFactory.get(clazz); + } + + //创建有参数构造,传递包路径,设置包扫描规则 + //当前包及其子包,哪个类有@Bean注解,把这个类通过反射实例化 + public AnnotationApplicationContext(String basePackage) { + try { + // com.lxcecho + //1 把.替换成\ + String packagePath = basePackage.replaceAll("\\.", + "\\\\"); + + //2 获取包绝对路径 + Enumeration urls + = Thread.currentThread().getContextClassLoader() + .getResources(packagePath); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + String filePath = URLDecoder.decode(url.getFile(), + "utf-8"); + //获取包前面路径部分,字符串截取 + rootPath = filePath.substring(0, filePath.length() - packagePath.length()); + //包扫描 + loadBean(new File(filePath)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + //属性注入 + loadDi(); + } + + //包扫描过程,实例化 + private void loadBean(File file) throws Exception { + //1 判断当前是否文件夹 + if (file.isDirectory()) { + //2 获取文件夹里面所有内容 + File[] childrenFiles = file.listFiles(); + + //3 判断文件夹里面为空,直接返回 + if (childrenFiles == null || childrenFiles.length == 0) { + return; + } + + //4 如果文件夹里面不为空,遍历文件夹所有内容 + for (File child : childrenFiles) { + //4.1 遍历得到每个File对象,继续判断,如果还是文件夹,递归 + if (child.isDirectory()) { + //递归 + loadBean(child); + } else { + //4.2 遍历得到File对象不是文件夹,是文件, + //4.3 得到包路径+类名称部分-字符串截取 + String pathWithClass = + child.getAbsolutePath().substring(rootPath.length() - 1); + + //4.4 判断当前文件类型是否.class + if (pathWithClass.contains(".class")) { + + //4.5 如果是.class类型,把路径\替换成. 把.class去掉 + // com.lxcecho.service.UserServiceImpl + String allName = pathWithClass.replaceAll("\\\\", ".") + .replace(".class", ""); + + //4.6 判断类上面是否有注解 @Bean,如果有实例化过程 + //4.6.1 获取类的Class + Class clazz = Class.forName(allName); + //4.6.2 判断不是接口 + if (!clazz.isInterface()) { + //4.6.3 判断类上面是否有注解 @Bean + Bean annotation = clazz.getAnnotation(Bean.class); + if (annotation != null) { + //4.6.4 实例化 + Object instance = clazz.getConstructor().newInstance(); + //4.7 把对象实例化之后,放到map集合beanFactory + //4.7.1 判断当前类如果有接口,让接口class作为map的key + if (clazz.getInterfaces().length > 0) { + beanFactory.put(clazz.getInterfaces()[0], instance); + } else { + beanFactory.put(clazz, instance); + } + } + } + } + } + } + } + } + + //属性注入 + private void loadDi() { + //实例化对象在beanFactory的map集合里面 + //1 遍历beanFactory的map集合 + Set, Object>> entries = beanFactory.entrySet(); + for (Map.Entry, Object> entry : entries) { + //2 获取map集合每个对象(value),每个对象属性获取到 + Object obj = entry.getValue(); + + //获取对象Class + Class clazz = obj.getClass(); + + //获取每个对象属性获取到 + Field[] declaredFields = clazz.getDeclaredFields(); + + //3 遍历得到每个对象属性数组,得到每个属性 + for (Field field : declaredFields) { + //4 判断属性上面是否有@Di注解 + Di annotation = field.getAnnotation(Di.class); + if (annotation != null) { + //如果私有属性,设置可以设置值 + field.setAccessible(true); + + //5 如果有@Di注解,把对象进行设置(注入) + try { + field.set(obj, beanFactory.get(field.getType())); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + } + } +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/bean/ApplicationContext.java b/spring-lxcecho/src/main/java/com/lxcecho/bean/ApplicationContext.java new file mode 100644 index 000000000000..b2d408cea81b --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/bean/ApplicationContext.java @@ -0,0 +1,11 @@ +package com.lxcecho.bean; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface ApplicationContext { + + Object getBean(Class clazz); + +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/dao/UserDao.java b/spring-lxcecho/src/main/java/com/lxcecho/dao/UserDao.java new file mode 100644 index 000000000000..8f65d6fca779 --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/dao/UserDao.java @@ -0,0 +1,11 @@ +package com.lxcecho.dao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface UserDao { + + void add(); + +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/dao/impl/UserDaoImpl.java b/spring-lxcecho/src/main/java/com/lxcecho/dao/impl/UserDaoImpl.java new file mode 100644 index 000000000000..6289786fe7a2 --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/dao/impl/UserDaoImpl.java @@ -0,0 +1,17 @@ +package com.lxcecho.dao.impl; + +import com.lxcecho.anno.Bean; +import com.lxcecho.dao.UserDao; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Bean +public class UserDaoImpl implements UserDao { + @Override + public void add() { + System.out.println("dao......."); + } + +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/reflect/Car.java b/spring-lxcecho/src/main/java/com/lxcecho/reflect/Car.java new file mode 100644 index 000000000000..86ce2b4b7b67 --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/reflect/Car.java @@ -0,0 +1,75 @@ +package com.lxcecho.reflect; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class Car { + // 属性 + + private String name; + + private int age; + + private String color; + + /** + * 无参数构造 + */ + public Car() { + } + + /** + * 有参数构造 + * @param name + * @param age + * @param color + */ + public Car(String name, int age, String color) { + this.name = name; + this.age = age; + this.color = color; + } + + /** + * 普通方法 + */ + private void run() { + System.out.println("私有方法-run....."); + } + + // get和set方法 + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + + @Override + public String toString() { + return "Car{" + + "name='" + name + '\'' + + ", age=" + age + + ", color='" + color + '\'' + + '}'; + } +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/service/UserService.java b/spring-lxcecho/src/main/java/com/lxcecho/service/UserService.java new file mode 100644 index 000000000000..2c5f27656379 --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/service/UserService.java @@ -0,0 +1,11 @@ +package com.lxcecho.service; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public interface UserService { + + void add(); + +} diff --git a/spring-lxcecho/src/main/java/com/lxcecho/service/impl/UserServiceImpl.java b/spring-lxcecho/src/main/java/com/lxcecho/service/impl/UserServiceImpl.java new file mode 100644 index 000000000000..612cc61428be --- /dev/null +++ b/spring-lxcecho/src/main/java/com/lxcecho/service/impl/UserServiceImpl.java @@ -0,0 +1,23 @@ +package com.lxcecho.service.impl; + +import com.lxcecho.anno.Bean; +import com.lxcecho.anno.Di; +import com.lxcecho.dao.UserDao; +import com.lxcecho.service.UserService; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +@Bean +public class UserServiceImpl implements UserService { + + @Di + private UserDao userDao; + + public void add() { + System.out.println("service......."); + //调用dao的方法 + userDao.add(); + } +} diff --git a/spring-lxcecho/src/test/java/com/lxcecho/CarTest.java b/spring-lxcecho/src/test/java/com/lxcecho/CarTest.java new file mode 100644 index 000000000000..8bf893194f80 --- /dev/null +++ b/spring-lxcecho/src/test/java/com/lxcecho/CarTest.java @@ -0,0 +1,107 @@ +package com.lxcecho; + +import com.lxcecho.reflect.Car; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * @author lxcecho azaki0426@gmail.com + * @since 2023/12/11 + */ +public class CarTest { + //1、获取Class对象多种方式 + @Test + public void test01() throws Exception { + //1 类名.class + Class clazz1 = Car.class; + + //2 对象.getClass() + Class clazz2 = new Car().getClass(); + + //3 Class.forName("全路径") + Class clazz3 = Class.forName("com.lxcecho.reflect.Car"); + + int length = clazz1.getInterfaces().length; + System.out.println(length); + //实例化 + Car car = (Car) clazz3.getConstructor().newInstance(); +// System.out.println(car); + } + + //2、获取构造方法 + @Test + public void test02() throws Exception { + Class clazz = Car.class; + //获取所有构造 + // getConstructors()获取所有public的构造方法 +// Constructor[] constructors = clazz.getConstructors(); + // getDeclaredConstructors()获取所有的构造方法public private + Constructor[] constructors = clazz.getDeclaredConstructors(); + for (Constructor c : constructors) { + System.out.println("方法名称:" + c.getName() + " 参数个数:" + c.getParameterCount()); + } + + //指定有参数构造创建对象 + //1 构造public +// Constructor c1 = clazz.getConstructor(String.class, int.class, String.class); +// Car car1 = (Car)c1.newInstance("夏利", 10, "红色"); +// System.out.println(car1); + + //2 构造private + Constructor c2 = clazz.getDeclaredConstructor(String.class, int.class, String.class); + c2.setAccessible(true); + Car car2 = (Car) c2.newInstance("捷达", 15, "白色"); + System.out.println(car2); + } + + //3、获取属性 + @Test + public void test03() throws Exception { + Class clazz = Car.class; + Car car = (Car) clazz.getDeclaredConstructor().newInstance(); + //获取所有public属性 + //Field[] fields = clazz.getFields(); + //获取所有属性(包含私有属性) + Field[] fields = clazz.getDeclaredFields(); + for (Field field : fields) { + if (field.getName().equals("name")) { + //设置允许访问 + field.setAccessible(true); + field.set(car, "五菱宏光"); + + System.out.println(car); + } + System.out.println(field.getName()); + } + } + + //4、获取方法 + @Test + public void test04() throws Exception { + Car car = new Car("奔驰", 10, "黑色"); + Class clazz = car.getClass(); + //1 public方法 + Method[] methods = clazz.getMethods(); + for (Method m1 : methods) { + //System.out.println(m1.getName()); + //执行方法 toString + if (m1.getName().equals("toString")) { + String invoke = (String) m1.invoke(car); + //System.out.println("toString执行了:"+invoke); + } + } + + //2 private方法 + Method[] methodsAll = clazz.getDeclaredMethods(); + for (Method m : methodsAll) { + //执行方法 run + if (m.getName().equals("run")) { + m.setAccessible(true); + m.invoke(car); + } + } + } +} diff --git a/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java index ae192da79c18..103e41020956 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/PlatformTransactionManager.java @@ -43,9 +43,11 @@ * @see org.springframework.transaction.interceptor.TransactionInterceptor * @see org.springframework.transaction.ReactiveTransactionManager */ -public interface PlatformTransactionManager extends TransactionManager { +public interface PlatformTransactionManager extends TransactionManager { // 平台事务管理器:定义事务和基础行为 /** + * 用于获取事务状态信息 + * * Return a currently active transaction or create a new one, according to * the specified propagation behavior. *

Note that parameters like isolation level or timeout will only be applied @@ -71,6 +73,8 @@ public interface PlatformTransactionManager extends TransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; /** + * 提交事务 + * * Commit the given transaction, with regard to its status. If the transaction * has been marked rollback-only programmatically, perform a rollback. *

If the transaction wasn't a new one, omit the commit for proper @@ -97,6 +101,8 @@ public interface PlatformTransactionManager extends TransactionManager { void commit(TransactionStatus status) throws TransactionException; /** + * 回滚事务 + * * Perform a rollback of the given transaction. *

If the transaction wasn't a new one, just set it rollback-only for proper * participation in the surrounding transaction. If a previous transaction diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java index c7b7c1e7b9f6..30ec45fbd91b 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionDefinition.java @@ -19,6 +19,8 @@ import org.springframework.lang.Nullable; /** + * 7 大传播 + 5大隔离 + 超时、只读、回滚 + * * Interface that defines Spring-compliant transaction properties. * Based on the propagation behavior definitions analogous to EJB CMT attributes. * @@ -43,6 +45,7 @@ */ public interface TransactionDefinition { + // ===========事务传播行为============ /** * Support a current transaction; create a new one if none exists. * Analogous to the EJB transaction attribute of the same name. @@ -131,8 +134,11 @@ public interface TransactionDefinition { */ int PROPAGATION_NESTED = 6; + // ======事务隔离级别========== /** + * PlatformTransactionManager 默认隔离级别,使用数据库默认的事务隔离级别。另外四个与 JDBC 的隔离级别相对应 + * * Use the default isolation level of the underlying datastore. *

All other levels correspond to the JDBC isolation levels. * @see java.sql.Connection @@ -140,6 +146,9 @@ public interface TransactionDefinition { int ISOLATION_DEFAULT = -1; /** + * 读未提交:这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别【会产生脏读,不可重复读和幻读】 + * 这种食物隔离级别下,select 语句不加锁。此时,可能读取到不一致数据,即“脏读”。这是并发最高,一致性最差的隔离级别。 + * * Indicates that dirty reads, non-repeatable reads, and phantom reads * can occur. *

This level allows a row changed by one transaction to be read by another @@ -151,6 +160,8 @@ public interface TransactionDefinition { int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED; /** + * 读已提交:保证一个事务修改的数据提交后才能被另一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别【可以避免脏读出现,但是可能会出现不可重复读和幻读】 + * * Indicates that dirty reads are prevented; non-repeatable reads and * phantom reads can occur. *

This level only prohibits a transaction from reading a row with uncommitted @@ -159,7 +170,12 @@ public interface TransactionDefinition { */ int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED; + // TODO 在互联网大数据量,高并发量的场景下,几乎不会使用上述两种隔离级别 【ISOLATION_READ_UNCOMMITTED 和 ISOLATION_READ_COMMITTED】 + /** + * 可重复读:这种事务隔离级别【可以防止脏读、不可重复读,但是可能出现幻读】。他除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读 + * 这是 MySql 默认隔离级别 + * * Indicates that dirty reads and non-repeatable reads are prevented; * phantom reads can occur. *

This level prohibits a transaction from reading a row with uncommitted changes @@ -171,6 +187,8 @@ public interface TransactionDefinition { int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ; /** + * 串行化:代价最大,可靠性最高的隔离级别,所有事务都是按顺序一个接一个的执行【可避免脏读、不可重复读、幻读】 + * * Indicates that dirty reads, non-repeatable reads, and phantom reads * are prevented. *

This level includes the prohibitions in {@link #ISOLATION_REPEATABLE_READ} diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java index d5d0706187e0..0eb81dd39bb5 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionExecution.java @@ -27,6 +27,8 @@ public interface TransactionExecution { /** + * 获取是否新事物 + * * Return whether the present transaction is new; otherwise participating * in an existing transaction, or potentially not running in an actual * transaction in the first place. @@ -34,6 +36,8 @@ public interface TransactionExecution { boolean isNewTransaction(); /** + * 设置事务回滚 + * * Set the transaction rollback-only. This instructs the transaction manager * that the only possible outcome of the transaction may be a rollback, as * alternative to throwing an exception which would in turn trigger a rollback. @@ -41,12 +45,16 @@ public interface TransactionExecution { void setRollbackOnly(); /** + * 是否回滚 + * * Return whether the transaction has been marked as rollback-only * (either by the application or by the transaction infrastructure). */ boolean isRollbackOnly(); /** + * 事务是否完成 + * * Return whether this transaction is completed, that is, * whether it has already been committed or rolled back. */ diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionManager.java index 6504495b59cb..8c47e236c3de 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionManager.java @@ -17,6 +17,8 @@ package org.springframework.transaction; /** + * Spring 管理的基接口,作为子接口上层接口区分,并没有定义实际的事务行为能力 + * * Marker interface for Spring transaction manager implementations, * either traditional or reactive. * diff --git a/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java b/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java index d61ed25f618d..21ef379af9d5 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java +++ b/spring-tx/src/main/java/org/springframework/transaction/TransactionStatus.java @@ -19,6 +19,8 @@ import java.io.Flushable; /** + * 事务运行状态接口:获取或设置事务的相应状态信息 + * * Representation of an ongoing {@link PlatformTransactionManager} transaction. * Extends the common {@link TransactionExecution} interface. * @@ -40,6 +42,8 @@ public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable { /** + * 是否存在保存点 + * * Return whether this transaction internally carries a savepoint, * that is, has been created as nested transaction based on a savepoint. *

This method is mainly here for diagnostic purposes, alongside @@ -53,6 +57,8 @@ public interface TransactionStatus extends TransactionExecution, SavepointManage boolean hasSavepoint(); /** + * 刷新事务 + * * Flush the underlying session to the datastore, if applicable: * for example, all affected Hibernate/JPA sessions. *

This is effectively just a hint and may be a no-op if the underlying diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java index 543f996d4215..112c1b27d059 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Isolation.java @@ -19,6 +19,8 @@ import org.springframework.transaction.TransactionDefinition; /** + * 数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。 + * SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。 * Enumeration that represents transaction isolation levels for use with the * {@link Transactional @Transactional} annotation, corresponding to the * {@link TransactionDefinition} interface. @@ -37,6 +39,8 @@ public enum Isolation { DEFAULT(TransactionDefinition.ISOLATION_DEFAULT), /** + * 允许 Transaction-1 读取 Transaction-2 未提交的修改 + * * A constant indicating that dirty reads, non-repeatable reads, and phantom reads * can occur. *

This level allows a row changed by one transaction to be read by @@ -48,6 +52,8 @@ public enum Isolation { READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), /** + * 要求 Transaction-1 只能读取 Transaction-2 已提交的修改 + * * A constant indicating that dirty reads are prevented; non-repeatable reads * and phantom reads can occur. *

This level only prohibits a transaction from reading a row with uncommitted @@ -57,6 +63,8 @@ public enum Isolation { READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), /** + * 确保 Transaction-1 可以多次从一个字段中读取到相同的值,即 Transaction-1 执行期间禁止其它事务对这个字段进行更新。 + * * A constant indicating that dirty reads and non-repeatable reads are * prevented; phantom reads can occur. *

This level prohibits a transaction from reading a row with uncommitted changes @@ -68,6 +76,8 @@ public enum Isolation { REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), /** + * 确保 Transaction-1 可以多次从一个表中读取到相同的行,在 Transaction-1 执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。 + * * A constant indicating that dirty reads, non-repeatable reads, and phantom * reads are prevented. *

This level includes the prohibitions in {@link #REPEATABLE_READ} diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Propagation.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Propagation.java index 18e43cf16e8f..8c888634442e 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Propagation.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Propagation.java @@ -19,6 +19,9 @@ import org.springframework.transaction.TransactionDefinition; /** + * 传播行为:在 service 类中有 a() 方法和 b() 方法, a() 方法上有事务, b() 方法上也有事务, + * 当 a() 方法执行过程中调用了 b() 方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务? + * * Enumeration that represents transaction propagation behaviors for use * with the {@link Transactional} annotation, corresponding to the * {@link TransactionDefinition} interface. @@ -30,6 +33,8 @@ public enum Propagation { /** + * 支持当前事务,如果存在,则加入事务;如果不存在就新建一个(默认)【没有就新建,有就加入】 + * * Support a current transaction, create a new one if none exists. * Analogous to EJB transaction attribute of the same name. *

This is the default setting of a transaction annotation. @@ -37,6 +42,8 @@ public enum Propagation { REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** + * 支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】 + * * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. *

Note: For transaction managers with transaction synchronization, @@ -50,12 +57,16 @@ public enum Propagation { SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** + * 必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】 + * * Support a current transaction, throw an exception if none exists. * Analogous to EJB transaction attribute of the same name. */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** + * 开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】 + * * Create a new transaction, and suspend the current transaction if one exists. * Analogous to the EJB transaction attribute of the same name. *

NOTE: Actual transaction suspension will not work out-of-the-box @@ -68,6 +79,8 @@ public enum Propagation { REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** + * 以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】 + * * Execute non-transactionally, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. *

NOTE: Actual transaction suspension will not work out-of-the-box @@ -80,12 +93,17 @@ public enum Propagation { NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** + * 以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】 + * * Execute non-transactionally, throw an exception if a transaction exists. * Analogous to EJB transaction attribute of the same name. */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** + * 如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像 REQUIRED 一样。 + * 【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和 REQUIRED 一样。】 + * * Execute within a nested transaction if a current transaction exists, * behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB. *

Note: Actual creation of a nested transaction will only work on specific diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java index f2128f3acaf8..1b7afa1121e2 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/ProxyTransactionManagementConfiguration.java @@ -41,14 +41,25 @@ @ImportRuntimeHints(TransactionRuntimeHints.class) public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration { + /** + * 创建并注入事务切面 advisor + * + * @param transactionAttributeSource + * @param transactionInterceptor + * @return + */ @Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor( TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) { + // 创建事务切面 Advisor BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); + // 设置事务属性数据源 advisor.setTransactionAttributeSource(transactionAttributeSource); + // 设置 Advice advisor.setAdvice(transactionInterceptor); + // 设置切面顺序 if (this.enableTx != null) { advisor.setOrder(this.enableTx.getNumber("order")); } @@ -62,9 +73,16 @@ public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(false); } + /** + * 创建 Advice 通知增强,实现 MethodInterceptor 接口 + * + * @param transactionAttributeSource + * @return + */ @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) { + // 注入事务核心拦截器 TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource); if (this.txManager != null) { diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java index 22d3dca76b0a..460e0bbec5cd 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/TransactionManagementConfigurationSelector.java @@ -38,6 +38,11 @@ public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector { /** + * 配置启动事务启动时,导入注册的配置 Bean: + * AutoProxyRegistrar:负责依赖注入事务的相关属性配置和注入事务入口类【InfrastructureAdvisorAutoProxyCreator】; + * ProxyTransactionManagementConfiguration:负责注入事务相关的 Bean,包括事务切面 Bean【BeanFactoryTransactionAttributeSourceAdvisor】, + * TransactionAttributeSource【事务配置属性 Bean】,TransactionInterceptor【事务拦截器 Bean】; + * * Returns {@link ProxyTransactionManagementConfiguration} or * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY} * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()}, @@ -46,6 +51,7 @@ public class TransactionManagementConfigurationSelector extends AdviceModeImport @Override protected String[] selectImports(AdviceMode adviceMode) { return switch (adviceMode) { + // 注册 AOP 入口类;这两个类变成 BeanDefinition case PROXY -> new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()}; case ASPECTJ -> new String[] {determineTransactionAspectClass()}; diff --git a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java index e0372ba45c79..c19e53455cdf 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java +++ b/spring-tx/src/main/java/org/springframework/transaction/annotation/Transactional.java @@ -155,6 +155,7 @@ String[] label() default {}; /** + * 事务传播行为:一个开启了事务的方法 A,调用了另一个开启了事务的方法 B,此时会出现什么情况?这就要看传播行为的设置了。 * The transaction propagation type. *

Defaults to {@link Propagation#REQUIRED}. * @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior() @@ -162,6 +163,7 @@ Propagation propagation() default Propagation.REQUIRED; /** + * 事务隔离级别:读未提交、读已提交、可重复读、可串行化。MySQL 的默认隔离级别是 可重复读。 * The transaction isolation level. *

Defaults to {@link Isolation#DEFAULT}. *

Exclusively designed for use with {@link Propagation#REQUIRED} or @@ -176,6 +178,9 @@ Isolation isolation() default Isolation.DEFAULT; /** + * 超时回滚,释放资源。默认值为 -1,不会超时。 + * 执行过程中抛出异常:org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Sat Dec 16 23:31:00 CST 2023 + * * The timeout for this transaction (in seconds). *

Defaults to the default timeout of the underlying transaction system. *

Exclusively designed for use with {@link Propagation#REQUIRED} or @@ -199,6 +204,10 @@ String timeoutString() default ""; /** + * 是否只读事务,只读事务要从两个方面来理解:它的功能是设置了只读事务后在整个事务的过程中,其他事务提交的内容对当前事务不可见的;只读事务只能有读操作,不能含有写操作,否则会报错。 + * 对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。 + * 对增删改操作设置只读会抛出下面异常:Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed + * * A boolean flag that can be set to {@code true} if the transaction is * effectively read-only, allowing for corresponding optimizations at runtime. *

Defaults to {@code false}. @@ -213,6 +222,7 @@ boolean readOnly() default false; /** + * 当方法内抛出指定的异常时,进行事务回滚。默认情况下支队 RuntimeException 回滚 * Defines zero (0) or more exception {@linkplain Class types}, which must be * subclasses of {@link Throwable}, indicating which exception types must cause * a transaction rollback. @@ -244,6 +254,7 @@ String[] rollbackForClassName() default {}; /** + * 用来设置出现指定的异常时,不进行回滚:需要设置一个 Class 类型的对象 * Defines zero (0) or more exception {@link Class types}, which must be * subclasses of {@link Throwable}, indicating which exception types must * not cause a transaction rollback. @@ -258,6 +269,7 @@ Class[] noRollbackFor() default {}; /** + * 需要设置一个字符串类型的全类名,如:noRollbackForClassName = "java.lang.ArithmeticException" * Defines zero (0) or more exception name patterns (for exceptions which must be a * subclass of {@link Throwable}) indicating which exception types must not * cause a transaction rollback. diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java index b800fb6ca629..319775f47360 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -325,6 +325,8 @@ public void afterPropertiesSet() { /** + * 包含了事务执行的整个流程,这里使用了模板模式,具体的实现交给子类去实现 + * * General delegate for around-advice-based subclasses, delegating to several other template * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager} * as well as regular {@link PlatformTransactionManager} implementations and @@ -339,11 +341,15 @@ public void afterPropertiesSet() { protected Object invokeWithinTransaction(Method method, @Nullable Class targetClass, final InvocationCallback invocation) throws Throwable { + // 获取事务属性,如果事务为空,则没有事务【TransactionAttributeSource 就是 @Transactional 中的配置】 // If the transaction attribute is null, the method is non-transactional. TransactionAttributeSource tas = getTransactionAttributeSource(); + // 获取 @Transactional 属性值,TransactionAttribute 继承 TransactionDefinition final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); + // 获取事务管理器:DatasourceTransactionManager 管理 JDBC 的 Connection【创建连接、提交回滚事务】 final TransactionManager tm = determineTransactionManager(txAttr); + // ReactiveTransactionManager 用的少且只是执行方式是响应式的,原理流程一样 if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager rtm) { boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method); boolean hasSuspendingFlowReturnType = isSuspendingFunction && @@ -377,25 +383,33 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class targe return result; } + // 将 tm 强制转换为 PlatformTransactionManager,所以一般我们在定义时直接使用 PlatformTransactionManager 类型 PlatformTransactionManager ptm = asPlatformTransactionManager(tm); + // 切点标识【唯一,即当前执行的方法名】 final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); + // CallbackPreferringPlatformTransactionManager:拥有回调功能的 PlatformTransactionManager,不常用 if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager cpptm)) { + // TODO:创建/开启事务,根据事务的传播行为属性去判断是否创建一个事务 + // TransactionInfo:表示一个逻辑事务,比如两个逻辑事务属于同一个物理事务 // Standard transaction demarcation with getTransaction and commit/rollback calls. TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification); Object retVal; try { + // 调用下一个 interceptor,这里已经没有下一个了,TODO:调用目标方法 // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { + // TODO:回滚事务,目标方法调用发生了异常【后置增强】 // target invocation exception completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { + // 清理消息,即:还原初始状态 cleanupTransactionInfo(txInfo); } @@ -407,11 +421,12 @@ protected Object invokeWithinTransaction(Method method, @Nullable Class targe } } + // TODO:提交事务 commitTransactionAfterReturning(txInfo); return retVal; } - else { + else { // 编程式事务 Object result; final ThrowableHolder throwableHolder = new ThrowableHolder(); @@ -501,9 +516,12 @@ else if (StringUtils.hasText(this.transactionManagerBeanName)) { else { TransactionManager defaultTransactionManager = getTransactionManager(); if (defaultTransactionManager == null) { + // 先从缓存中拿 defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY); if (defaultTransactionManager == null) { + // 从注入的实例中获取 defaultTransactionManager = this.beanFactory.getBean(TransactionManager.class); + // 设置事务管理器对象缓存 this.transactionManagerCache.putIfAbsent( DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager); } @@ -595,9 +613,11 @@ public String getName() { }; } + // 每个逻辑事务都会创建一个 TransactionStatus,但是 TransactionStatus 中有一个属性代表当前逻辑事务底层的物理事务是不是新的 TransactionStatus status = null; if (txAttr != null) { if (tm != null) { + // 开启事务 !!!【真正创建事务的方法】并返回 TransactionStatus status = tm.getTransaction(txAttr); } else { @@ -607,6 +627,7 @@ public String getName() { } } } + // 返回一个 TransactionInfo 对象【包装成事务信息,记录事务】,表示得到了一个事务,可能是新创建的一个事务,也可能是拿到的已有的事务 return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); } @@ -673,8 +694,10 @@ protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "] after exception: " + ex); } + // rollbackOn 获取回滚规则:可以自定义设置回滚规则,默认会判断 RuntimeException 和 Error!!!!! if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) { try { + // TODO 回滚事务 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } catch (TransactionSystemException ex2) { @@ -784,7 +807,7 @@ public boolean hasTransaction() { private void bindToThread() { // Expose current TransactionStatus, preserving any existing TransactionStatus // for restoration after this transaction is complete. - this.oldTransactionInfo = transactionInfoHolder.get(); + this.oldTransactionInfo = transactionInfoHolder.get(); // 第一次进来是 NULL transactionInfoHolder.set(this); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java index d388850da43b..00b4a60a85b1 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAttributeSourcePointcut.java @@ -79,13 +79,21 @@ public String toString() { */ private final class TransactionAttributeSourceClassFilter implements ClassFilter { + /** + * 判断是否事务内部类,如果是,返回 false; + * + * @param clazz the candidate target class + * @return + */ @Override public boolean matches(Class clazz) { + // 是否为事务内部类 if (TransactionalProxy.class.isAssignableFrom(clazz) || TransactionManager.class.isAssignableFrom(clazz) || PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) { return false; } + // 判断某个类是否存在 @Transaction 注解【org.springframework.transaction.annotation.SpringTransactionAnnotationParser.isCandidateClass】 return (transactionAttributeSource == null || transactionAttributeSource.isCandidateClass(clazz)); } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java index 788c1f251994..a7adceaf03b6 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionInterceptor.java @@ -32,6 +32,8 @@ import org.springframework.transaction.TransactionManager; /** + * Spring 事务拦截器的核心业务实现,AOP 调用链也最终触发它的 invoke() 方法 + * * AOP Alliance MethodInterceptor for declarative transaction * management using the common Spring transaction infrastructure * ({@link org.springframework.transaction.PlatformTransactionManager}/ @@ -107,6 +109,13 @@ public TransactionInterceptor(PlatformTransactionManager ptm, Properties attribu } + /** + * 获取目标类 + * + * @param invocation the method invocation joinpoint + * @return + * @throws Throwable + */ @Override @Nullable public Object invoke(MethodInvocation invocation) throws Throwable { @@ -115,11 +124,13 @@ public Object invoke(MethodInvocation invocation) throws Throwable { // as well as the method, which may be from an interface. Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null); + // TODO 开始调用父类方法,触发事务 // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() { @Override @Nullable public Object proceedWithInvocation() throws Throwable { + // 执行后续的 Interceptor,以及被代理的方法 return invocation.proceed(); } @Override diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java b/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java index 5b3a68c71f50..848109a7511f 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/AbstractPlatformTransactionManager.java @@ -37,6 +37,8 @@ import org.springframework.transaction.UnexpectedRollbackException; /** + * 负责实现整个事务管理和运行过程中的公共行为和通用实现逻辑 + * * Abstract base class that implements Spring's standard transaction workflow, * serving as basis for concrete platform transaction managers like * {@link org.springframework.transaction.jta.JtaTransactionManager}. @@ -344,6 +346,7 @@ public final TransactionStatus getTransaction(@Nullable TransactionDefinition de // Use defaults if no transaction definition given. TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults()); + // 得到一个新的 DataSourceTransactionManager 对象,包含链接、数据源、缓存等信息 Object transaction = doGetTransaction(); boolean debugEnabled = logger.isDebugEnabled(); @@ -362,14 +365,20 @@ public final TransactionStatus getTransaction(@Nullable TransactionDefinition de throw new IllegalTransactionStateException( "No existing transaction found for transaction marked with propagation 'mandatory'"); } + // 在当前 Thread 中没有事务的前提下,以下三个是等价的 else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { + // 没有事务需要挂起,不过 TransactionSynchronization 有可能需要挂起 + // suspendedResources 表示当前线程被挂起的资源持有对象(数据库连接、TransactionSynchronization) + // 没有事务为啥还要挂起?啥都没干????? SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def); } try { + // 开启一个新事务,transaction 中就会有数据库连接了,并且 isTransactionActive 为 true + // 并返回一个新的 TransactionStatus 对象,该对象保存了很多信息,包括被挂起的资源 return startTransaction(def, transaction, debugEnabled, suspendedResources); } catch (RuntimeException | Error ex) { @@ -567,6 +576,7 @@ protected int determineTimeout(TransactionDefinition definition) { */ @Nullable protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException { + // 判断事务管理器是否同步激活状态:根据另一个 ThreadLocal 对象是否有值判断,第一次进来当前为空,因此是未激活状态 if (TransactionSynchronizationManager.isSynchronizationActive()) { List suspendedSynchronizations = doSuspendSynchronization(); try { @@ -583,7 +593,7 @@ protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) t boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive(); TransactionSynchronizationManager.setActualTransactionActive(false); return new SuspendedResourcesHolder( - suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); + suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive); // // 挂起事务之后,新建一个 挂起事务上下文对象 } catch (RuntimeException | Error ex) { // doSuspend failed - original transaction is still active... @@ -591,12 +601,15 @@ protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) t throw ex; } } + // 当前事务不为空 else if (transaction != null) { + // 事务管理器是非同步激活状态,但事务是激活状态,就挂起当前事务 // Transaction active but no synchronization active. Object suspendedResources = doSuspend(transaction); return new SuspendedResourcesHolder(suspendedResources); } else { + // 事务未激活,事务管理器也是非同步激活状态 // Neither transaction nor synchronization active. return null; } @@ -693,6 +706,8 @@ public final void commit(TransactionStatus status) throws TransactionException { } DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status; + // 可以通过 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 来设置 + // 事务本来是可以要提交的,但是可以强制回滚 if (defStatus.isLocalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Transactional code has requested rollback"); @@ -701,6 +716,7 @@ public final void commit(TransactionStatus status) throws TransactionException { return; } + // 判断此事务在之前是否设置了需要回滚,跟 globalRollbackOnParticipationFailure 有关 if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) { if (defStatus.isDebug()) { logger.debug("Global transaction is marked as rollback-only but transactional code requested commit"); diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java index 85cd680ad8b1..677525b36a0c 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionSynchronizationManager.java @@ -73,6 +73,9 @@ */ public abstract class TransactionSynchronizationManager { + /** + * TODO 存储连接上下文 + */ private static final ThreadLocal> resources = new NamedThreadLocal<>("Transactional resources"); @@ -167,7 +170,7 @@ private static Object doGetResource(Object actualKey) { public static void bindResource(Object key, Object value) throws IllegalStateException { Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key); Assert.notNull(value, "Value must not be null"); - Map map = resources.get(); + Map map = resources.get(); // 第一次进来结果是 NULL【如果前面事务被挂起,这里获取的也是 NULL】 // set ThreadLocal Map if none found if (map == null) { map = new HashMap<>(); diff --git a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionTemplate.java b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionTemplate.java index 87c37645eaf4..01ed3bdb7cbb 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/support/TransactionTemplate.java +++ b/spring-tx/src/main/java/org/springframework/transaction/support/TransactionTemplate.java @@ -117,6 +117,9 @@ public PlatformTransactionManager getTransactionManager() { return this.transactionManager; } + /** + * 只是校验了事务管理器不为空 + */ @Override public void afterPropertiesSet() { if (this.transactionManager == null) { @@ -134,21 +137,27 @@ public T execute(TransactionCallback action) throws TransactionException return cpptm.execute(this, action); } else { + // TODO 创建事务【与声明事务调用同一个方法】 TransactionStatus status = this.transactionManager.getTransaction(this); T result; try { + // 执行业务逻辑,这里就是用户自定义的业务代码。如果没有返回值的,就是 doInTransactionWithoutResult(); result = action.doInTransaction(status); } catch (RuntimeException | Error ex) { + // 应用运行时异常/错误异常-->回滚,调用 AbstractPlatformTransactionManager#rollback():事务提交回滚 + // TODO 回滚【与声明事务调用同一个方法】 // Transactional code threw application exception -> rollback rollbackOnException(status, ex); throw ex; } catch (Throwable ex) { + // 未知异常-->回滚,调用 AbstractPlatformTransactionManager#rollback():事务提交回滚 // Transactional code threw unexpected exception -> rollback rollbackOnException(status, ex); throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception"); } + // TODO 事务提交【与声明事务调用同一个方法】 this.transactionManager.commit(status); return result; }