文章目录
一、Spring框架:
1、原生web开发中存在哪些问题?
- 传统Web开发存在硬编码所造成的过度程序耦合(例如:Service中作为属性Dao对象)。
- 部分Java EE API较为复杂,使用效率低(例如:JDBC开发步骤)。
- 侵入性强,移植性差(例如:DAO实现的更换,从Connection到SqlSession)。
2、概念
- Spring是一个项目管理框架,同时也是一套Java EE解决方案。
- Spring是众多优秀设计模式的组合(工厂、单例、代理、适配器、包装器、观察者、模板、策略)。
- Spring并未替代现有框架产品,而是将众多框架进行有机整合,简化企业级开发,俗称"胶水框架"。
- spring是以IOC(Inverse Of Control:控制反转)和AOP(Aspect Oriented Programming:面向切面编程)为内核的分层的Java SE/EE应用 full-stack轻量级开源框架。
二、Spring架构组成
Spring架构由诸多模块组成,可分类为
GroupId | ArtifactId | 说明 |
---|---|---|
org.springframework | spring-beans | Beans 支持,包含 Groovy |
org.springframework | spring-aop | 基于代理的AOP支持 |
org.springframework | spring-aspects | 基于AspectJ 的切面 |
org.springframework | spring-context | 应用上下文运行时,包括调度和远程抽象 |
org.springframework | spring-context-support | 支持将常见的第三方类库集成到 Spring 应用上下文 |
org.springframework | spring-core | 其他模块所依赖的核心模块 |
org.springframework | spring-expression | Spring 表达式语言,SpEL |
org.springframework | spring-instrument | JVM 引导的仪表(监测器)代理 |
org.springframework | spring-instrument-tomcat | Tomcat 的仪表(监测器)代理 |
org.springframework | spring-jdbc | 支持包括数据源设置和 JDBC 访问支持 |
org.springframework | spring-jms | 支持包括发送/接收JMS消息的助手类 |
org.springframework | spring-messaging | 对消息架构和协议的支持 |
org.springframework | spring-orm | 对象/关系映射,包括对 JPA 和 Hibernate 的支持 |
org.springframework | spring-oxm | 对象/XML 映射(Object/XML Mapping,OXM) |
org.springframework | spring-test | 单元测试和集成测试支持组件 |
org.springframework | spring-tx | 事务基础组件,包括对 DAO 的支持及 JCA 的集成 |
org.springframework | spring-web | web支持包,包括客户端及web远程调用 |
org.springframework | spring-webmvc | REST web 服务及 web 应用的 MVC 实现 |
org.springframework | spring-webmvc-portlet | 用于 Portlet 环境的MVC实现 |
org.springframework | spring-websocket | WebSocket 和 SockJS 实现,包括对 STOMP 的支持 |
org.springframework | spring-jcl | Jakarta Commons Logging 日志系统 |
三、自定义工厂
不使用Spring提供IOC容器 (Container),通过自定义的工厂来实现对象的创建
1、创建bean.properties
userService=com.mxd.service.impl.UserServiceImpl
2、工厂类
public class MyFactory { //公共的访问方法 public static Object getBean(String beanName) throws Exception { //读取properties文件中的信息 InputStream inputStream = MyFactory.class.getClassLoader().getResourceAsStream("bean.properties"); //创建属性集合列表 Properties properties = new Properties(); properties.load(inputStream); //获取值(全类名) String userServiceClass = properties.getProperty(beanName); //反射获取类对象 Class clazz = Class.forName(userServiceClass); //创建实例 Object obj = clazz.newInstance(); return obj; } }
3、测试
public void test1() throws Exception { UserService userService = (UserService) MyFactory.getBean("userService"); String name = userService.getName(); System.out.println(name); }
四、Spring环境搭建
1、在pom.xml中添加spring常用的依赖
<!--spring常用的依赖:spring-context--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.9</version> </dependency>
2、创建Spring-context.xml配置文件
命名无限制,常用的名称有:spring-context.xml、applicationContext.xml、beans.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 http://www.springframework.org/schema/beans/spring-beans.xsd"> </beans>
3、利用spring中的bean工厂创建对象
3.1 定义目标bean类型
接口:
public interface UserService { String getName(); }
实现类:
public class UserServiceImpl implements UserService { @Override public String getName() { return "你好Spring!"; } }
3.2 配置spring-context.xml中的bean标签
<!-- spring管理bean对象 bean标签 id属性: 标识符,必须唯一 举例: 接口名字:UserService- id标识符 "userService" class属性: 业务层或者控制器层/dao层接口实现类的全限定名称 --> <bean id="userService" class="com.mxd.service.impl.UserServiceImpl"></bean>
3.3 测试(调用Spring工厂API(ApplicationContext接口))
/** ** 程序中的对象都交由Spring的ApplicationContext工厂进行创建。 **/ @Test public void test1(){ //1. 读取配置文件中所需创建的bean对象,并获得工厂对象 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml"); //2. 通过id获取bean对象 UserService userService = (UserService) applicationContext.getBean("userService"); //通过 当前类型.class 获取bean对象 //UserService userService = (UserService) applicationContext.getBean(UserService.class); //3. 使用对象 String name = userService.getName(); System.out.println(name); }
五、IOC 控制反转 【重点】
反转了依赖关系的满足方式,由之前的自己创建依赖对象,变为由工厂推送。(变主动为被动,即反转),解决了具有依赖关系的组件之间的强耦合,使得项目形态更加稳健。
IOC反转控制:程序之间解耦
在类和类之间存在控制权,控制权指的是对象的创建和使用
比如有类A和类B,我们之前的做法是在A中调用B,那么控制权就在A中,这样做的耦合度较高,如果修改了B,A也要做相应修改。引入Spring框架后,控制权由spring容器来负责。当A想使用B时,需要由Spirng容器通过配置文件进行注入。这种思想就是IoC。
(为了更好的理解,我们可以这样认为,对象创建和使用的控制权转移到了Spring容器,由Spring容器来控制)。
六、DI(依赖注入)【重点】
在Spring创建对象的同时,为其属性赋值,称之为依赖注入。
常用的依赖注入有三种:setter注入,构造方法注入,基于注解的注入
1、setter方法注入(最常用):
创建对象时,Spring工厂会通过Set方法为对象的属性赋值。
1.1 定义目标Bean类型
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Integer id; private String name; private Integer age; private Date birthday; private String[] hobby; private Set<String> phones; private List<String> names; private Map<String,String> countries; private Properties files; }
1.2 基本类型 + 字符串类型 + 日期类型
<bean id="user" class="com.mxd.pojo.User"> <property name="id" value="1"/> <property name="name" value="张三"/> <property name="age" value="66"/> <property name="birthday" value="2020/11/11"/><!--注意格式"/"--> </bean>
1.3 容器类型
<bean id="user" class="com.mxd.pojo.User"> <property name="id" value="1"/> <property name="name" value="张三"/> <property name="age" value="66"/> <property name="birthday" value="2020/11/11"/> <!--数组--> <property name="hobby"> <array> <value>篮球</value> <value>足球</value> <value>羽毛球</value> </array> </property> <!--set集合--> <property name="phones"> <set> <value>17722223333</value> <value>15366668888</value> <value>1399997777</value> </set> </property> <!--list集合--> <property name="names"> <list> <value>mxd</value> <value>tlx</value> <value>zy</value> </list> </property> <!--Map集合--> <property name="countries"> <map> <entry key="china" value="中国"></entry> <entry key="uk" value="英国"></entry> <entry key="us" value="美国"></entry> </map> </property> <!--propertie文件--> <property name="files"> <props> <prop key="one">一</prop> <prop key="two">二</prop> <prop key="three">三</prop> </props> </property> </bean>
1.4 自建类型
定义目标bean类型
@Data @AllArgsConstructor @NoArgsConstructor public class TbUser { private Integer id; private String name; private Integer age; private TbUserInfo tbUserInfo; }
@Data @AllArgsConstructor @NoArgsConstructor public class TbUserInfo { private String sex ; //性别 private String phone ;//电话 private String address ;//地址 private Integer addrId ; //门牌号 }
配置spring-context.xml
<bean id="tbUser" class="com.mxd.pojo.TbUser"> <property name="id" value="1"/> <property name="name" value="李四"/> <property name="age" value="99"/> <property name="tbUserInfo" ref="tbUserInfo"/><!--用ref属性引用--> </bean> <bean id="tbUserInfo" class="com.mxd.pojo.TbUserInfo"> <property name="sex" value="男"/> <property name="phone" value="119"/> <property name="address" value="南窑头"/> <property name="addrId" value="999"/> </bean>
2、构造函数注入(了解)
创建对象时,Spring工厂会通过构造方法为对象的属性赋值,必须要有构造函数
<!--构造函数注入,基本和set方法一致--> <bean id="user" class="com.mxd.pojo.User"> <constructor-arg name="id" value="22"/> <constructor-arg name="name" value="王五"/> <constructor-arg name="age" value="66"/> <constructor-arg name="birthday" value="2021/12/7"/><!--注意时间格式--> <!--数组--> <constructor-arg name="hobby" > <array> <value>打篮球</value> <value>踢足球</value> <value>打台球</value> </array> </constructor-arg> <!--set集合--> <constructor-arg name="phones"> <set> <value>17722223333</value> <value>15366668888</value> <value>1399997777</value> </set> </constructor-arg> <!--list集合--> <constructor-arg name="names"> <list> <value>mxd</value> <value>tlx</value> <value>zy</value> </list> </constructor-arg> <!--Map集合--> <constructor-arg name="countries"> <map> <entry key="china" value="中国"></entry> <entry key="uk" value="英国"></entry> <entry key="us" value="美国"></entry> </map> </constructor-arg> <!--propertie文件--> <constructor-arg name="files"> <props> <prop key="one">一</prop> <prop key="two">二</prop> <prop key="three">三</prop> </props> </constructor-arg> </bean>
3、基于注解注入
需要的配置:
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 --> <!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> --> <context:component-scan base-package="com.mxd"></context:component-scan> <!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager --> <tx:annotation-driven transaction-manager="txManager"/>
3.1 使用注解注册bean
有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:
(2)@Repository:主要用于注册dao层的bean
这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。
@Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。
注:注册bean就是装配到Spring容器中,表示这个类的创建、销户交由spring进行管理,但这时bean可能并没有被创建。
3.2 装配bean时常用的注解
3.2.1 @Resource
jdk中定义的注解
①@Resource是默认以byName(名字)的方式去匹配与属性名相同的bean的id,同时可以指定name属性。
②既不指定name属性,也不指定type属性时,则自动按byName方式先进行查找,如果没有找到符合的bean,则回退为一个原始类型(type)进行查找,如果找到就注入,找不到就会抛出异常
3.2.2@Autowired
spring中提供的注解
①使用@Autowired注解来自动装配指定的bean。
②默认按照类型(type)注入,如果需要按照名称搜索,则需要借助于@Qualifier
@Qualifier(value="filmTypeService") @Autowired
③在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:
- 如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
- 如果查询的结果不止一个,那么@Autowired会根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
④@Autowired属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值。
注:装配bean就是创建对象,依赖注入(DI)
3.2.3 @Resource和@Autowired的区别
相同点:
@Resource的作用相当于@Autowired,均可标注在字段或属性的setter方法上。
不同点:
(1)提供方
@Autowired是Spring的注解;@Resource是javax.annotation注解,而是来自于JSR-250,J2EE提供,需要JDK1.6及以上。
(2)注入方式
@Autowired默认按照Type 注入;@Resource默认按Name自动注入,也提供按照Type 注入;
(3)属性
@Autowired注解可用于为类的属性、构造器、方法进行注值。
默认情况下,其依赖的对象必须存在(bean可用),如果需要改变这种默认方式,可以设置其required属性为false。
还有一个比较重要的点就是,@Autowired注解默认按照类型装配,如果容器中包含多个同一类型的Bean,那么启动容器时会报找不到指定类型bean的异常,解决办法是结合@Qualified注解进行限定,指定注入的bean名称。
@Resource有两个中重要的属性:name和type。name属性指定byName,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。
注:
@Resource如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时, @Resource注解会回退到按类型装配。但一旦指定了name属性,就只能按名称装配了。
@Resource注解的使用性更为灵活,可指定名称,也可以指定类型;
@Autowired注解进行装配容易抛出异常,特别是装配的bean类型有多个的时候,而解决的办法是需要在增加@Qualitied进行限定。一般@Autowired和@Qualifier一起用,@Resource单独用。
4、p标签(p名称空间)注入(了解)
<bean id="tbUser" class="com.mxd.pojo.TbUser" p:id="22" p:name="哈哈" p:age="33" p:tbUserInfo-ref="tbUserInfo"> </bean> <bean id="tbUserInfo" class="com.mxd.pojo.TbUserInfo"> <property name="sex" value="女"/> <property name="phone" value="110"/> <property name="address" value="山西"/> <property name="addrId" value="222"/> </bean>
5、自动注入(了解)
<bean id="user" class="com.mxd.pojo.User" autowire="byName"> </bean>
autowire属性有5种模式:
(1)no
(默认)不采用autowire机制.。这种情况,当我们需要使用依赖注入,只能用ref属性。
(2)byName
通过属性的名称自动装配(注入)。Spring会在容器中查找名称与bean属性名称一致的bean,并自动注入到bean属性中。当然bean的属性需要有setter方法。例如:bean A有个属性master,master的setter方法就是setMaster,A设置了autowire=“byName”,那么Spring就会在容器中查找名为master的bean通过setMaster方法注入到A中。
(3)byType
通过类型自动装配(注入)。Spring会在容器中查找类(Class)与bean属性类一致的bean,并自动注入到bean属性中,如果容器中包含多个这个类型的bean,Spring将抛出异常。如果没有找到这个类型的bean,那么注入动作将不会执行。
(4)constructor
类似于byType,但是是通过构造函数的参数类型来匹配。假设bean A有构造函数A(B b, C c),那么Spring会在容器中查找类型为B和C的bean通过构造函数A(B b, C c)注入到A中。与byType一样,如果存在多个bean类型为B或者C,则会抛出异常。但时与byType不同的是,如果在容器中找不到匹配的类的bean,将抛出异常,因为Spring无法调用构造函数实例化这个bean。
(5)default
采用父级标签(即beans的default-autowire属性)的配置。
七、控制简单对象的单例模式/多例模式
在spring-context.xml中:
<!-- singleton(默认):每次调用工厂,得到的都是同一个对象。单例模式 prototype:每次调用工厂,都会创建新的对象。多例模式 --> <bean id="user" class="com.mxd.pojo.User" scope="prototype"> </bean>
- 注意:需要根据场景决定对象的单例、多例模式。
- 可以共用:Service、DAO、SqlSessionFactory(或者是所有的工厂)。
- 不可共用:Connection、SqlSession、ShoppingCart。
八、spring中bean对象的生命周期
1、饿汉式创建的优势
工厂创建之后,会将Spring配置文件中的所有对象都创建完成(饿汉式)。
提高程序运行效率。避免多次IO,减少对象创建时间。(概念接近连接池,一次性创建好,使用时直接获取)
注:单例:始终只有一个对象 。
饿汉式:类一加载就创建对象
懒汉式 :需要使用的时候才创建对象
2、生命周期方法
实体类中:
- 自定义无参构造:
public User(){ System.out.println("对象被创建"); }
- 自定义初始化方法:添加“init-method”属性,Spring则会在创建对象之后,调用此方法。
public void init(){ System.out.println("对象初始化"); }
- 自定义销毁方法:添加“destroy-method”属性,Spring则会在销毁对象之前,调用此方法。
public void destroy(){ System.out.println("对象销毁"); }
- 销毁:工厂的close()方法被调用之后,Spring会毁掉所有已创建的单例对象。
- 分类:singleton对象由Spring容器直接销毁、prototype对象由JVM销毁。
spring-context.xml中:
单例模式:
<bean id="user" class="com.mxd.pojo.User" init-method="init" destroy-method="destroy"> </bean>
测试:
public void test3(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); User user = (User) applicationContext.getBean("user"); applicationContext.close();//调用close方法之后才会被销毁 }
输出结果:
对象被创建 对象初始化 对象销毁
多例模式:
<!--lazy-init(延时加载):当使用多例模式时,需要使用的时候才创建对象 初始化过程需要懒加载--> <bean id="user" class="com.mxd.pojo.User" init-method="init" destroy-method="destroy" scope="prototype" lazy-init="true"> </bean>
测试:
public void test3(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); User user = (User) applicationContext.getBean("user"); applicationContext.close();//多例模式中销毁时由JVM直接销毁 }
输出结果:
对象被创建 对象初始化
3、生命周期注解
@PostConstruct //初始化注解 public void init(){ System.out.println("对象初始化"); } @PreDestroy //销毁注解 public void destroy(){ System.out.println("对象销毁"); } @Scope("singleton")//默认单例模式,依赖范围的注解,prototype:多例 //@Primary 如果有多个实现类,这个注解标记的哪个类上,就默认使用当前这个接口的实现类去使用的
4、生命周期阶段
**单例bean:**singleton
**多例bean:**prototype
九、Spring+Mybatis【重点】
1、需要导入的依赖:
<!--spring和mybatis整合必须要导入的两个依赖--> <!--1.mybatis提供和spring无缝整合的jar包--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency> <!--2.spring的jdbc的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--*********************************************************--> <!--spring常用依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--mybatis的依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> <!--分页插件(根据需求选择)--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency>
2、配置spring-mybatis.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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 1、 加载properties文件中数据库参数信息 2、创建数据源对象(有好几种:①spring-jdbc中的数据源 ②mybatis中的数据源 ③druid中的数据源) 3、创建SqlSessionFactory对象 SqlSessionFactoryBean对象 4、配置mapper的扫描器对象 --> <!--1.加载properties文件中数据库参数信息(输入<property...就会有提示了)--> <context:property-placeholder location="classpath:db.properties"/> <!--2.创建数据源对象(有好几种)--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}" /> </bean> <!--3.创建SqlSessionFactory对象 SqlSessionFactoryBean对象--> <bean id="sqlSessionFactory" class = "org.mybatis.spring.SqlSessionFactoryBean"> <!-- 配置注入的数据源--> <property name="dataSource" ref="dataSource" /> <!-- 配置mybatis映射文件的路径,如果用注解了这个就要注释--> <property name="mapperLocations" value="classpath:com/mxd/mapper/*Mapper.xml" /> <!-- 配置实体的别名包扫描--> <property name="typeAliasesPackage" value="com.mxd.pojo" /> <!--提供分页插件,如果配置了这个插件,就必须要进行分页查询,不然会抛异常--> <!--<property name="plugins"> <array> <bean class="com.github.pagehelper.PageInterceptor"></bean> </array> </property>--> </bean> <!--4.spring与mybatis整合: 配置mapper的扫描器对象 管理DAO实现类的创建,并创建DAO对象,存入工厂管理 mapper/dao实现对象在工厂中的id是:“首字母小写的-接口的类名” mybatis提供的这个和spring整合的依赖中MapperScannerConfigurer,Mapper的配置扫描器必须配置这个类 他的id必须mapperScannerConfigurer,否则报错 --> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer" > <!-- 必须配置扫描器数据源对象--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!-- 配置接口扫描器扫描的默认包路径,然后交给spring --> <property name="basePackage" value="com.mxd.mapper"/> </bean> </beans>
3、剩余操作:
3.1 db.properties配置文件
jdbc.driverClassName=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/testdata?serverTimezone=GMT%2B8 jdbc.username=root jdbc.password=000000
3.2 实体类
@Data @NoArgsConstructor @AllArgsConstructor public class Dept { private Integer deptno; private String dname; private String loc; }
3.3 mapper接口
public interface DeptMapper { List<Dept> findAll(); }
3.4 services接口和实现类
public interface DeptService { List<Dept> findAll(); }
//使用@Service注册bean @Service public class DeptServieImpl implements DeptService { //使用@Autowired注入 @Autowired private DeptMapper deptMapper; @Override public List<Dept> findAll() { return deptMapper.findAll(); } }
3.5 测试
@Test public void test1(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); DeptService bean = applicationContext.getBean(DeptService.class); List<Dept> all = bean.findAll(); System.out.println(all); }
3.6 错误解析 | 注意事项 | 附加内容
3.6.1 使用@service等注解时
Spring容器在运行的时候,怎么知道从哪个包扫描呢?需要在spring的xml文件中添加
<!-- 在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件, 如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean 注意:如果配置了<context:component-scan>那么<context:annotation-config/>标签 就可以不用再xml中配置了,因为前者包含了后者。 --> <context:component-scan base-package="com.mxd.service"/>
3.6.2 引入其他的xml文件
可以通过下列标签引入其他的xml文件
<import resource="classpath:spring-mybatis.xml"></import>
3.6.3 其他的数据源
<!--与PooledDataSource集成(mybatis)--> <bean id="dataSource" class="org.apache.ibatis.datasource.pooled.PooledDataSource"> <property name="driver" value="${driverClass}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </bean> <!--与DruidDataSource集成(druid)--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!--基本配置--> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
使用druid数据源时,要导入依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
3.6.4 Druid连接池可选参数
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!--基本配置--> <property name="driverClassName" value="${jdbc.driverClass}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="${jdbc.init}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxActive" value="${jdbc.maxActive}"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> </bean>
3.6.5 分页插件的使用
在service层的实现类中:
@Override public PageInfo<Shop> findShopByPage(Integer page, Integer size) { //设置页面,每页显示条数 PageHelper.startPage(page,size) ; List<Shop> list = shopMapper.findAll(); //创建PageInfo<Shop>对象 PageInfo<Shop> pageInfo = PageInfo.of(list); return pageInfo; }
测试类:
@Test public void test1(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-config.xml"); //创建service对象,按照类型创建 ShopService shopService = applicationContext.getBean(ShopService.class); PageInfo<Shop> pageInfo = shopService.findShopByPage(1, 2); List<Shop> list = pageInfo.getList(); System.out.println(list); System.out.println(pageInfo); }
十、Spring+Mybatis+Servlet【重点】
在上面spring+mybatis整合的基础上完成下列操作
1、需要导入的依赖:
<!--servlet--> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope><!--不发布--> </dependency> <!--fastjson,json解析工具--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency>
2、剩余操作:
2.1 创建servlet
@WebServlet("/findAll") public class DeptController extends HttpServlet { private DeptService deptService; //为了防止空指针 public DeptController(){ //创建Spring的ioc容器 ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); deptService = applicationContext.getBean(DeptService.class); System.out.println(deptService); } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //处理返回页面的乱码问题方法①: resp.setContentType("text/html;charset=utf-8"); //处理返回页面的乱码问题方法②: /*resp.setContentType("application/json"); resp.setCharacterEncoding("utf-8");*/ List<Dept> all = deptService.findAll(); System.out.println(all);//在控制台输出查看一下 String res = JSONObject.toJSONString(all); PrintWriter pw = resp.getWriter(); pw.write(res); pw.flush(); pw.close(); } }
2.2 错误解析
如果发送请求时出现空指针异常,检查注入问题,是否未注入
十一、代理设计模式
1、概念
将核心功能与辅助功能(事务、日志、性能监控代码)分离,达到核心业务功能更纯粹、辅助业务功能可复用。
2、静态代理
2.1 概念
代理角色和真实角色都需要实现同一个接口
通过代理类的对象,为原始类的对象(目标类的对象)添加辅助功能,更容易更换代理实现类、利于维护。
- 代理类 = 实现原始类相同接口 + 添加辅助功能 + 调用原始类的业务方法。
- 静态代理的问题
- 代理类数量过多,不利于项目的管理。
- 多个代理类的辅助功能代码冗余,修改时,维护性差。
2.2 实现步骤
①UserService接口:
public interface UserSrervice { //添加用户 void addUser(); }
②UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice { @Override public void addUser() { System.out.println("UserServiceImpl:添加了一个用户"); } }
③静态代理模板:
public class StaticProxy implements UserService { private UserService userService; //构造为属性初始化 public StaticProxy(UserService userService){ this.userService = userService; } @Override public void addUser() { System.out.println("staticproxy: 权限校验!"); //调用业务层的方法 userService.addUser(); System.out.println("staticproxy: 生成日志!"); } }
④测试:
@Test public void staticProxyTest(){ //最初的使用方式,真实角色 UserService userService = new UserServiceImpl(); userService.addUser(); System.out.println("****************************"); //创建静态代理对象,业务能力增强(代理角色) StaticProxy staticProxy = new StaticProxy(userSrervice); staticProxy.addUser(); }
⑤运行结果:
UserServiceImpl:添加了一个用户 **************************** staticproxy: 权限校验! UserServiceImpl:添加了一个用户 staticproxy: 生成日志!
3、动态代理
动态创建代理类的对象,为原始类的对象添加辅助功能。
3.1 JDK动态代理实现(基于接口)
基于接口实现 ,通过反射的方式,执行代理处理程序的时候就获取了代理对象
①UserService接口:
public interface UserSrervice { //添加用户 void addUser(); }
②UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice { @Override public void addUser() { System.out.println("UserServiceImpl:添加了一个用户"); } }
③JDK动态代理模板:
public class JdkProxy implements InvocationHandler {//java.lang.reflect.InvocationHandler //声明被代理接口 private Object target; //注入 public JdkProxy(Object target) { this.target = target; } //处理代理实例,调用被代理对象的方法! /* Object proxy:具体的代理类对象,其中有被代理接口的方法! Method method:被调用的方法 Object[] args:被调用方法的参数 */ //InvocationHandler接口是动态代理的桥梁 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("JdkProxy: 权限校验!"); //调用被代理对象的方法 Object obj = method.invoke(target, args); System.out.println("JdkProxy: 生成日志!"); return obj; } }
④测试:
@Test public void jdkProxyTest(){ //最初的使用方式,真实角色 UserService userSrervice1 = new UserServiceImpl(); userSrervice1.addUser(); System.out.println("****************************"); JdkProxy jdkProxy = new JdkProxy(userSrervice1); /* 用代理类,创建代理实例 参数为: 1.类加载器ClassLoader loader 2.被代理的接口 Class<?>[] interfaces 3.调用处理器对象 InvocationHandler h 不能强转为实现类,因为不能基于类实现,只能基于接口实现 */ //Proxy类是动态代理的入口 UserService UserService2 = (UserService) Proxy.newProxyInstance( jdkProxy.getClass().getClassLoader(), userSrervice1.getClass().getInterfaces(), jdkProxy); //调用业务方法 UserService2.addUser(); }
⑤运行结果:
UserServiceImpl:添加了一个用户 **************************** JdkProxy: 权限校验! UserServiceImpl:添加了一个用户 JdkProxy: 生成日志!
3.2 CGlib动态代理实现(基于继承)
①在pom.xml中添加依赖
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
②UserService接口:
public interface UserSrervice { //添加用户 void addUser(); }
③UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice { @Override public void addUser() { System.out.println("UserServiceImpl:添加了一个用户"); } }
④CGlib动态代理模板:
public class CglibProxy implements InvocationHandler {//net.sf.cglib.proxy.InvocationHandler //代理的目标对象 private Object target; public Object createProxyInstance(Object target){ this.target=target; //创建目标对象的代理对象 //1.生成代理对象 Enhancer enhancer = new Enhancer(); //2.设置父类 enhancer.setSuperclass(target.getClass()); //3.设置回调 对象:代表当前类对象地址值引用 this enhancer.setCallback(this); //4.创建当前代理角色对象 Object obj = enhancer.create(); return obj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("CglibProxy: 权限校验!"); //调用被代理对象的方法 Object obj = method.invoke(target, args); System.out.println("CglibProxy: 生成日志!"); return obj; } }
⑤测试:
@Test public void cglibProxyTest(){ //最初的使用方式,真实角色 UserService userSrervice1 = new UserServiceImpl(); userSrervice1.addUser(); System.out.println("****************************"); CglibProxy cglibProxy = new CglibProxy(); //基于子类,实现类 UserServiceImpl userService2 = (UserServiceImpl) cglibProxy.createProxyInstance(userSrervice1); userService2.addUser(); }
十二、AOP(面向切面编程)【重点】
1、概念
AOP(Aspect Oriented Programming),即面向切面编程,利用一种称为"横切"的技术,剖开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。对逻辑业务方法和非业务方法进行分离。对逻辑业务方法和非业务方法进行分离。
注:目前上面叙述的代理方式都可以实现针对业务方法进行增强,而且JDK动态代理/CGlib动态代理将增强代码和业务方法直接分离了,但是写法很麻烦,Spring提供一个核心AOP :面向切面编程 基于OOP(面向对象)
2、AOP开发术语即解释
2.1 通知、增强(Advice):
就是你想要的功能,例如安全,事物,日志等。先定义好,然后在想用的地方用一下。分为:前置通知、后置通知、异常通知、最终通知、环绕通知等。
2.2 连接点(Joinpoint):
连接点是程序类中客观存在的方法,可被Spring拦截并切入内容。就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectj还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。
2.3 切入点(Pointcut):
被Spring切入连接点。上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对吧,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。
2.4 目标对象(Target):
代理的目标对象。引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,它可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。
2.5 引介(Introduction):
一种特殊的增强,可在运行期为类动态添加Field和Method。
允许我们向现有的类添加新方法属性。就是把切面(也就是新方法属性:通知定义的)用到目标类中
2.6 织入(Weaving):
把通知应用到具体的类,进而创建新的代理类的过程。
把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时
2.7 代理(Proxy):
被AOP织入通知后,产生的结果类。
2.8 切面(Aspect):
由切入点和通知组成,将横切逻辑织入切面所指定的连接点中。
切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切入点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。
3、作用
Spring的AOP编程即是通过动态代理类为原始类的方法添加辅助功能。
就是为了更清晰的逻辑,可以让你的业务逻辑去关注自己本身的业务,而不去想一些其他的事情,这些其他的事情包括:安全,事物,日志等
4、开发流程
4.1 在pom.xml中添加依赖
<!--spring常用依赖--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--spring切面jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
4.2 定义原始类和接口
UserService接口:
public interface UserSrervice { //添加用户 void addUser(); }
UserServiceImpl实现类:
public class UserServiceImpl implements UserSrervice { @Override public void addUser() { System.out.println("UserServiceImpl:添加了一个用户"); } }
4.3 定义通知类
我们这里只利用一个通知做介绍,其余都差不多
//前置通知 public class BeforeHandler implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("前置通知:执行业务方法之前"); } }
4.4 在spring-context.xml中定义bean标签
<!--service层bean对象--> <bean id="userServiceImpl" class="com.mxd.service.impl.UserServiceImpl"/> <!--前置通知--> <bean id="beforeHandler" class="com.mxd.aop.BeforeHandler"/>
4.5 定义切入点(PointCut),形成切面(Aspect)
4.6.1 方式一:使用原生的Spring API接口
<!--pointcut:切点 使用切点表达式 execution() execution(* *.*.service.impl.*.*(..)) 第一个* 是默认的 和第二个*必须有空格,必须写 第二个* :就是com 第三个* :mxd 第四个:* :一般不用写*,要写为明确给业务层的包名service 第五个:* :一般service下的impl包 第五个*:impl包下的实现类 第六个*:类中方法名,也可以用*代替 (..):不明确方法的参数时在括号里点两个点 --> <aop:config> <!--配置切入点--> <aop:pointcut id="pointcut" expression="execution(* *.*.service.*.*.addUser(..))"/> <!--如果方法名唯一可以像下面这样写,但是不建议写--> <!--<aop:pointcut id="pointcut" expression="execution(* addUser(..))"/>--> <!--组装切面--> <aop:advisor advice-ref="beforeHandler" pointcut-ref="pointcut"/> </aop:config>
通知类接口:
前置通知:MethodBeforeAdvice
后置通知:AfterAdvice
后置通知(最终通知):AfterReturningAdvice //有异常不执行,方法会因异常而结束,无返回值
异常通知:ThrowsAdvice
环绕通知:MethodInterceptor
4.6.2 方式二:自定义切面
①自定义切面:
public class MyAspect { public void before() { System.out.println("自定义前置通知!"); } public void after(){ System.out.println("自定义后置通知!"); } }
②spring-context.xml中配置:
<bean id="myAspect" class="com.mxd.aop.MyAspect"/> <aop:config> <!--自定义切面,注意要写ref属--> <aop:aspect ref="myAspect"> <!--切点--> <aop:pointcut id="pointcut" expression="execution(* *.*.*.*.*.addUser(..))"/> <!--前置通知--> <aop:before method="before" pointcut-ref="pointcut"/> <aop:after method="after" pointcut-ref="pointcut"/> </aop:aspect> </aop:config>
③测试:
@Test public void aopTest(){ //最初的使用方式,真实角色 UserService userSrervice1 = new UserServiceImpl(); userSrervice1.addUser(); System.out.println("****************************"); ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); UserService userService = applicationContext.getBean("userServiceImpl", UserService.class); userService.addUser(); }
④运行结果:
UserServiceImpl:添加了一个用户 **************************** 自定义前置通知! UserServiceImpl:添加了一个用户 自定义后置通知!
5、注解开发:
①自定义切面
@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice) @Component //声明组件,进入工厂 public class MyAspect { // 定义切入点 @Pointcut("execution(* com.mxd.service.impl.UserServiceImpl.*(..))") public void pc(){} @Before("pc()") // 前置通知 public void mybefore(JoinPoint a) { System.out.println("target:"+a.getTarget()); System.out.println("args:"+a.getArgs()); System.out.println("method's name:"+a.getSignature().getName()); System.out.println("before~~~~"); } @AfterReturning(value="pc()",returning="ret") // 后置通知 public void myAfterReturning(JoinPoint a,Object ret){ System.out.println("after~~~~:"+ret); } @AfterThrowing(value="pc()",throwing="ex") // 异常通知 public void myThrows(JoinPoint jp,Exception ex){ System.out.println("throws"); System.out.println("===="+ex.getMessage()); } @Around("pc()") // 环绕通知(使用around时,将其他的注解注释了) public Object myInterceptor(ProceedingJoinPoint p) throws Throwable { //是要将所有的通知关联进来 Object target = null ; //代理角色 try{ System.out.println("执行业务方法前,前置通知") ; target= p.proceed();//执行底层业务方法 System.out.println("执行业务方法之后的,后置通知"); return target; } catch (Throwable throwable) { throwable.printStackTrace(); System.out.println("执行异常通知..."+throwable.getMessage()); }finally { System.out.println("after..通知"); } } }
②配置spring-context.xml文件:
<!-- 添加如下配置,启用aop注解 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
十三、事务【重点】
1、事务属性
1.1 隔离级别
1.1.1 概念
isolation
隔离级别
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
1.1.2 特性
- 安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
- 并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
1.1.3 并发问题
事务并发时的安全问题
问题 | 描述 |
---|---|
脏读 | 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止 |
不可重复读 | 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止 |
幻影读 | 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止 |
1.2 传播行为
propagation
传播行为
当涉及到事务嵌套(Service调用Service)时,可以设置:
- SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
- REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
1.3 读写性
readonly
读写性
- true:只读,可提高查询效率。(适合查询)
- false:可读可写。 (默认值)(适合增删改)
1.4 事务超时
timeout
事务超时时间
当前事务所需操作的数据被其他事务占用,则等待。
- 100:自定义等待时间100(秒)。
- -1:由数据库指定等待时间,默认值。(建议)
1.5 事务回滚
rollback-for
回滚属性
- 如果事务中抛出 RuntimeException,则自动回滚
- 如果事务中抛出 CheckException(非运行时异常 Exception),不会自动回滚,而是默认提交事务
- 处理方案 : 将CheckException转换成RuntimException上抛,或 设置 rollback-for=“Exception”
2、具体的实现流程:
2.1 在pom.xml中添加依赖
<!--提供spring的事务支持的jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.5.RELEASE</version> </dependency>
2.2 编写相关的mapper接口,xml
@Repository public interface BankMapper { void subMoney(Bank bank); void addMoney(Bank bank); }
<update id="subMoney" parameterType="com.mxd.pojo.Bank"> update bank set money=money - #{money} where id = #{id} </update> <update id="addMoney" parameterType="com.mxd.pojo.Bank"> update bank set money=money + #{money} where id = #{id} </update>
2.3 编写service层接口和实现类
public interface BankService { void transfer_accounts(int id1,int id2,float price); }
@Service @Transactional public class BankServiceImpl implements BankService { @Autowired private BankMapper bankMapper; @Override @Transactional(propagation = Propagation.MANDATORY) public void transfer_accounts(int id1,int id2,float price) { //转账人,减去金额 Bank bank1 = new Bank(); bank1.setId(id1); bank1.setMoney(price); bankMapper.subMoney(bank1); int i=1/0; //异常报错 //被转账人,加上金额 Bank bank2 = new Bank(); bank2.setId(2); bank2.setMoney(price); bankMapper.addMoney(bank2); } }
2.4 编写spring-context.xml
2.4.1 配置事务管理器
事务管理器,其中持有DataSource,可以控制事务功能(commit,rollback等)。
<!--xml配置方式; 引入配置事务管理器,其中依赖DataSource,借以获得连接,进而控制事务逻辑--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--管理数据源--> <property name="dataSource" ref="dataSource"></property> </bean>
注意: DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource的Bean,否则事务控制失败
2.4.2 配置事务通知
基于事务管理器,进一步定制,生成一个额外功能:Advice。
此Advice可以切入任何需要事务的方法,通过事务管理器为方法控制事务。
<!--配置事务通知: id:事务控制的标识 transaction-manager:引入指定的事务管理器 --> <tx:advice id="transactionInterceptor" transaction-manager="dataSourceTransactionManager"> <tx:attributes> <!--<tx:method name="insertUser" rollback-for="Exception" isolation="DEFAULT" propagation="REQUIRED" read-only="false"/>--> <tx:method name="*" rollback-for="Exception"/> </tx:attributes> </tx:advice>
2.4.3 编织
将事务管理的Advice 切入需要事务的业务方法中
<aop:config> <aop:pointcut id="pointcut" expression="execution(* transfer_accounts(..))"/> <!-- 组织切面 --> <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="pointcut"/> </aop:config>
2.5 测试
@Test public void test1(){ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml"); BankService bean = applicationContext.getBean(BankService.class); bean.transfer_accounts(1, 2, 100); }
3、注解实现事务控制
3.1 编写 spring-context.xml 文件
<!--开启事务注解--> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
3.2 具体操作
用于控制事务切入
- @Transactional
- 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!
//标记在类上,表示类中的每个方法都切入事务(有自己的事务控制的方法除外) @Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1) public class UserServiceImpl implements UserService { ... //标记在方法上表示该方法自己的事务控制,仅对此方法有效 @Transactional(propagation=Propagation.REQUIRED)//默认情况 public List<User> queryAll() { return userDao.queryAll(); } public void save(User user){ userDao.save(user); } }
3.3 注解参数详解
事物传播行为介绍:
@Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
@Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
@Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
@Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
@Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.事物超时设置:
@Transactional(timeout=30) //默认是30秒
事务隔离级别:
@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化MYSQL: 默认为REPEATABLE_READ级别
SQLSERVER: 默认为READ_COMMITTED脏读 : 一个事务读取到另一事务未提交的更新数据
不可重复读 : 在同一事务中, 多次读取同一数据返回的结果有所不同, 换句话说,
后续读取可以读到另一事务已提交的更新数据. 相反, "可重复读"在同一事务中多次
读取数据时, 能够保证所读数据一样, 也就是后续读取不能读到另一事务已提交的更新数据
幻读 : 一个事务读到另一个事务已提交的insert数据
十四、Spring-test和junit整合
1、在pom.xml文件中添加依赖
<!--spring-test--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.5.RELEASE</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency>
2、具体操作:
可以免去工厂的创建过程;
可以直接将要测试的组件注入到测试类。
@RunWith(SpringRunner.class) //由SpringJUnit4ClassRunner启动测试 @ContextConfiguration("classpath:spring-context.xml") //spring的配置文件位置 public class SpringTest{//当前测试类也会被纳入工厂中,所以其中属性可以注入 @Autowired // 注入要测试的组件 @Qualifier("userDAO") private UserDAO userDAO; @Test public void test(){ // 测试使用userDAO userDAO.queryUser(); .... } }
十五、注解开发
1、 声明bean
用于替换自建类型组件的 <bean…>标签;可以更快速的声明bean
- @Service 业务类专用
@Repository dao实现类专用
@Controller web层专用- @Component 通用
- @Scope 用户控制bean的创建模式
// @Service说明 此类是一个业务类,需要将此类纳入工厂 等价替换掉 <bean class="xxx.UserServiceImpl">
// @Service默认beanId == 首字母小写的类名"userServiceImpl"
// @Service("userService") 自定义beanId为"userService"
@Service //声明bean,且id="userServiceImpl"
@Scope("singleton") //声明创建模式,默认为单例模式 ;@Scope("prototype")即可设置为多例模式
public class UserServiceImpl implements UserService {
...
}
2、 注入(DI)
用于完成bean中属性值的注入
- @Autowired 基于类型自动注入
- @Resource 基于名称自动注入
- @Qualifier(“userDAO”) 限定要自动注入的bean的id,一般和@Autowired联用
- @Value 注入简单类型数据 (jdk8种+String)
@Service
public class UserServiceImpl implements UserService {
@Autowired //注入类型为UserDAO的bean
@Qualifier("userDAO2") //如果有多个类型为UserDAO的bean,可以用此注解从中挑选一个
private UserDAO userDAO;
}
@Service
public class UserServiceImpl implements UserService {
@Resource("userDAO3") //注入id=“userDAO3”的bean
private UserDAO userDAO;
/*
@Resource //注入id=“userDAO”的bean
private UserDAO userDAO;
*/
}
public class XX{
@Value("100") //注入数字
private Integer id;
@Value("shine") //注入String
private String name;
}
3、 事务控制
用于控制事务切入
- @Transactional
- 工厂配置中的 <tx:advice… 和 <aop:config… 可以省略 !!
//类中的每个方法都切入事务(有自己的事务控制的方法除外)
@Transactional(isolation=Isolation.READ_COMMITTED,propagation=Propagation.REQUIRED,readOnly=false,rollbackFor=Exception.class,timeout = -1)
public class UserServiceImpl implements UserService {
...
//该方法自己的事务控制,仅对此方法有效
@Transactional(propagation=Propagation.SUPPORTS)
public List<User> queryAll() {
return userDao.queryAll();
}
public void save(User user){
userDao.save(user);
}
}
4、注解所需配置
<!-- 告知spring,哪些包中 有被注解的类、方法、属性 -->
<!-- <context:component-scan base-package="com.qf.a,com.xx.b"></context:component-scan> -->
<context:component-scan base-package="com.qf"></context:component-scan>
<!-- 告知spring,@Transactional在定制事务时,基于txManager=DataSourceTransactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
5、 AOP开发
5.1 注解使用
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect // 声明此类是一个切面类:会包含切入点(pointcut)和通知(advice)
@Component //声明组件,进入工厂
public class MyAspect {
// 定义切入点
@Pointcut("execution(* com.qf.spring.service.UserServiceImpl.*(..))")
public void pc(){}
@Before("pc()") // 前置通知
public void mybefore(JoinPoint a) {
System.out.println("target:"+a.getTarget());
System.out.println("args:"+a.getArgs());
System.out.println("method's name:"+a.getSignature().getName());
System.out.println("before~~~~");
}
@AfterReturning(value="pc()",returning="ret") // 后置通知
public void myAfterReturning(JoinPoint a,Object ret){
System.out.println("after~~~~:"+ret);
}
@Around("pc()") // 环绕通知
public Object myInterceptor(ProceedingJoinPoint p) throws Throwable {
System.out.println("interceptor1~~~~");
Object ret = p.proceed();
System.out.println("interceptor2~~~~");
return ret;
}
@AfterThrowing(value="pc()",throwing="ex") // 异常通知
public void myThrows(JoinPoint jp,Exception ex){
System.out.println("throws");
System.out.println("===="+ex.getMessage());
}
}
5.2 配置
<!-- 添加如下配置,启用aop注解 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
十六、lombok:
Lombok是一个Java库,能自动插入编辑器并构建工具,简化Java开发。通过添加注解的方式,不需要为类编写getter或eques方法,同时可以自动化日志变量。
1、添加依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.20</version> </dependency>
2、例子
@Data @NoArgsConstructor //提供无参构造 @AllArgsConstructor //提供所有参数的有参构造 public class Bank { private Integer id; private String username; private Float money; }
十七、附加:
1、BeanFactory接口和ApplicationContext接口,有什么区别(生命周期角度考虑)
答:
BeanFactory是Spring容器中的顶层接口,ApplicationContext是它的子接口。
BeanFactory和ApplicationContext的区别:创建对象的时间点不一样。
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。(立即加载的思想来创建bean对象)
BeanFactory:什么使用什么时候创建对象。(延迟加载的思想来创建bean对象)BeanFactory顶层的父接口 ApplicationContext:间接的子接口 共同点:都是工厂接口 前者:老工厂接口,部分不支持SpringAOP结束 管理bean对象的操作 bean对象的初始化/销毁...,不支持国际化 后者:是常用的工厂接口 ClassPathXmlApplicationContext:就是ApplicationContext的具体的子实现类 很多子实现类 ClassPathXmlApplicationContext:获取项目下类路径下的配置文件(推荐:resources下面的配置文件) FileSystemXmpApplicationContext:可以获取系统文件的配置文件 GenericXmlApplicationContext:通用的子类(统一资源定位的文件) ApplicationContext:支持国际化/SpringAOP..
2. 简述以下SpringIOC
控制反转/控制倒置,通过引入SpringIOC容器,利用依赖注入的方式,实现对象间的解耦 实现对象的解耦,利用工厂模式. 创建容器对象,加载Spring的配置文件----读取xml文件 (dom4j解析:基于面向对象/sax解析:基于事件编程) <?xml version="1.0" encoding="UTF-8"?> <beans> <bean id="" class=""></bean> </beans> 容器对象.getBean("唯一标识"/类型的Class) 通过反射的方式:获取class属性 "xx.xx.x"---->Class.forName("类的全限定名称");---Class 创建当前类实例 <bean id="标识" class="类的全限定名称"></bean>
3.什么是Spring的AOP
面向切面编程:基于OOP面向对象的! 利用通知类型:前置通知,后置通知,异常通知,最终通知(不去设置),环绕通知(前面所有通知都可以进行配置) 实现业务层方法的增强(控制) 需要实现类似于jdk动态代理/cglib动态代理:将业务层代码和增强代码进行分离 好处: 1)方便后期业务的方法维护管理:管理整个业务方法service 2)通过AOP实现程序间的解耦(Spring ioc) 3)业务层代码和增强代码进行分离 //简单的配置---- 实现了SpringAOP相关的接口 xxAdvice <aop:config> <aop:pointcut id="" 表达式="execulotion(* *.*.service.impl.*.*(..))" > <aop:advice> <aop:config>
4.jdk动态代理和Cglib动态代理的区别
jdk动态代理基于接口实现的:需要有一个接口:xxService 代理实现过程: java.lang.reflect. InvocationHandler :代理的处理程序 cglib:基于子类实现 InvocationHandler(cglib.proxy包下的) :代理的处理程序 实现的过程:需要增强类Enhancer //定义方法:bindObject public Object bindObject(Object tareget){ this.tareget = tareget ; //1)使用步骤 // 创建增强类 Enhancer enhancer = new Enhancer() ; //2)给Enhancer增强类对象 进行赋值 enhancer.setSuperclass(tareget.getClass()); //3)设置回调 对象:代表当前类对象地址值引用 this enhancer.setCallback(this); //4)创建当前角色对象 Object obj = enhancer.create(); return obj ; }
5.列出Spring常见的注解,分别阐述其意思
Spring注解 创建bena对象的注解 @Component: 通用的注解 @Service :业务层 @Controller :控制器 @Reospitory :持久层dao层 注入注解 @Autowired:自动按照类型注入 @Qualifier:按照唯一标识进行注入,结合使用 @Resource(name="唯一标识") 生命相关的注解 @PostConstruct:初始化 @PreDestroy:销毁 @Scope("名称"):单例/多列 //@Primary 指定唯一标识