Spring基础知识
Spring快速入门
- ioc(控制反转):
- 概念:对象的控制权交由第三方管理
- 含义理解:比如到一个城市工作,面临的第一个问题就是找房子居住。此时有两种方式,第一种就是自己直接去找房东,这个过程需要自己花费很多时间和精力去搜集信息;第二种方式是找中介,通过房屋位置、价格区间可以快速选择到自己要租住的房子,高效便捷,但是付出的代价是半个月或者一个月的房租。其中找中介的过程,就是把搜寻房屋信息的事情交由专业的中介处理,可以类比控制反转概念的理解;
- inverse of control,是一种设计理念,不是一种具体的技术;
- 由代理人创建和管理对象,消费者通过代理人来获取对象;
- IOC的目的是为了降低对象之间的耦合;
- 加入IOC容器将对象统一管理,将对象关联变为弱耦合
- 依赖对象不直接去实例化被依赖的对象,而是直接从IOC容器获取
- 概念:对象的控制权交由第三方管理
- 😋DI(Dependency Injection):依赖注入
- IoC是一种设计理念,与具体的语言无关,是现代程序设计遵循的标准,是宏观目标;落实的不同的语言,就有不同的具体实现。依赖注入就是具体的技术实现,是微观实现;
Spring理解
Spring初体验
Maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
配置文件(类路径下):applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="sweetApple" class="com.xinongbest.entity.Apple">
<property name="title" value="软苹果"></property>
<property name="origin" value="金帅"></property>
<property name="color" value="金色"></property>
</bean>
</beans>
启动项目
public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); Apple sweetApple = context.getBean("sweetApple", Apple.class); System.out.println(sweetApple.getTitle()); }
理解:传统的编码习惯中,实例化对象都是通过new关键字完成;如果一个对象的实例化依赖与另一个类对象,就需要先实例化依赖的对象再注入到目标对象的实例化过程中,最终完成一个类的实例化工作。对应所有的操作都需要手动完成,且在代码编译的时候就完成依赖检查,且代码一旦编译就不能动态修改,如果后面需求有变化,则需要重新修改源代码、重新编译、重新测试、重新发布和上线,整个过程毫无敏捷性可言。而Spring IoC容器是类的实例化以及实例化依赖都放在xml配置文件中,在代码运行的时候,利用反射技术动态完成类对象的实例化及解决依赖。如果后期需求修改,则只需要修改配置文件即可。两种操作从展现形式是两种不同的方式达到最终的目的,但是从开发的敏捷性和编程思想的高度上,后者更胜一筹。
对象实例化配置
Spring IOC配置对象实例化的三种方式
构造方法实例化
- 无参构造
- 有参构造(各种重载)
<!-- 无参构造--> <bean id="apple" class="com.xinongbest.entity.Apple"></bean> <!-- 有参构造 --> <bean id="apple1" class="com.xinongbest.entity.Apple"> <constructor-arg name="title" value="红富士"></constructor-arg> <constructor-arg name="origin" value="欧洲"></constructor-arg> <constructor-arg name="color" value="红色"></constructor-arg> </bean>
静态工厂实例化
- 描述:通过工厂的静态方法实例化对象
<!-- 静态工厂 --> <bean id="apple2" class="com.xinongbest.entity.AppleFactory" factory-method="getInstance"></bean>
- 描述:通过工厂的静态方法实例化对象
工厂方法实例化
- 描述:通过工厂类的方法实例化对象(工厂方法中依旧是通过new关键字做一下操作获取需求的类对象,这种情况虽然是使用了new关键字,但是适用于需要在获取类对象前后做一些操作的场景)
<!-- 工厂方法 --> <bean id="factoryBean" class="com.xinongbest.entity.FactoryInstance"></bean> <bean id="apple3" factory-bean="factoryBean" factory-method="instanceFactory"></bean>
- 描述:通过工厂类的方法实例化对象(工厂方法中依旧是通过new关键字做一下操作获取需求的类对象,这种情况虽然是使用了new关键字,但是适用于需要在获取类对象前后做一些操作的场景)
路径表达式
依赖注入配置
- 利用构造方法注入依赖
- 利用setter方法注入依赖
注入集合对象
List
<bean id="xxx" class="zzz"> <property name="someList"> <list> <value>具体的值</value> <ref bean="beanId"></ref> </list> </property> </bean>
Set
<bean id="xxx" class="zzz"> <property name="someSet"> <set> <value>具体的值</value> <ref bean="beanId"></ref> </set> </property> </bean>
Map
<bean id="xxx" class="zzz"> <property name="someMap"> <map> <entry key="k1" value="v1"></entry> <entry key="k2" value-ref="beanId"></entry> </map> </property> </bean>
Properties
<bean id="xxx" class="zzz"> <property name="someProperties"> <props> <entry key="k1">v1</entry> <entry key="k2">v2</entry> <entry key="k3"> <bean class="xxx.yyy.zzz.Aclass"> <constructor-arg name="xxx" value="yyy" /> </bean> </entry> </props> </property> </bean>
获取IOC管理的bean对象
- 通过ApplicationContext接口的getBeanDefinitionNames()方法获取;
- 在IOC配置文件中,定义bean的时候可以不写id或者name属性,则在IOC容器中会自动以bean对象的全限定类名加上同一个类的bean配置书写顺序编号(全限定类名#0);
- 如果一个bean是定义在集合中的,则只IOC容器会认为该bean对象是给特定的类实例化使用,不会在IOC容器提供获取的操作;
- 如果要获取没有定义id或者name属性的bean对象的时候,如果直接使用类对象的全限定类名,则是默认获取对应类对象定义的第一个bean的数据,如果要获取指定的bean则需要全限定类名加上bean定义书写的顺序编号;
- bean定义中的scope
- 含义:定义对应的bean对象何时被创建以及作用范围;bean scope配置将影响容器内对象的数量(单例singleton或者多例)
- singleton(单例):全局共享同一个对象实例;
- Spring IoC容器默认实例化是单例模式,同一个对象可能被多个其他对象引用,但是这个单例不是单线程,是单例多线程,此时就需要考虑线程安全问题;
- 单例的bean是在IoC容器启动的时创建;
- prototype(多例):可以存在多个实例化对象,每一次使用时都是创建一个实例;
- 多例的bean是在调用getBean()或者对象注入的时创建,属于延迟创建方式;
- request:web环境下,每一次独立请求存在唯一实例;
- session:web环境下,每一个会话存在唯一实例;
- application:web环境下,ServletContext存在唯一实例;
- Websocket:每一次websocket连接中存在唯一实例;
- bean scope默认值是singleton(单例);
- singleton(单例):全局共享同一个对象实例;
- 单例和多例的使用场景
- 比如一个对象的实例化依赖与另一个对象,如果该属性是恒定不变,且在运行时也不会发生改变,则使用单例模式,否则使用多例;
- 含义:定义对应的bean对象何时被创建以及作用范围;bean scope配置将影响容器内对象的数量(单例singleton或者多例)
- 对象生命周期
方法配置
- 配置init-method
- 使用场景:比如一个商品类实例化,有三个属性,单价、数量、总价,知道单价和数量就可以计算出总价,此时可以让总价自行计算而不是手动计算 ```java public class Goods { private Double price; private Long num; private Double totalMoney;
public Goods(){}
public void init() { this.totalMoney = price * num; }
public void destroy(){ // 做资源回收操作 }
// setter和getter方法略 }
```xml <bean id="goods1" class="xxx.yyy.zzz.Goods" init-method="init" destroy-method="destroy"> <property name="price" value="10.9"></property> <property name="num" value="1000"></property> </bean>
- 配置destroy-method
- 配置init-method
xml配置、注解、Java config的本质都是一样,只不过是不同实现形式;
注解方式是免去繁琐的xml配置
- 注解分类
- 组件类型注解:声明当前类的功能与职责;
- @Component:组件注解,通用注解,被该注解描述的类将被IoC容器管理并实例化;
- @Controller:语义注解,说明当前类是MVC应用中的控制器类;
- @Service:语义注解,说明当前类是Service业务服务类注解;
- @Repository:语义注解,说明当前类用于业务持久层,通常描述对应的DAO类;
- 自动装配注解:根据属性特征自动注入对象;
- 元数据注解:更细化的辅助IoC容器管理对象的注解;
- 组件类型注解:声明当前类的功能与职责;
- 要使用注解,需开启组件扫描
<!-- 在 applicationContext.xml 文件中增加开启注解扫描 --> <context:component-scan base-package="xxx.yyy.zzz"> <!-- 该标签是作用于排除忽略扫描的包 --> <context:exclude-filter type="regex" expression="xxx.yyy.exl.*" /> </context:component-scan>
- 注解分类
注解实例
配置注解扫描包入口
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> </beans>
在添加注解后,可以设置bean对象的ID,如果不显示设置则按照类名首字母小写的字符串自动命名;
- 两类自动装配注解
- 按类型装配
- @Autowired:按容器内对象类型动态注入属性,由Spring机构提供
- 如果出现一个类型的多个对象,则会出现注入问题,解决方法是增加 @Primary 注解
- @Inject:基于JSR-330(Dependency Injection for Java)标准,其他同@Autowired,但不支持required属性
- @Autowired:按容器内对象类型动态注入属性,由Spring机构提供
- 按名称装配
- @Named:与@Inject配合使用,JSR-330规范,按属性名称自动装配属性
- @Resource:基于JSR-250规范,优先按名称、再按类型智能匹配
- 按类型装配
- 自动装配注意点:
- @Autowired
- 如果自动装配注解写在set方法上,则自动按照类型或者名称对set方法参数进行注入;
- 如果自动装配注解写在属性上,Spring IoC容器会自动通过反射技术将属性private修饰符自动改为public,直接进行赋值,不再执行set方法;
- @Autowired
元数据注解
- @Primary:按类型装配时出现多个相同类型对象,拥有此注解对象优先被注入
- @PostConstruct:相当于xml配置中的init-method属性配置
- @PreDestroy:相当于xml配置中的destroy-method属性配置
- @Scope:设置对象的scope属性
@Value:为属性注入静态数据
- 该注解是使用反射为属性设置值,在使用上和直接给属性赋值是没有区别的,从性能上是没有直接赋值高效;但是该注解的应用场景是把静态值写在配置文件中
- 示例代码 ```java // UserController.java @Controller public class UserController {
@Value(“${metaData}”) private String metaData;
}
```xml
<!-- 通知spring IoC容器初始化时加载属性文件 -->
<!-- metaData的值需要配置在property文件中 -->
<context:property-placeholder location="classpath:config.properties" />
Java Config
Spring Test与JUnit整合
- 优势:不用手动初始化IoC容器
- 整合步骤
- Maven工程增加spring-test依赖;
- 利用@RunWith注解和@ContextConfiguration描述测试用例类;
- 测试用例类从容器获取对象完成测试用例的执行
示例代码 ```java @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {“classpath:applicationContext.xml”}) public class SpringTest {
@Resource private Apple sweetApple;
@Test public void testDemo() { String color = sweetApple.getColor(); System.out.println(color); }
}
<a name="p09Op"></a>
#### Spring-AOP入门
- aop简介和相关名词理解
- aspect oriented programming:面向切面编程
- aop的做法是将**通用**的、**与业务无关**的功能抽象封装为**切面**,切面可配置在目标方法执行前后运行,真正做到即插即用,做到在不修改源码的情况下对程序行为进行扩展;
- Eclipse AspectJ,一种基于Java平台的面向切面编程的语言;
- Spring AOP使用的是AspectJWeaver实现类与方法匹配;
- Spring AOP利用**_代理模式实现对象运行时功能扩展__;_**

- spring-aop开发和配置流程
- 配置流程
- 依赖AspectJ:pom文件中增加依赖aspectjweaver
- 实现切面类/方法
- 配置Aspect Bean
- 定义PointCut
- 配置Advice
```xml
<!-- 配置切面类,依赖spring ioc -->
<bean id="methodAspect" class="com.aop.aspect.MethodAspect" />
<!-- 增加aop配置 -->
<aop:config>
<!-- 增加切点,使用切点表达式 -->
<aop:pointcut id="pointcut" expression="execution(public * com.aop..*.*(..))"></aop:pointcut>
<!-- 配置切面 -->
<aop:aspect ref="methodAspect">
<!-- 配置前置通知 -->
<aop:before method="printExecutionTime" pointcut-ref="pointcut" />
</aop:aspect>
</aop:config>
- JointPoint:连接点类
- 切面是一个可插拔的组件功能类,通常一个切面只实现一个通用功能
- 切面类的方法必须是public的,且返回值类型要么是void,要么是Object,且必须有一个JointPoint类型的参数;
- JointPoint类的三个重要的方法
- Object getTarget()
- Signature getSignature()
- Object[] getArgs()
- execution表达式
- 完整方法的表达式
- 格式:权限修饰符 返回值类型 包名称.类名称.方法名称(参数)
- 表达式梳理
- * 表示通配符
- .. 表示包
- (..) 参数统配符,不限参数和个数
<aop:pointcut id="pointcut" expression="execution(public * com.aop..*.*(..))"></aop:pointcut>
- 表达式练习
<!-- 无返回值的方法 -->
<aop:pointcut id="pointcut" expression="execution(public void com.aop..*.*(..))"></aop:pointcut>
<!-- 对类名是以Service结尾的类的方法 -->
<aop:pointcut id="pointcut" expression="execution(public * com.aop..*Service.*(..))"></aop:pointcut>
<!-- 对方法名是以create开头的方法 -->
<aop:pointcut id="pointcut" expression="execution(public void com.aop..*.create*(..))"></aop:pointcut>
<!-- 无参数的方法 -->
<aop:pointcut id="pointcut" expression="execution(public void com.aop..*.*())"></aop:pointcut>
<!-- 有两个参数的方法 -->
<aop:pointcut id="pointcut" expression="execution(public void com.aop..*.*(*,*))"></aop:pointcut>
<!-- 有两个参数,且第一参数是字符串类型的方法 -->
<aop:pointcut id="pointcut" expression="execution(public void com.aop..*.*(String,*))"></aop:pointcut>
- spring五中通知类型和应用场景
- 通知的概念:简单理解为spring什么时候执行切面的方法;
- 5种通知:前置通知、返回后通知、异常通知、后置通知、环绕通知
- 返回后通知、后置通知、异常通知执行的顺序是与xml配置中配置的顺序有关系;
- 配置切面类的返回后通知方法需要增加一个Object类型的参数;
- 配置切面类的异常通知方法需要增加一个Throwable类型的参数;
- 扩展:引介增加(IntroductionInterceptor),是对类的增加,而非方法;
- 引介增强允许在运行时为目标类增加新属性或方法;允许在运行时改变类的行为,让类随运行环境动态变更;
注解形式的aop配置
在注解配置Ioc的基础上增加开启aop注解形式
<context:component-scan base-package="xxx.xxx" /> <aop:aspectj-autoproxy/>
配置切面类
- 类注解:@Component、@Aspect
- 方法注解:@Around(“切面表达式”)
- 示例代码
@Component @Aspect public class MethodAspect { @Around("execution(public * com.ioc..*.*(..))") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { try { long start = new Date().getTime(); Object proceed = joinPoint.proceed(); long end = new Date().getTime(); long duration = end - start; if (duration >= 1000) { String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); System.out.println("-------->" + className + "." + methodName + "方法执行时间:" + duration + "ms"); } return proceed; } catch (Throwable throwable) { throw throwable; } } }
代理模式与静态代理
- 代理模式理解
- Spring基于代理模式实现功能动态扩展,包含两种形式:
- 目标类拥有接口,通过JDK动态代理实现功能扩展;
- 目标类没有接口,通过CGLib组件实现功能扩展;
- 代理模式
- 通过代理对象对原对象功能实现扩展;
- 代理类持有委托类对象;
- 代理类和委托类实现相同的接口;
- 举例:房东和中介的关系;房东和中介共同的目标(实现同一个接口)是把房子租出去,中介替房东联系租房者、看房等操作;
- 静态代理:需要手动完成代理类的编写
- JDK1.2以后,通过反射,按照委托类实现的接口在运行时自动生成代理类;
- 示例代码 ```java // 代理类 package com.proxy;
- 通过代理对象对原对象功能实现扩展;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date;
/**
- InvocationHandler是JDK提供的反射类,用于在JDK动态代理中对目标方法进行增强
InvocationHandler实现类于切面类的环绕通知类似 */ public class ProxyInvocationHandler implements InvocationHandler {
/* 目标对象 / private Object target;
public ProxyInvocationHandler(Object target) {
this.target = target;
}
/**
- 在invoke方法中对目标对象方法进行增强
- @param proxy 代理对象
- @param method 目标对象方法
- @param args 目标对象方法参数
- @return 目标对象方法运行后返回值
- @throws Throwable 目标对象方法抛出的异常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“代理方法执行” + new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss SSS”).format(new Date()));
Object ret = method.invoke(target,args);
return ret;
}
}
import com.proxy.service.UserService; import com.proxy.service.impl.UserServiceImpl;
import java.lang.reflect.Proxy;
public class ProxyApplication { public static void main(String[] args) { // 动态代理必须实现了接口才能使用,否则一定报错 UserService userService = new UserServiceImpl(); UserService proxyInstance = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), new ProxyInvocationHandler(userService)); proxyInstance.createUser(); } }
- CGLib:Code Generation Library(运行时字节码增强技术)
- Spring Aop扩展无接口类使用CGLib,自动生成目标继承类字节码的方式进行行为扩展;
- CGLib实现代理类类名是以目标类名为开头加上两个$符号,再拼接上EnhancerByCGLIB作为类名,继承自需要被代理的类,然后重写父类方法;
<a name="JDBCTemplate"></a>
#### JDBCTemplate
- JDBCTemplate是spring对Java程序访问数据库的一种封装(jdbc),mybatis封装的过于全面,适合快速敏捷开发,JDBCTemplate是封装程度让访问更加便捷,但是留有足够的扩展空间,是介于jdbc和mybatis之间的;
- xml配置
```xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://10.1.1.181:3306/java?useUnicode=true&characterEncoding=utf8" />
<property name="username" value="root" />
<property name="password" value="xxxxx" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="userDao" class="com.jdbc.dao.UserDao">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
package com.jdbc.dao;
import com.jdbc.entity.User;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserDao {
private JdbcTemplate jdbcTemplate;
public User selectById(Integer id) {
String sql = "select user_id as userId,username as userName,password,employee_id as employeeId,salt from sys_user where user_id = ?";
User user = jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<User>(User.class));
return user;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
- 常用方法
- 查询方法
- queryForObject
- query
- queryForList
- 更新、插入、删除方法
- update
- 查询方法
编程式事务
- 概念:通过代码手动提交回滚事务的事务控制方法;JDBCTemplate通过TransactionManager事务管理器实现事务控制;事务管理器提供commit/rollback方法进行事务的提交或者回滚;
- 使用步骤
- 配置bean:配置transactionManager
- 在对应的Service bean中增加事务依赖
- 在业务代码中增加事务控制代码
- 相关类:DefaultTransactionDefinition
public void batchInsert(List<User> list) { DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(definition); try { for (User user : list) { insert(user); } transactionManager.commit(status); } catch (RuntimeException e) { transactionManager.rollback(status); throw e; } }
- 相关类:DefaultTransactionDefinition
声明式事务
- 在不修改源代码的情况下通过配置形式自动实现事务控制,声明式事务的本质就是就是AOP环绕通知;
- 当目标方法执行成功时,自动提交事务;当目标方法抛出运行时异常时,自动回滚事务;
- 配置过程
- 配置事务管理器;
- 用于创建事务、提交或者回滚操作;
- 配置事务通知;
- 决定哪些方法使用事务,哪些方法不使用事务,该配置支持通配符配置;
- 配置声明式事务的作用范围;
- 决定哪些类使用事务;
- 配置事务管理器;
事务传播行为
- 概念:指的是多个拥有事务的方法在嵌套调用时的事务控制方式;
配置方式
XML形式配置
<tx:method name="methodName" propagation="REQUIRED" />
注解形式配置
@Transactional(propagation=Propagation.REQUIRED)
事务传播行为的七中类型
- 注解形式的配置关键的注解是@Transactional,该注解作用在类上则表示该类下的所有方法都按照默认的传播行为执行事务控制;可以在类的方法上增加该注解单独配置事务传播行为,方法上的事务传播行为控制方式会覆盖类上注解指定的事务传播行为;
- 总结
- spring jdbc是spring框架用于处理关系型数据库的模块,该框架是spring对jdbc api进行封装,极大简化开发工作量。template是spring jdbc核心类,提供curd方法;
- spring jdbc使用步骤
- maven工程引入spring jdbc依赖;
- applicationContext.xml配置DataSource数据源;
- 在DAO注入jdbcTemplate对象,实现数据curd;