1. Spring
1.1 简介
Spring是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架。
- spring理念:使现有技术更加容易使用,本身是一个大杂烩。
- SSM:springMVC + Spring + Mybatis
1.2 优点
- Spring是一个开源的免费的框架(容器)。
- Spring是一个轻量级的,非入侵式的框架。
- 控制反转(IOC),面向切面编程(AOP)。
- 支持事务的处理,对框架整合的支持。
1.3 拓展
现代化的java开发基于spring的开发。
- SpringBoot(构建一切)
- 一个快速开发的脚手架。
- 基于SpringBoot可以快速的开发单个微服务。
- 约定大于配置。
- SpringCloud(协调一切)
- SpringCloud是基于SpringBoot实现的。
- SpringCloud DataFlow(连接一切)
学习SpringBoot的前提需要完全掌握 Spring 和 SpringMVC 。
2. IOC
2.1 IOC思想
-
在之前的代码中,程序创造对象的主动权在程序员手上,即将代码写死。
-
使用了set方法注入对象后,代码不再写死,而是根据用户需求创造对象,主动权在用户手上。
这种思想,从本质上解决了问题,程序员不再管理对象的创建,系统的耦合性大大降低,可以更加专注的在业务的实现上。
2.2 IOC本质
控制反转IOC(Inversion of Control)是一种设计思想,DI(依赖注入)是实现IOC的一种方法。
IOC是Spring框架的核心内容。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入。
2.3 IOC例子
现有一dao接口 UserDao 和多个实现类 UserDaoImpl UserDaoMySqlImpl UserDaoOracleImpl
传统方法创建对象:在Service实现类中创建接口UserDao的实例对象,但这样的写法会将代码写死,主动权在程序员手上。
public class UserServiceImpl{
private UserDao userDao = new UserDaoImpl();
public void print(){
userDao.getPrint();
}
}
IOC方法创建对象:在bean.xml中配置好注入对象,只需修改ref即可改变UserDao的实例对象。
<bean id="userImpl" class="com.wzj.dao.UserDaoImpl"/>
<bean id="mySqlImpl" class="com.wzj.dao.UserDaoMySqlImpl"/>
<bean id="oracleImpl" class="com.wzj.dao.UserDaoOracleImpl"/>
<bean id="UserServiceImpl" class="com.wzj.service.UserServiceImpl">
<property name="userDao" ref="mySqlImpl"/>
</bean>
UserServiceImpl实现类将不再实例化UserDao,而是通过配置文件来实例化。
public class UserServiceImpl{
private UserDao userDao;
public void print(){
userDao.getPrint();
}
}
具体实例化操作
ApplicationContext context = new ClassPathXmlApplicationContext(bean.xml);
UserServiceImpl userServiceImpl = (UserServiceImpl)context。getBean("UserServiceImpl");
userServiceImpl.print();
小结:所谓的IOC,就是对象由Spring来创建,管理和装配!
2.4 IOC创建对象方式
-
默认使用无参构造创建对象。
-
若要使用有参构造创建对象:
-
下标赋值:
<bean id="User" class="com.wzj.pojo.User"> <constructor-arg index="0" value="XXX"/> </bean>
-
类型赋值(不建议使用):
<bean id="User" class="com.wzj.pojo.User"> <constructor-arg type="java.lang.String" value="XXX"/> </bean>
-
参数名赋值:
<bean id="User" class="com.wzj.pojo.User"> <constructor-arg name="name" value="XXX"/> </bean>
-
小结:在配置文件加载的时候,容器中管理的对象就已经初始化了,且地址相同。
3. 代理模式
-
为什么要学习代理模式?
因为代理模式是SpringAOP的底层。
代理模式的分类:静态代理和动态代理
3.1 静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类解决。
- 真实角色:被代理的角色。
- 代理角色:代理真实角色,并做一些附属操作。
- 客户角色:通过访问代理角色获取真实角色信息。
具体例子1:房东委托中介公司帮忙代理协助客户看房子,房东只负责提供房子和收租金。客户通过中介公司找房子。
创建一个接口类 Rent
//抽象角色:租房
public interface Rent {
void rent();
}
创建一个类 Host 实现 Rent 的出租方法。
//真实角色:房东
public class Host implements Rent{
public void rent() {
System.out.println("房东出租房子");
}
}
一般代理类不会选择使用继承的方式去代理,代理类可能会代理很多东西,继承只能代理一个。中介公司找到房东后接受委托租房,同时中介公司还负责帮助房东带客户看房子等方法。
//代理角色:中介公司
public class Proxy implements Rent{
private Host host;
public Proxy(Host host){
this.host = host;
}
public void rent() {
System.out.println("中介公司被客户委托找房东租房子");
seeHouse();
fare();
host.rent();
}
public void seeHouse(){
System.out.println("中介公司带你看房子");
}
public void fare(){
System.out.println("中介公司收中介费");
}
}
租客通过中介公司找到想联系的房东,但所有事情都是由中介公司代替房东操办,包括看房子,租房。
//客户角色:租客
public class Client {
public static void main(String[] args) {
Host host = new Host();
//不直接接触房东,而是通过中介公司代理找房子
Proxy proxy = new Proxy(host);
proxy.rent(); 中介公司被客户委托找房东租房子
中介公司带你看房子
中介公司收中介费
房东出租房子
}
}
具体例子2
创建一个接口类 UserService ,包括增删查改四个方法。
//抽象角色
public interface UserService {
void add();
void delete();
void update();
void search();
}
创建一个类 UserServiceImpl 实现 UserService 的所有方法。
//真实角色
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void search() {
System.out.println("查询了一个用户");
}
}
现要求在所有的方法之前添加日志功能,可以在实例类 UserServiceImpl 的方法前添加。但一般情况下不选择更改方法体,而是通过一个代理类去调用方法,且在调用前添加日志功能。
//代理角色
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public UserServiceProxy(UserServiceImpl userService){
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void search() {
log("search");
userService.search();
}
//给方法添加日志
public void log(String msg){
System.out.println("使用了" + msg + "方法");
}
}
客户调用接口 UserService 的方法不再直接访问 UserServiceImpl 实例类,而是通过访问代理类 UserServiceProxy 来调用 UserServiceImpl 的实例对象。这样的方式可以避免修改原有的方法体,可以更好的管理和维护代码。
//客户角色
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.add(); 使用了add方法
增加了一个用户
}
}
代理模式的好处:
- 可以是真实角色的操作更加纯粹,不用关注一些公共的业务。
- 公共业务交给代理角色,实现了业务的分工。
- 公共业务发生扩展时,方便集中管理。
代理模式的缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率降低。
3.2 动态代理
- 动态代理和静态代理的角色是一样的。
- 动态代理的代理类是动态生成的,不是我们写好的。
- 动态代理分为两大类:
- 基于接口的动态代理:JDK动态代理
- 基于类的动态代理:cglib
需要了解两个类:Proxy(代理),Invokation(调用处理程序)
具体例子1:房东委托中介公司帮忙代理协助客户看房子,房东只负责提供房子和收租金。客户通过中介公司找房子。
创建一个接口类 Rent
//抽象角色:租房
public interface Rent {
void rent();
}
创建一个类 Host 实现 Rent 的出租方法。
//真实角色:房东
public class Host implements Rent{
public void rent() {
System.out.println("房东出租房子");
}
}
创建一个自动生成代理的类 ProxyInvocationHandler
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent){
this.rent = rent;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是使用反射机制实现
seeHouse();
Object result = method.invoke(rent, args);
fare();
return result;
}
public void seeHouse(){
System.out.println("中介公司带客户看房子");
}
public void fare(){
System.out.println("中介公司收中介费");
}
}
在Client类中,我们不再调用写好的代理类,而是调用自动生成代理类现场使用代理类
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象
pih.setRent(host);
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
万能自动生成代理类 ProxyInvocationHandler 模板
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Object target;
public void setTarget(Object target){ this.target = target; }
//生成得到代理类(这里的代码是死的,只需要修改target变量)
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(target, args);
return result;
}
}
动态代理的好处:
- 可以是真实角色的操作更加纯粹,不用关注一些公共的业务。
- 公共业务交给代理角色,实现了业务的分工。
- 公共业务发生扩展时,方便集中管理。
- 一个动态代理类代理的是一个接口,一般对应一类业务。
- 一个动态代理类可以代理多个类,只要实现了同一个接口。
4. AOP
4.1 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术,是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
4.2 AOP在Spring中的作用
提供声明式事务;允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能,即与我们业务逻辑无关但需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等
- 切面(ASPECT):横切关注点被模块化的特殊对象,是一个类。
- 通知(Advice):切面必须要完成的工作,是类中的一个方法。
- 目标(Target):被通知的对象。
- 代理(Proxy):向目标对象应用通知之后创建的对象。
- 切入点(PointCut):切面通知、执行的地点的定义。
- 连节点(JointPoint):与切入点匹配的执行点。
AOP具体例子
创建接口类和实例类
public interface UserService {
void add();
void delete();
void update();
void search();
}
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("添加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void search() {
System.out.println("查询了一个用户");
}
}
方式一:使用Spring的API接口
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("添加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void search() {
System.out.println("查询了一个用户");
}
}
创建两个切面类,在调用方法前后输出日志
public class BeforeLog implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(method.getName() + "方法开始执行。。。");
}
}
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(method.getName() + "方法执行了。。。");
}
}
在 applicationContext.xml 中配置切点和切面类
<bean id="userService" class="AOPTest.service.UserServiceImpl"/>
<bean id="beforeLog" class="AOPTest.log.BeforeLog"/>
<bean id="afterLog" class="AOPTest.log.AfterLog"/>
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* *.service.UserServiceImpl.*(..))"/>
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
add方法开始执行。。。
添加了一个用户
add方法执行了。。。
}
}
方式二:使用自定义来实现AOP
创建一个自定义的切面类
public class Aspect {
public void Before(){
System.out.println("=========方法执行前=========");
}
public void After(){
System.out.println("=========方法执行后=========");
}
}
在 applicationContext.xml 中配置切点和切面类
<bean id="userService" class="AOPTest.service.UserServiceImpl"/>
<bean id="aspect" class="AOPTest.aspect.Aspect"/>
<aop:config>
<aop:aspect ref="aspect">
<aop:pointcut id="point" expression="execution(* *.service.UserServiceImpl.*(..))"/>
<aop:before method="Before" pointcut-ref="point"/>
<aop:after method="After" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
=========方法执行前=========
添加了一个用户
=========方法执行后=========
}
}
方式三:使用注解法来实现AOP
创建一个自定义的切面类
@Aspect //注解这个类是一个切面
public class aspect {
@Before("execution(* *.service.UserServiceImpl.*(..))")
public void Before(){
System.out.println("=========方法执行前=========");
}
@After("execution(* *.service.UserServiceImpl.*(..))")
public void After(){
System.out.println("=========方法执行后=========");
}
}
在 applicationContext.xml 中配置切点和切面类
<bean id="userService" class="AOPTest.service.UserServiceImpl"/>
<bean id="aspect" class="AOPTest.aspect.aspect"/>
<!-- 开启注解支持 -->
<aop:aspectj-autoproxy/>
测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口
UserService userService = (UserService) context.getBean("userService");
userService.add();
=========方法执行前=========
添加了一个用户
=========方法执行后=========
}
}
5. 声明式事务
5.1 事务的特性
- 把一组业务当成一个业务来做,要么都成功,要么都失败。
- 事务在项目开发中涉及到数据的一致性问题,十分重要。
- 事务能确保数据的完整性和一致性。
事务ACID原则:
-
原子性
-
一致性
-
隔离性
- 多个业务可能操作同一个资源,防止数据损坏。
-
持久性
- 事务一旦提交,无论系统发生什么问题,结果都不会再被影响,被持久化的写到存储器中。
5.2 spring中的事务管理
- 声明式事务:AOP
- 编程式事务:需要在代码中进行事物的管理
具体实例
- 创建实体类 User 以及 User 的数据库
public class User {
private int id;
private String name;
private String pwd;
public User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPwd() { return pwd; }
public void setPwd(String pwd) { this.pwd = pwd; }
}
- 创建接口 UserMapper 和其实例类 UserMapperImpl 和配置文件 UserMapper.xml
public interface UserMapper {
List<User> searchUser(); //查询所有用户
int addUser(User user); //添加一个用户
int deleteUser(int id); //删除一个用户
}
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
//继承 SqlSessionDaoSupport 类后,不再需要获取SqlSession,而是在配置文件提前配置注入
public List<User> searchUser() {
User user = new User(5,"赵六","999999");
//获取 UserMapper 的映射
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
//此处调用两个方法作为事务的例子
mapper.addUser(user); //添加第5号用户
mapper.deleteUser(4); //删除第4号用户
return mapper.searchUser();
}
public int addUser(User user) {
return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wzj.mapper.UserMapper">
<select id="searchUser" resultType="user">
select * from user;
</select>
<insert id="addUser" parameterType="user">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd});
</insert>
<!-- 此处故意在删除语句的最后添加"/"符号,使该sql语句无法生效 -->
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}/;
</delete>
</mapper>
-
配置所需的配置文件
- mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="com.wzj.pojo"/> </typeAliases> </configuration>
- spring-dao.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 连接数据库 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/user? useUnicode=true& characterEncoding=UTF-8& serverTimezone=Asia/Shanghai& useSSL=true"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- 配置 sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 绑定Mybatis配置文件 --> <!-- 如果需要使用Mybatis配置文件,则需要配置 configLocation --> <!-- 如果没有使用到Mybatis配置文件的mapper映射,则需要配置 mapperLocations --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:com/wzj/mapper/*.xml"/> </bean> <!-- 配置声明式事务 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 结合AOP实现事物的植入 --> <!-- 配置事务通知 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!-- 配置事务切入 --> <aop:config> <aop:pointcut id="txPointCut" expression="execution(* com.wzj.mapper.*.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/> </aop:config> </beans>
- 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" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 获取 spring-dao.xml 文件下的资源 sqlSessionFactory --> <import resource="spring-dao.xml"/> <!-- 将 sqlSessionFactory 注入到 SqlSessionDaoSupport 类中 --> <bean id="userMapper" class="com.wzj.mapper.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory"/> </bean> </beans>
-
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
for (User user : userMapper.searchUser()) {
System.out.println(user);
}
}
}
无事务结果:当配置文件不存在配置声明式事务的代码时,在调用查询方法时,控制台抛出sql语句异常的错误(delete的sql语句多加了“/”),但系统不会回滚,依然执行了添加语句。
有事务结果:当配置文件存在配置声明式事务的代码时,在调用查询方法时,控制台抛出sql语句异常的错误(delete的sql语句多加了“/”),系统回滚,添加语句不再执行,数据库不发生改变。
若代码执行过程中不存在错误,则代码会执行成功不报错,且数据也会根据代码需求进行相应的修改。