Spring容器的基本应用
管理bean组件
1、实例化容器
- ClassPathXmlApplicationContext:通过类路径查询
- FileSystemXmlApplicationContext:通过文件路径查询
ApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext fxa = new FileSystemXmlApplicationContext("/src/main/resources/applicationContext.xml");
System.out.println("ca:" + ca);
System.out.println("fxa:" + fxa);
2、配置bean
在applicationContext.xml文件中添加配置:<bean id="标识名" class="bean组件全路径"></bean>
<bean id="deptDAO" class="com.qf.dao.DeptDAOImpl"></bean>
3、getBean
使用ApplicationContext.getBean("标识名") 获取JavaBean对象
ApplicationContext ca = new ClassPathXmlApplicationContext("applicationContext.xml");
DeptDao deptDao = ca.getBean("deptDaoImpl", DeptDao.class);
对象的创建模式
- singleton:单例 每一个spring容器中只定义一个对象实例
- prototype:多例 每调用一次getBean返回一个新的对象
- request:一次交互中,一个实例
- session:一次会话中,一个实例
默认是singleton,可以通过<bean scope="prototype"/> 修改创建模式
对象的创建时机
时机
- singleton模式:容器创建时实例化JavaBean对象
- prototype模式:调用getBean()方法时创建
延迟加载
singleton模式:可以使用<bean lazy-init="true" /> 延迟实例化。当调用getBean时才创建,或者在<bean default-lazy-init="true"> 推迟所以单例bean的创建时机
对象的初始化和销毁
<bean> 元素中添加init-method="" 指定初始化的方法名destory-method="" 指定销毁的方法名- 初始化:在构造方法之后自动执行
- 销毁:在调用容器的close方法之后执行,并且只适用于单例模式的组件中
IOC特性
概念
Inverse of Controller:控制反转
- 控制:对象的创建、初始化、销毁和对象之间关系的指定
- 反转:将控制的逻辑交给第三方框架或者容器负责,当两个组件之间的关系发生改变时,只需要修改框架或者容器的配置
作用
处理组件之间的调用,以低耦合的方式建立组件之间的关系
DI(依赖注入)
Setter注入
-
添加成员变量,并提供set方法 @Data
public class SaveController {
private DeptDao deptDao;
public String execute() {
System.out.println("执行新增操作");
deptDao.add();
return "success";
}
}
-
在applicationContext.xml中配置bean
<bean id="deptDaoImpl" class="dao.impl.DeptDaoImpl" scope="prototype"
init-method="myinit"></bean>
<bean id="saveController" class="controller.SaveController">
<property name="deptDao" ref="deptDaoImpl"></property>
</bean>
-
实现
AbstractApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SaveController acBean = ac.getBean("saveController", SaveController.class);
acBean.execute();
思路:
- 根据applicationcontext.xml文件创建容器
- 在xml文件中查找
id=saveController 的bean,通过class反射找到SaveController文件 - 查找属性为 deptDao 方法,并且查找
id=deptDaoImpl 的bean,给daoDao属性赋值 - 这时daoDao通过反射已经有值,并且通过第二个参数使用泛型得到对象acBean
- acBean调用Dao中的方法
构造方法注入
-
添加成员变量,并提供带参的构造方法(建议也添加无参构造方法) @NoArgsConstructor
@AllArgsConstructor
public class SaveController {
private DeptDao deptDao;
public String execute() {
System.out.println("执行新增操作");
deptDao.add();
return "success";
}
}
-
在applicationContext.xml中配置bean
<bean id="deptDaoImpl" class="dao.impl.DeptDaoImpl" scope="prototype"
init-method="myinit"></bean>
<bean id="saveController" class="controller.SaveController">
<constructor-arg name="deptDao" ref="deptDaoImpl"></constructor-arg>
</bean>
-
实现 注:与setter的实现一致 思路:查找带参数的构造方法,并且查找id=deptDaoImpl 的bean,给daoDao属性赋值
自动装配
-
xml配置
-
写法一:autowire=“byType” 容器中存在一个与属性相同类型的bean(大于1个不行)
<bean id="deptDaoImpl" class="dao.impl.DeptDaoImpl" scope="prototype"
init-method="myinit"></bean>
<bean id="saveController" class="controller.SaveController" autowire="byType"></bean>
备注:set中deptDao属性与 id="deptDaoImpl" 类型相同 -
写法二:
<bean id="deptDao" class="dao.impl.DeptDaoImpl" scope="prototype"
init-method="myinit"></bean>
<bean id="saveController" class="controller.SaveController" autowire="byName"></bean>
备注:set中deptDao属性与 id="deptDaoImpl" 名称相同 -
Controller @Data
public class SaveController {
private DeptDao deptDao;
public String execute() {
System.out.println("执行新增操作");
deptDao.add();
return "success";
}
}
-
实现 与setter注入的实现相同 思路:根据set的属性类型或者名称与xml配置匹配
数据注入
使用set注入
@Data
public class MessageBean {
private String name;
private int age;
private List<String> colors;
private Set<String> cities;
private Map<String, String> books;
private Properties properties;
private List<String> typeList = new ArrayList<String>();
public void setTypeList(String types) {
String[] arr = types.split(",");
Arrays.stream(arr).forEach(type -> {
typeList.add(type);
});
}
public void show() {
System.out.println("基本数据类型:" + name + "\t" + age);
System.out.print("list集合:");
colors.forEach(System.out::print);
System.out.print("set集合:");
cities.forEach(System.out::print);
System.out.print("map集合:");
Set<String> strings = books.keySet();
strings.forEach(Key -> {
System.out.print(Key + ":" + books.get(Key));
});
System.out.print("properties集合:");
Set<Object> objects = properties.keySet();
objects.forEach(obj -> {
System.out.print(obj + "" + properties.getProperty(obj.toString()));
});
System.out.print("入参类型String,成员变量是list:");
typeList.forEach(System.out::print);
}
}
@Data
public class DbBean {
private String username;
private String password;
private String url;
private String driver;
}
基本数据
<property name="name" value="张三"></property>
<property name="age" value="22"></property>
结果:基本数据类型:张三 22
list
<property name="colors">
<list>
<value>black</value>
<value>red</value>
</list>
</property>
结果:list集合:black red
set
<property name="cities">
<set>
<value>南京</value>
<value>北京</value>
</set>
</property>
结果:set集合:南京 北京
map
<property name="books">
<map>
<entry key="1001" value="java基础"></entry>
<entry key="1002" value="k8s"></entry>
</map>
</property>
结果:map集合:1001:java基础 1002:k8s
Properties集合
<property name="properties">
<props>
<prop key="username">root</prop>
<prop key="password">1234</prop>
</props>
</property>
结果:properties集合:password 1234 username root
入参类型String,成员变量是list
<property name="typeList" value="png,jpg"></property>
结果:入参类型String,成员变量是list:png jpg
properties文件信息注入
<bean id="dbBean" class="pojo.DbBean">
<property name="driver" value="#{db.driver}"></property>
<property name="password" value="#{db.password}"></property>
<property name="url" value="#{db.url}"></property>
<property name="username" value="#{db.username}"></property>
</bean>
结果:DbBean(username=root, password=1232456, url=localhost:3306, driver=com.jdbc.mysql.driver)
AOP特性
概念
主要解决的是一对多组件的调用问题,以低耦合的方式指定组件之间的调用关系
- oop:object oriented programming(面向对象编程),关注的是对象,任何封装和抽象行为与特征
- aspect oriented programming(面向方面[切面]编程),以oop为基础,更加关注的是方面,方面组件主要用来封装通用的逻辑,以低耦合的方式切入某一批目标对象中
参数概念
-
aspect:方面 封装共通的功能组件,作用到某一批目标组件上 -
pointcut:切入点 指定目标组件的表达式,方面组件和某一批目标组件的方法有关系 -
joinPoint:连接点 切入点是连接点的集合,方面组件和某一个组件的方法关系 -
advice:通知 指定方面组件和目标组件方法之间的作用时机,比如先执行方面组件在执行目标组件 -
target:目标 利用切入点指定的组件和方法 -
autoProxy:动态代理 Spring采用动态代理的技术实现AOP机制,当使用AOP之后,从容器中getBean() 获取的目标组件,返回一个动态代理类,调用代理类的方法,代理类根据通知类型负责调用方面组件和目标组件的方法 Spring动态代理技术:
-
CGLIB技术:目标组件没有接口实现 public class 代理类 extends 原目标类型{} -
Proxy技术:目标组件有接口实现 public class 代理类 implement 原目标类型{}
通知类型
方面组件在目标组件什么位置执行?
- brfore 前置通知:之前执行
- after-returning 后置通知:之后执行,目标组件没有异常才会执行方面组件
- after-throwing 异常通知:在目标组件抛异常之后执行
- after 最终通知:之后执行,不管是否发生异常
- around 环绕通知:之前和之后执行
try {
前置通知
执行目标方法
后置通知
}catch (){
异常通知
}finally {
最终通知
}
切入点
切入点用于指定目标组件和方法
方法限定表达式
execution(修饰符? 返回类型 方法名(参数列表) throws 异常?)
-
实例1:匹配容器中所有public修饰的方法 execution(public * *(..)) -
实例2:匹配容器中所有set开头的方法 execution(* set*(..)) -
实例3:匹配容器中AccounServlet类中的所有方法 execution(* com.xyz.service.AccountService.*(..)) -
实例4:匹配容器中service包下所有类的所有方法 execution(* com.xyz.service.*.*(..)) -
实例5:匹配容器中service包及其子包中所有类的所有方法 execution(* com.xyz.service..*.*(..))
类型限定表达式
within(类型)
- 实例1:匹配容器中service包下所有类的所有方法
within(com.xyz.service.*) - 实例2:匹配容器中service包及其子包下所有类的所有方法
within(com.xyz.service..*) - 实例3:匹配AccountService类中所有的方法
within(com.xyz.service.AccountService)
Bean名称限定
按照bean元素的id值进行匹配:bean(id值)
- 实例1:匹配容器中
id=deptDao 的对象 bean(deptDao) - 实例2:匹配容器中id以Dao结尾的对象
bean(*Dao)
args参数限定表达式
按照参数类型匹配
实例:匹配容器中只有一个参数并且参数类型是符合Serializable接口的方法
args(java.io.Serializable)
public void f1(String str){}
bean(id)&&arg(..)
步骤
流程
-
导入jar <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.13</version>
</dependency>
-
定义方面组件 public class LoggerBean {
public void logger(ProceedingJoinPoint pjp) throws Throwable {
String targetClz = pjp.getTarget().getClass().getName();
String targetMethod = pjp.getSignature().getName();
String info = targetClz + "." + targetMethod;
System.out.println("用户在" + new Date().toLocaleString() + "完成了" + PropertiesUtil.getVal(info) + "事务");
pjp.proceed();
}
}
-
在applicationContent.xml配置文件中添加aop配置 <bean id="LoggerBean" class="pojo.LoggerBean"></bean>
<aop:config>
<aop:pointcut id="controllerPonitCut" expression="within(controller.*)"/>
<aop:aspect id="loggerAspect" ref="LoggerBean">
<aop:around method="logger" pointcut-ref="controllerPonitCut"></aop:around>
</aop:aspect>
</aop:config>
-
实现 @Test
public void LoggerBean() {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SaveController acBean = ac.getBean("saveController", SaveController.class);
acBean.execute();
}
示例
练习:利用AOP实现异常处理,将异常信息写入文件
切入点:所有的controller
方面组件:异常信息写入文件-->模拟
通知类型:异常通知
-
导入jar -
编写方面组件 public class ExceptionBean {
Logger logger = Logger.getLogger(ExceptionBean.class);
public void exec(Exception e) {
logger.error("异常信息");
logger.error("异常类型" + e);
StackTraceElement[] stackTrace = e.getStackTrace();
Arrays.stream(stackTrace).forEach(es -> logger.error(es));
}
}
-
编写Controller @NoArgsConstructor
@AllArgsConstructor
public class SaveController {
public String execute() {
System.out.println(0 / 0);
return "success";
}
}
-
编写aop配置 <bean id="saveController" class="controller.SaveController"></bean>
<bean id="ExceptionBean" class="pojo.ExceptionBean"></bean>
<aop:config>
<aop:pointcut id="controllerPonitCut" expression="within(controller.*)"/>
<aop:aspect id="execAspect" ref="ExceptionBean">
<aop:after-throwing method="exec" pointcut-ref="controllerPonitCut" throwing="e"/>
</aop:aspect>
</aop:config>
-
完成logger配置 <dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
#logger 日志器:控制消息的级别,并指定消息输出的目的地
log4j.rootLogger=debug, myconsole,myfile
#appender 输出器:消息输出的方式
log4j.appender.myconsole=org.apache.log4j.ConsoleAppender
log4j.appender.myfile=org.apache.log4j.FileAppender
log4j.appender.myfile.File=F:\\idealogger\\mylog.txt
#layout 布局器:消息以什么格式输出
log4j.appender.myconsole.layout=org.apache.log4j.TTCCLayout
log4j.appender.myfile.layout=org.apache.log4j.PatternLayout
log4j.appender.myfile.layout.ConversionPattern=[%t] %5p -%m%n
注解配置
扫包
Bean
在Spring容器中开启组件扫描
- 控制层:@Controller
- 业务层:@Service
- dao层:@Repository
- 其他:@Component
添加注解后的作用:
- id属性名默认是类型的首字母小写,如果需要自定义id的值,可以使用**
@Repository("自定义id") ** - 默认采用的单例模式创建bean对象,可以使用**
@Scope("prototype") **改变创建模式 - 如果是单例,默认在容器启动时创建bean对象,可以使用**
@Lazy **注解推迟到第一次调用getBean方法实例化 - 使用**
@PostConstructor 指定初始化方法,@PreDestory **指定销毁的方法
演示:
<context:component-scan base-package="dao"></context:component-scan>
<context:component-scan base-package="controller"></context:component-scan>
@Repository
@Scope("prototype")
@Lazy
public class DeptDAOImpl implements DeptDAO {
@PostConstruct
private void myinit() {
System.out.println("myinit");
}
@PreDestroy
private void mydestroy() {
System.out.println("mydestroy");
}
}
@Controller
public class SaveController
ClassPathXmlApplicationContext csc = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(csc.getBean("saveController", SaveController.class));
System.out.println(csc.getBean("deptDAOImpl", DeptDAO.class));
IOC
如果容器中两个bean组件有调用关系,可以使用下面注解
演示:
@Repository
public class DeptDAOImpl implements DeptDAO
@Controller
public class SaveController {
@Autowired
private DeptDAO deptDao;
public String execute() {
System.out.println("执行新增操作");
deptDao.add();
return "success";
}
}
ClassPathXmlApplicationContext csc = new ClassPathXmlApplicationContext("applicationContext.xml");
SaveController saveController = csc.getBean("saveController", SaveController.class);
saveController.execute();
csc.close();
Properties
properties文件中常量数据注入给bean
-
将properties文件数据加载到spring容器中 <util:properties id="db" location="classpath:db.properties"/>
-
在bean组件上使用**@Component(或其他) 注解,并在成员变量的上方使用@value **注解注入properties文件中的数据 @Data
@Component
public class DbBean {
@Value("#{db.username}")
private String username;
@Value("#{db.password}")
private String password;
@Value("#{db.url}")
private String url;
@Value("#{db.driver}")
private String driver;
}
ClassPathXmlApplicationContext csc = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(csc.getBean("dbBean", DbBean.class));
Aop相关注解
-
在Spring配置文件中启用aop注解 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
-
方面组件中,使用相应的注解标记 在类上方使用@Component注解标记将该组件扫描到spring容器中
在类上方使用@Aspect将该组件定义为方面组件
定义一个空方法,在方法上方使用@Pointcut定义切入点表达式
在方面组件的方法上定义通知,通知类型如下
- @Before:前置通知
- @AfterReturning:后置通知
- @AfterThrowing:异常通知
- @After:最终通知
- @Around:环绕通知
@Component
@Aspect
public class ExceptionBean {
Logger logger = Logger.getLogger(ExceptionBean.class);
@Pointcut("within(controller..*)")
public void mypoint() {
}
@AfterThrowing(pointcut = "mypoint()", throwing = "e")
public void exec(Exception e) {
logger.error("异常信息");
logger.error("异常类型" + e);
StackTraceElement[] stackTrace = e.getStackTrace();
Arrays.stream(stackTrace).forEach(es -> logger.error(es));
}
}
Spring与Mybatis整合
导入依赖
- mybatis.jar 用于mybatis开发
- mysql.jar 数据库连接
- druid Druid连接池
- spring-context.jar spring核心包
- spring-aspects.jar 通过aspects支持:面向方面
- mybatis-spring.jar mybatis和spring整合的包
- spring-jdbc.jar Spring 对JDBC 数据访问
- juinit.jar 单元测试
- guava.jar 提供集合,缓存,支持原语句,并发性,常见注解,字符串处理,I/O和验证的实用方法
- mybatis-generator-core.jar mybatis逆向工程
- log4j.jar 日志
xml配置
定义SqlSessionFactoryBean
application.xml
<util:properties id="db" location="classpath:db.properties"/>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSoruce"></property>
<property name="mapperLocations" value="mapper/*.xml"></property>
<property name="typeAliasesPackage" value="entity"></property>
</bean>
<bean id="myDataSoruce" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="#{db.driver}"/>
<property name="url" value="#{db.url}"/>
<property name="username" value="#{db.username}"/>
<property name="password" value="#{db.password}"/>
<property name="initialSize" value="#{db.initialSize}"/>
<property name="maxActive" value="#{db.maxActive}"/>
<property name="minIdle" value="#{db.minIdle}"/>
<property name="maxWait" value="#{db.maxWait}"/>
</bean>
MapperScannerConfigurer
? 在spring配置文件中定义MapperScannerConfigurer组件,通过该组件会根据定义的包路径扫描各个mapper(Dao)接口,并注册对应的MapperFactoryBean对象,最终通过MapperFactoryBean.getObject()获取接口的代理对象
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="dao"></property>
</bean>
如果指定的某个包下并不完全是我们定义的mapper(dao)接口,使用annotationClass缩小搜索和注册范围
@Target(TYPE)
@Retention(RUNTIME)
public @interface MyBatisAnnotation {
}
@Repository
@MyBatisAnnotation
public interface UserMapper {
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="dao"></property>
<property name="annotationClass" value="annotation.MyBatisAnnotation"></property>
</bean>
事务
什么是事务
? 事务是一系列的动作,它们综合在一起是一个完整的工作单元,这些动作要么全部完成,只要一个失败,就回到最初的状态,仿佛什么都没有发生
? ACID:原子性、一致性、隔离性、持久性
spring事务
? 在spring平台,提供了事务的支持,本质上是数据库支持对事务的处理,没有数据库的事务支持,spring是无法提供事务功能的。 使用spring事务管理组件之后,事务的控制不需要程序员编写,spring自动完成,spring并不是直接管理事务,而是提供了事务管理器接口PlatformTransactionManager接口,接口中定义了统一的编程模型,具体事务管理机制由各个平台实现。
Connection con = getCon();
con.setAutocommit(false);
...业务逻辑...
commit/rollback
事务定义:TransactionDefinition
传播行为
- REQUIRED(常用):如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
- MANDATORY:强制的 使用当前的事务,如果当前没有事务,就抛出异常。
- REQUIRES_NEW:需要JTA事务管理器的支持,新建事务,如果当前存在事务,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED:嵌套 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类 似的操作
隔离级别
- int ISOLATION_DEFAULT = -1; 使用数据库默认的隔离级别
- int ISOLATION_READ_UNCOMMITTED = 1;读未提交,A事务可以读到B事务未提交的数据
- int ISOLATION_READ_COMMITTED = 2;读提交,A事务可以读到B事务已提交的数据
- int ISOLATION_REPEATABLE_READ = 4;可重复读,A事务读不到B事务已提交的数据
- int ISOLATION_SERIALIZABLE = 8;串行化
只读
? 如果事务只对数据进行查询的操作,数据库可以利用事务的只读特性来进行一些特定的优化
超时
? 为了使应用程序正常运行,事务不能运行太长时间,因为事务可以涉及对数据库的锁定,所以长时间的事务会占用数据库资源, 事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待事务结束。
回滚机制
? 回滚规则定义了哪些异常会导致事务回滚而哪些不会,默认情况下,事务只有遇到运行期间异常时才会回滚,而在遇到检查型异常时不会回滚。
事务管理
- 编程式事务管理
- 声明式事务管理
- 基于xml事务配置
- 基于注解事务配置
编程式事务与声明式事务的区别
- 编程式事务需要修改业务代码,可以更具体管理事务,但是代码的侵入性较强
- 声明式事务以AOP机制作用到各个组件,编写更方便,实现类事务和业务组件的解耦
A(){
B();
}
B(){
setAutoCommit(false);
...
commit;
事务实现
xml
application.xml
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSoruce"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="execute" propagation="REQUIRED" isolation="DEFAULT" timeout="3000" read-only="false"
rollback-for="java.lang.ClassNotFoundException"></tx:method>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="within(controller..*)"></aop:advisor>
</aop:config>
注解
<tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSoruce"></property>
</bean>
@Controller
public class DeptController {
@Autowired
private DeptMapper deptMapper;
@Transactional
public void execute() throws ClassNotFoundException{
hod>
</tx:attributes> </tx:advice>
<!--事务aop配置-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="within(controller..*)"></aop:advisor>
</aop:config>
### 注解
```xml
<!--开启事务的注解 transaction-manager:指定事务管理器的标识-自定义,默认transactionManager-->
<tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
<!--配置事务管理器-->
<!--id:数据源事务管理器标识 class:mybatis对应的数据源事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--name:数据源 ref:使用的数据源-->
<property name="dataSource" ref="myDataSoruce"></property>
</bean>
@Controller
public class DeptController {
@Autowired
private DeptMapper deptMapper;
@Transactional
public void execute() throws ClassNotFoundException{
|