# spring-demo **Repository Path**: zoranwang/spring-demo ## Basic Information - **Project Name**: spring-demo - **Description**: spring框架学习记录 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-03-16 - **Last Updated**: 2022-06-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Spring知识点积累 ## 一、Spring概述 ### 1.1 简介 - Spring是一个开源框架 - Spring为简化企业级开发而生,使用Spring开发可以将Bean对象、Dao组件对象、Service组件对象等交给Spring容器来管理,这样使得很多复杂的代码在Spring中开发变得非常优雅和简洁,大大**降低了代码的耦合度** ,极大的方便项目的**后期维护、升级和扩展** - Spring是一个IOC(DI)和AOP容器框架 - 优良特性: - Spring开发的应用中,对象可以不依赖Spring的API - 控制反转:IOC,将对象的创建权交给Spring框架 - 依赖注入:DI,依赖不需要手动调用setXXX方法设置,而是通过赋值配置(xml配置或者注解配置即可) - 面向切面:AOP - 容器:Spring是一个容器,包含并且管理应用对象的生命周期 - 组件化:Spring中,可以使用简单的组件配置合成一个复杂的应用,使用XML或者Java注解组合这些对象 - 一站式:Spring框架可以整合各种企业应用的开源框架和第三方类库 ### 1.2 Spring框架模块介绍 Spring框架分为四大模块 Core核心模块,负责管理组件的Bean对象 - spring-beans - spring-context - spring-core - spring-expression AOP面向切面编程模块 - spring-aop - spring-aspects 数据库操作模块 - spring-jdbc - spring-orm - spring-oxm - spring-tx - spring-jms Web模块 - spring-web - spring-webmvc - spring-websocket - spring-webmvc-portlet ## 二、HelloWorld工程 > 代码见`spirng-helloworld` ### 2.1 通过IOC容器创建对象,并为属性赋值 创建一个普通的Java项目 需要的jar包: - junit_4.12.jar - org.hamcrest.core_1.3.0.jar - spring-beans-5.2.5.RELEASE.jar - spring-context-5.2.5.RELEASE.jar - spring-core-5.2.5.RELEASE.jar - spring-expression-5.2.5.RELEASE.jar - spring-jcl-5.2.5.RELEASE.jar 创建Spring配置文件 - 在**src根目录**,右键 -> New -> XML configuration File -> Spring Config 创建Bean对象 在applicationContext.xml配置文件中使用bean标签注入对象 测试类测试 - 获取Spring容器对象,由接口ApplicationContext表示 - 从容器中获取Bean对象 ## 三、IOC > 代码见`spring-ioc&di`,依赖和`spring-helloworld`一致 ### 3.1 简介 - IOC(Inverse Of Control) 控制反转:不用自己new对象,对象的创建交给Spring容器负责 - DI(Dependency Injection)依赖注入:依赖不需要手动调用setXXX方法设置,而是通过赋值配置(xml配置或者注解配置即可) ### 3.2 IOC容器获取对象的方式 #### 3.2.1 通过id获取对象 - 返回Object,需要强转 - Bean默认在创建Application容器时一起创建 - 默认**单例**,线程不安全,可以使用多例或者ThreadLocal解决 #### 3.2.2 通过类型获取对象 - 如果通过class类型找到唯一一个,就返回 - 如果没有找到或者找到多个,报错 #### 3.2.3 通过id和类型获取对象 - `applicationContext.getBean`时指定id和类型即可 ### 3.2 DI依赖注入 #### 3.2.1 getXxx方法注入 配置文件中,在`property`标签内,使用`name`和`value`注入 p名称空间(p名称空间可以以非常简短的形式通过调用setXxx方法给属性赋值) 1. 在`applicationContext.xml`配置文件的头文件中加上`xmlns:p="http://www.springframework.org/schema/p"`即可启用p名称空间 2. 使用:在`bean`标签内使用`p:xxx="xxx"`注入属性值,简化`property`标签 ```xml ``` `null`值的处理 ```xml ``` #### 3.2.2 构造方法注入 通过构造方法形参名注入 通过index注入 构造方法注入可以指定类型 #### 3.2.3 工厂方法注入bean 工厂方法注入 ```java public class PersonFactory { public static Person createPerson1() { return new Person(11, "工厂方法创建的对象", "2222", 104); } public Person createPerson2() { return new Person(12, "工厂方法创建的对象", "1111", 104); } } ``` 配置类注入 - 无论是静态方法注入还是普通方法注入,都默认单例, 工厂静态方法注入对象直接注入静态方法 ```xml ``` 工厂普通方法注入对象,先注入工厂,再注入对象 ```xml ``` ```xml ``` #### 3.2.4 FactoryBean接口方式创建对象 创建FactoryBean接口的实现类,重写方法 ```java public class PersonFactoryBean implements FactoryBean { /** * 创建Bean对象时创建的方法 * @return * @throws Exception */ @Override public Object getObject() throws Exception { return new Person(66, "FactoryBean创建的对象", "123456566", 16); } /** * 获取对象Class类型的方法 * @return */ @Override public Class getObjectType() { return Person.class; } /** * 是否为单例 * @return */ @Override public boolean isSingleton() { return true; } } ``` 配置 ```xml ``` #### 3.2.5 Bean对象的继承 - 通过继承实现bean对象配置信息的复用 - 子bean赋值会覆盖掉父bean的值 ```xml ``` #### 3.2.6 抽象Bean - 抽象bean只能用于继承,不能被实例化 - 使用:在`bean`标签内加`abstract="true"`即可 #### 3.2.7 对不同类型属性的赋值 对自定义JavaBean(内部Bean)属性的赋值 - 在`property`标签内写`bean`标签即可 - 内部`bean`不能被IOC容器直接获取 ```xml ``` 对List属性的赋值(list或者array标签) ```xml 华为 荣耀 一加 ``` 对Map属性的赋值(map标签) ```xml ``` 对Properties属性的赋值 #### 3.2.8 util名称空间 util名称空间,可以定义全局公共的集合信息,方便容器直接获取,或者给属性赋值使用 ```xml 1111 2222 3333 ``` 异常:通配符的匹配很全面,但是无法找到元素`util:list`的声明 - 原因:没有正确配置utils的引用资源 - 解决:在`schemaLocation`中加两个地址 - `http://www.springframework.org/schema/util` - `http://www.springframework.org/schema/util/spring-util-4.0.xsd` 级联属性赋值 - 如果要使用级联属性赋值,要先给上级对象赋值 ```xml ``` #### 3.2.9 IOC容器中bean对象的创建顺序 - 在Spring容器中,Bean对象的创建顺序默认是他们在配置文件中,从上到下的顺序决定 - 可以在`bean`标签中使用`depend-on`指定bean创建的顺序 ```xml ``` 结果: ``` B的无参构造器... C的无参构造器... A的无参构造器... ``` #### 3.2.10 bean的单例和多例 (略) #### 基于xml配置文件的自动注入 > 可以参考`spring-ioc&di`工程的`context-autowire.xml`配置文件 自动注入指的是按照某种指定的算法,自动给子对象赋值 使用`autoware`属性设置一种自动注入的算法 - `default`和`no`都表示不自动赋值子对象 - `byName`表示按子对象的属性名作为`id`,在容器中查找并注入 - 如果找到就赋值,没有找到就为`null` - `byType`表示按子对象的类型在容器中查找并注入 - 找到一个就注入 - 找到多个就报错 - 没有找到就位`null` - `constructor`表示按构造器参数进行查找并注入 - 先按类型找,再按属性名找,如果找不到或者指代不明,就为`null` ### 3.3 Bean对象的生命周期 spring容器中,单例对象的声明周期和spring容器的声明周期一致;而多例对象不随着spring的创建而创建,而是被使用时才会创建 无论是`bean`的自定义初始化方法和销毁方法,还是spring容器中的自定义后置处理器方法,都是针对于`bean`对象而言的生命周期,不是spring容器的生命周期 `pojo`类的初始化方法和销毁方法 - 初始化方法一般用于初始化赋值,销毁方法一般用于资源释放(容器关闭时调用,只对**单例**有效) ```java public void init(){ System.out.println("init..."); } public void destroy(){ System.out.println("destroy..."); } ``` ```xml ``` `BeanPostProcessor`后置处理器的实现类 - 在每个对象创建开始和结束时调用,对每个对象都有作用 ```java public class MyBeanPostProcessor implements BeanPostProcessor { /** * 对象初始化前方法,对象初始化后执行 * * @param bean 当前的初始化对象实例 * @param beanName 当前初始化对象的id值 * @return * @throws BeansException */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("后置处理器器初始化..." + "," + bean + "," + beanName); return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName); } /** * 对象初始化后方法,对象初始化后执行 * * @param bean * @param beanName * @return * @throws BeansException */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("后置处理器销毁..." + "," + bean + "," + beanName); return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); } } ``` 以上四个方法的顺序: 1. 后置处理器对象初始化前方法(bean创建时,可以获取到bean对象和id,需要把自定义的后置处理器注入到spring容器中,对每个bean对象都起作用) 2. `bean`对象自定义`init`方法(bean创建时,可以用于对象属性的初始化,需要在注入时配置`init-method`) 3. 后置处理器对象初始化后方法(bean创建完成时) 4. `bean`对象自定义`destroy`方法(单例对象在容器销毁时调用) ## 四、Spring管理数据库连接池 需要新增的依赖: - druid-1.1.9.jar - mysql-connector-java-5.1.37-bin.jar ### 4.1 数据库连接池 配置文件`jdbc.properties` ```properties username=zoran password=zoran driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://192.168.149.128:3306/spring?characterEncoding=UTF-8 ``` 配置数据库连接池 ```xml ``` ```xml ``` ### 4.2 JdbcTemplate 略,使用mybatis取代 ## 五、Spring EL表达式 ### 5.1 语法 用于创建`bean`对象时,为`bean`对象赋一些比较复杂的值 基本语法: SpEL使用`#{…}`作为定界符,所有在大框号中的字符都将被认为是SpEL表达式 Spring EL字面量 - 整数:#{8} - 小数:#{8.8} - 科学计数法:#{3e5} - 字符串(单引号或者双引号):#{'111'} - 布尔类型:#{true} 支持的运算符 - 算术运算符、字符串连接 - 比较运算符 - 逻辑运算符 - 三目运算符 - 正则表达式:matches ### 5.2 场景 引用其他`bean` ```xml ``` 引用其他`bean`的属性值 ```xml ``` 调用非静态方法 ```xml ``` ```xml ``` 调用静态方法 ```xml ``` ## 六、注解 引入支持AOP的依赖: - spring-aop-5.2.5.RELEASE.jar - spring-aspects-5.2.5.RELEASE.jar ### 6.1 常用注解 注解 | 含义 --- | --- `@Component` | 配置Web层,Servcie层,DAO层之外的Bean对象 `@Controller` | 配置Web层的组件 `@Service` | 配置Service层的组件 `@Repository` | 配置DAO组件 `Scope` | 配置Bean的作用域(单例,多例),用于类上或者有`@Bean`的方法上 `@PostConstruct` | init-method `@PreDestory` | destroy-method `@Value("abc")` | 给基本类型注入 `@Value("${user}")` | 注入配置文件的值 `@Value("${user:root}")` | 注入配置文件的user,如果没有user,注入root `@Configuration` | 表明当前类是一个配置文件类 `@ComponentScan(basePackages={})` | 扫描其他包下的注解 `@Import` | 导入其他配置文件类 `@Bean` | 注入当前方法的返回值,默认Id是方法名 ### 6.2 扫描包和排除组件 扫描注解包 ```xml ``` 指定扫描包时包含的类和不包含的类(不常用) 注意排除的是有指定注解和其**子注解**、指定类和其**子类**的组件类 ```xml ``` ```xml ``` `type="annotation"`可以替换成如下值 类别 | 示例 | 说明 --- | --- | --- annotation | XxxAnnotation | 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤 assignafble | BaseXxx | 过滤所有BaseXxx类的子类,这个规则根据目标组件是否是指定类型的子类的方式进行过滤 aspectj | zoran.wang.*Service+ | 所有类名是以Service结束的,或这样的类的子类,这个规则根据AspectJ表达式进行过滤 regex | zoran.wang.anno.* | 所有zoran.wang.anno包下的类,这个规则根据正则表达式匹配到的类名进行过滤 custom | zoran.wang.XxxTypeFilter | 使用XxxTypeFilter类通过编码的方式自定义过滤规则,该类必须实现org.springframework.core.type.filter.TypeFilter接口 ### 6.3 自动装配 使用`@Autowired`注解实现自动装配 注入规则: 1. 先根据类型注入 2. 如果有多个类型, 根据`id`注入 当类型匹配到多个且`id`都不匹配时,可以使用组合注解`@Qualifier`指定`bean`对象的`id` ```java @Autowired @Qualifier(value = "personService") private PersonService personService; ``` ### 6.4 Spring的专有测试 引入依赖: - spring-test-5.2.5.RELEASE.jar Spring整合junit简化测试,在扩展的Junit类中,有一个Spring容器,不再需要我们自己去实现这个容器 使用注解`@ContextConfiguration`指定配置文件路径,使用注解`@RunWith指定Spring扩展的测试类` 使用Spring提供的扩展Junit测试,可以使用Spring的依赖注入功能 ```java @ContextConfiguration(locations = "/applicationContext.xml") @RunWith(SpringJUnit4ClassRunner.class) public class SpringJunitTest { @Autowired private Person person; @Test public void test01() { System.out.println("获取到了 === " + person); } } ``` ## 七、AOP切面编程 ### 7.1 jdk动态代理和Cglib动态代理 #### 7.1.1 jdk动态代理 jdk动态代理 - 目的:动态修改class字节码 - 功能:增强(前置增强、后置增强、异常增强、返回增强) 注意使用jdk动态代理,需要有接口 示例代码: 接口: ```java public interface Calculation { /** * 相加 * @param a * @param b * @return */ int add(int a, int b); /** * 除法 a/b * @param a * @param b * @return */ int div(int a, int b); } ``` 实现类: ```java public class Calculator implements Calculation { @Override public int add(int a, int b) { return a + b; } @Override public int div(int a, int b) { return a / b; } public int add_3(int a, int b, int c) { return a + b + c; } } ``` 代理类: ```java public class CalculatorProxy { public static Object createJdkProxyInstance(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { LogUtils.logBefore(method.getName(), args); Object result = null; try { if (args.length > 0) { result = method.invoke(target, args); } else { result = method.invoke(target); } LogUtils.logAfterReturning(method.getName(), result); } catch (Exception e) { LogUtils.logAfterThrowing(method.getName(), e); throw new RuntimeException(e); } return result; } } ); } } ``` 测试: ```java public class CalculateTest { public void test01() { //1. 使用jdk动态代理,必须要接口 Calculator target = new Calculator(); //2. 创建动态代理实例 Calculation jdkProxyInstance = (Calculation) CalculatorProxy.createJdkProxyInstance(target); result = jdkProxyInstance.div(1, 1); System.out.println("result = " + result); } } ``` #### 7.1.2 Cglib动态代理 Cglib动态代理不需要对象有实现接口 Cglib动态代理是通过修改目标对象的字节码程序产生一个子类,生成一个代理对象实例 Cglib产生的代理对象是目标对象的子类 实现类`Calculator`和jdk动态代理一样 Cglib代理工厂类: ```java public class CglibProxyFactory { public static Object createCglibProxy(Object target) { //1. 增强器,负责产生一个Cglib代理对象实例 Enhancer enhancer = new Enhancer(); //2. 指定目标对象 enhancer.setSuperclass(target.getClass()); //3. 设置方法拦截器 enhancer.setCallback(new MethodInterceptor() { /** * 只要代理对象方法调用,就会执行intercept方法 * @param proxy 代理对象实例 * @param method 调用方法的反射对象 * @param args 调用方法时传递的参数 * @param methodProxy 方法反射对象的代理对象 * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //前置增强 LogUtils.logBefore(method.getName(), args); Object result = null; try { //通过反射调用方法 result = method.invoke(target, args); LogUtils.logAfterReturning(method.getName(), result); } catch (Exception e) { LogUtils.logAfterThrowing(method.getName(), e); throw new RuntimeException(e); } return result; } }); //返回Cglib代理对象的实例 return enhancer.create(); } } ``` 测试: ```java public class CalculateTest { public static void main(String[] args) { //测试Cglib动态代理 Calculator cglibProxy = (Calculator) CglibProxyFactory.createCglibProxy(target); //add_3是Calculator独有的方法 result = cglibProxy.add_3(1, 2, 3); System.out.println("result = " + result); } } ``` ### 7.2 Spring AOP #### 7.2.1 术语 - 通知(Advice):增强的代码(前置、后置、异常增强的代码) - 切面(Aspect):包含通知代码的类 - 横切关注点:添加通知的位置 - 目标(target):被代理的对象 - 代理(proxy):代理对象 - 连接点(Joinpoint):横切关注点和程序代码的连接 - 切入点(pointcut):用户真正处理的连接点 #### 7.2.2 使用Spring AOP实现简单的切面编程 > 模块`spring-springaop` 实现Spring AOP编程,需要 - Spring配置文件开启切面生效配置 - 切面(写通知代码的类) - 切入点表达式(指定真正处理的方法) - 连接点JoinPoint(也可以无参) spring配置文件 ```xml ``` 切面,即写通知代码的类,注意`@Before`下方法的参数 ```java @Component @Aspect//切面类,包含通知代码的类 public class LogUtils { @Pointcut(value = "execution(* zoran.wang.calculate.impl.Calculator.*(..))") private void pointcut(){}; //也可以把pointcut直接写到这里 @Before("pointcut()") public static void before(){ System.out.println("before..."); } } ``` PointCut切入点表达式语法格式: ``` execution(访问权限 返回值类型 方法全限定名(参数类型列表)) 在spring中只有public权限能拦截到,访问权限可以省略 ``` 连接点JoinPoint - 获取连接点信息:`joinPoint` - 获取方法名 `joinPoint.getSignature().getName()` - 获取参数 `joinPoint.getArgs()`,日志中可以写为`Arrays.asList(joinPoint.getArgs())` - 获取返回值 ```java @AfterReturning(value = "pointcut()", returning = "result") public static void logAfterReturning(JoinPoint joinPoint, Object result) { //... } ``` - 获取异常信息 ```java @AfterThrowing(value = "pointcut()", throwing = "e") public static void logAfterThrowing(JoinPoint joinPoint, Exception e) { //... } ``` 执行顺序: 前置通知 -> 执行方法 -> 后置通知 -> 返回通知或者异常通知 #### 7.2.3 环绕通知 - 使用`@Around`表示环绕通知 - 环绕通知需要使用`ProceedingJoinPoint`执行目标方法 - 环绕通知需要返回方法的返回值 - 环绕通知的异常一定要往外抛,否则普通的异常通知不执行 - 环绕通知比普通通知优先执行 ![通知执行顺序](https://zoran-pictures.oss-cn-hangzhou.aliyuncs.com/Typora/202203211604279.png) 日志打印: ``` 环绕前置...方法名:add,参数:[1, 1] 前置通知...方法名:add,参数:[1, 1] 环绕后置...方法名:add,参数:[1, 1] 环绕返回...方法名:add,参数:[1, 1],返回值:2 后置通知...方法名:add,参数:[1, 1] 返回通知...方法名:add,参数:[1, 1],返回值:2 ``` 多个切面的执行顺序 - 在切面类上使用注解`@Order`,值越小,越先执行 ### 7.3 纯xml配置spring aop > 模块:`spring-aop-xml` ```xml ``` ### 7.4 纯注解配置spring aop ```java @Configuration @ComponentScan(basePackages = {"zoran.wang"}) /*** proxyTargetClass = true:cglib * false:根据有无接口判断 */ @EnableAspectJAutoProxy(proxyTargetClass = true) public class AopConfig { } ``` ## 八、事务 略,spring中的事务在`mybatis-demo`项目中演示 ## 九、Spring整合Web工程 略