Spring中的IOC和AOP以及Spring中的声明式事务
Spring中的IOC和AOP以及Spring中的声明式事务(基于狂神说Java-Spring)
Spring中的IOC
IOC(Inversion Of Controller)又称控制反转,是一种通过(xml或者注解)并通过第三方生成或获取特定对象的方式,在Spring中实现控制反转的是IOC容器,其实现方法是依赖注入(DI:Dependency Injection)
- 控制:控制对象的创建,传统的Java面向对象编程,对象是由程序本身控制实现创建的。用了Spring之后,对象都是由Spring来进行创建的
- 反转:程序现在不用自己创建对象,而是自己被动的接收一个对象
总结:所谓的控制反转,就是创建对象的方式反转了,总的来说,对象可以交给Spring容器进行创建,管理,装配
Spring 当中的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
注解说明
- @Autowired:Spring注解自动装配,可以通过类型和名字,但是当Bean实例多于两个时无法装配,需要通过@Quefilier注解配合使用指定某个Bean实例(注意:@Qualifier不能单独使用),如果允许对象为null,设置required = false,required属性默认为true,即为**@Autowired(required = false)**
- @Nullable:如果某个字段标记了这个注解后,说明这个字段可以为null
- @Resource:此注解是JavaEE原生注解实现,同样可以实现自动装配,在Bean实例多于两个时可以通过name属性指定Bean实例(但是这边有一个小问题,随着JDK版本的提升,JavaEE原生注解就不再支持,所以在高版本的JDK中还需要导入相关的Maven依赖),@Resource会通过name属性进行查找,如果说查找不到才会通过Bean的类型进行查找。
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
-
@Component:组件,使用此注解声明当前Java类交由Spring进行管理(在此注解的基础上还增加了三个衍生注解,基于MVC三层架构)
- @Repository:dao层注解
- @Service:Service层注解
- @Controller:Controller层注解
注意事项:翻阅底层源码可以发现这四个注解的代码都是一样的,意味着他们的功能都是一样的,都是代表将某个类交由Spring托管,进行Bean的装配
对于xml和注解:
- xml配置在有些情况下会较为复杂,但是xml文件配置适用于任何场合
- 注解,对于注解,它的作用范围最大只能在当前这个Java类中,无法延伸到其他的类其他的包中,并且在后期项目维护极为不便
所以针对这种情况最直接的解决方案就是两种方式进行组合(最佳实现)
- xml用来对Bean进行统一的管理
- 注解用来对属性进行注入处理
- 在使用的过程中,必须要让注解生效,就需要在xml中进行开启注解支持
<!--扫描包,在指定包下的注解即刻生效-->
<context:component-scan base-package="com.hrc"/>
<context:annotation-config/>
回顾代理模式
SpringAOP(Aspect Oriented Programming)的底层就是代理设计模式!!!
代理模式有两种:静态代理和动态代理
静态代理
业务分析:
- 抽象角色:一般都会用接口(使用较多)或者是抽象类来解决
// 以租房为例实现静态代理
public interface Rent {
void rent();
}
- 真实角色:处理真实业务,同时被代理角色进行代理
// 房东
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东要出租房子(真实业务)");
}
}
- 代理角色:代理真实角色,并且实现属于自己的扩展业务
public class MiddleProxy extends Host {
// 处理真实业务
public void rent() {
this.rentBefore();
super.rent(); // 真实业务
this.rentAfter();
this.res();
}
// 实现扩展业务
public void rentBefore() {
System.out.println("中介接单");
}
public void rentAfter() {
System.out.println("中间商赚差价");
}
public void res() {
System.out.println("扩展业务完成");
}
}
- 用户:访问代理对象
public class Client {
public static void main(String[] args) {
// 代理角色,中介帮房东租房子,但是中介会做一些房东不会做的扩展业务
MiddleProxy proxy = new MiddleProxy();
proxy.rent();
}
}
代理模式的好处:
- 可以让真实业务操作变得更加纯粹,说白了就是“我”该干嘛就干嘛,不用去关心其他的扩展业务
- 公共业务就交给了其他的代理角色,实现业务的分工处理
- 公共业务延伸出其他的扩展业务时,方便集中进行管理
缺点:
- 一个真实业务就需要一个代理对象进行代理,一旦真实业务多了之后,代码量提升就会造成冗余,开发效率降低
动态代理
业务分析:
-
抽象角色:与静态代理相同
-
代理角色:动态生成,不是由开发者自己编写,而是开发者自定义一个模板然后交给程序去生成代理对象
-
动态代理的实现机制有两种:一种是基于接口的动态代理,还有一种就是基于cglib实现动态代理
- 基于接口:JDK动态代理
- 基于类的动态代理,也就是cglib动态代理(需要导入额外的cglib相关依赖)
- Java字节码实现动态代理:Javassist
首先需要熟悉两个Java类:Proxy:代理;InvocationHandler:调用处理程序
动态代理的好处:
- 静态代理的优点全部具备
- 一个动态代理类代理的是一个接口,一般就是对应其某一个类型的业务,而不是单个业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
优化之前实现的房东买房子的业务操作
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 用此类动态生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
// 在生成代理类的同时还需要一个被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 生成代理类
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
* 处理代理实例,并返回结果
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
// 动态代理的本质就是使用反射机制实现
Object res = method.invoke(target, args);
return res;
}
public void log(String msg) {
System.out.println("【INFO】" + msg);
}
}
public class Client {
public static void main(String[] args) {
// 真实业务主题
Host host = new Host();
// 生成代理角色
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(host); // 设置代理对象
// 动态生成代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
SpringAOP
AOP(Aspect Oriented Programming),意思是面向切面编程,它是OOP(Object-Oriented Programming 面向对象编程)的一种延续
在不破坏原有代码实现的基础上,利用“织入”的模式来实现一些代码的动态配置
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高代码可重用性,提高开发效率
使用SpringAOP需要额外导入相关依赖
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
实现AOP的方式之一:使用Spring的接口
主要是通过实现Spring的接口来进行切面的处理
public interface UserService {
void insert();
void delete();
void update();
void select();
}
/*--------------两个类分开写----------------*/
public class UserServiceImpl implements UserService {
@Override
public void insert() {
System.out.println("【INSERT】增加了一个用户");
}
@Override
public void delete() {
System.out.println("【DELETE】删除了一个用户");
}
@Override
public void update() {
System.out.println("【UPDATE】修改了一个用户");
}
@Override
public void select() {
System.out.println("【SELECT】查询了一个用户");
}
}
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
/**
* 在实现操作业务之前首先要先做一些日志信息
* @param method 要执行的目标对象的方法
* @param objects 参数,对象数组
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行了");
}
}
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
// 后置通知,并且接收返回值
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
}
}
<!--注册Bean-->
<bean id="userService" class="com.hrc.service.UserServiceImpl"/>
<bean id="log" class="com.hrc.log.Log"/>
<bean id="afterLog" class="com.hrc.log.AfterLog"/>
<!--配置aop-->
<aop:config>
<!--首先需要设置切入点pointcut,expression,是一个AspectJ表达式-->
<aop:pointcut id="pointcut" expression="execution(public * com.hrc.service..*(..))"/>
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
这里有一个小东西叫做AspectJ表达式,Spring容器中支持此表达式,用来进行切入点的定义
其语法格式为:方法注解 权限修饰符 返回值 全限定类名 方法名 (参数);这里有几个地方需要注意
- 方法注解:可选操作,即一个方法上的注解,例如@Override
- 权限修饰符:public,protected,default(权限修饰符为可选操作,即可有可无)
- 返回值:有多种,这里可以用 * 来表示这个表达式的返回值为任意返回值
- 全限定类名:其名称格式为,包.类名称;如果这个包下面还有子包,可以使用“…”来代替所有的子包
- 方法名:普通普通的方法名称,这边可以和全限定类名进行组合,所以总的名称格式可以为包.类名称.方法名(参数)
- (参数):括号里可以使用“…”来表示任意的参数
import com.hrc.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 动态代理代理的是接口,实现类不需要被代理
UserService service = context.getBean("userService", UserService.class);
service.insert();
}
}
SpringAOP的实现方式二:自定义类实现 AOP
自定义类实现AOP只需要编写自定义类,无需实现Spring自带的接口,通过配置文件进行切面的处理配置
public class MyselfClass {
public void before() {
System.out.println("【INFO---BEFORE】业务处理之前");
}
public void after() {
System.out.println("【INFO---AFTER】业务处理之后");
}
}
<bean id="myselfClass" class="com.hrc.myClass.MyselfClass"/>
<aop:config>
<!--aop:aspect标签,自定义切面,ref,引用要切入的类-->
<aop:aspect ref="myselfClass">
<!--定义切入点-->
<aop:pointcut id="pointcut" expression="execution(public * com.hrc.service..*(..))"/>
<!--通知,这个概念指的是实现切入点的扩展业务-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
SpringAOP 实现注解配置
// 使用注解方式实现AOP
@Aspect // 标注此类为一个切面
public class AnnotationPointcut {
@Before("execution(public * com.hrc.service..*(..))")
public void before() {
System.out.println("【INFO】方法执行前");
}
@After("execution(public * com.hrc.service..*(..))")
public void after() {
System.out.println("【INFO】方法执行后");
}
// 在环绕增强中,可以给定一个参数,代表我们要获取的切入的点
@Around("execution(public * com.hrc.service..*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("【INFO】使用环绕之前");
System.out.println("【INFO】获得签名:" + pj.getSignature());
// 执行此方法
Object proceed = pj.proceed();
System.out.println("【INFO】获得执行对象:" + proceed);
System.out.println("【INFO】使用环绕之后");
}
}
<bean id="annotationPointcut" class="com.hrc.myClass.AnnotationPointcut"/>
<!--开启注解支持 在开启注解支持的时候,Spring容器有两种实现AOP的方式
1.JDK动态代理(Spring默认)
2.cglib动态代理
设置两种实现AOP的方式有一个属性标签proxy-target-class="false"
如果属性值为false,那么就是默认JDK动态代理
如果为true,就会以cglib动态代理实现AOP,两种实现方式的运行结果都是相同的
实现机制不一样
-->
<aop:aspectj-autoproxy/>
Spring整合MyBatis
实现步骤
- 导入相关依赖
- junit 单元测试
- MyBatis
- MySQL数据库
- Spring相关
- SpringAOP的相关依赖
- MyBatis-Spring(一个新的依赖,可以和Spring完美整合)
- 编写配置文件
- 测试
相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--要想使用Spring操作数据库的话,还需要一个相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
回顾MyBatis
- 编写实体类
- 编写核心配置文件
- 编写接口
- 编写mapper.xml
- 测试
MyBatis-Spring
- 编写数据源配置
<!--DataSource:使用Spring的数据源替换掉MyBatis的配置,除此之外还有 c3p0 dbcp druid
这里使用的是Spring当中提供的JDBC:org.springframework.jdbc.datasource.*
-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8"/>
</bean>
- SqlSessionFactory
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--绑定MyBatis配置文件-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath*:com.hrc.mapper/UserMapper.xml"/>
</bean>
- SqlSessionTemplate
<!--SqlSessionTemplate:他就是我们使用的SqlSession
翻阅底层源码,发现这个类,没有相应的set方法,所以在使用Spring容器注入时,
需要使用构造器注入的方式进行资源注入
-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
- Mapper接口加实现类
import com.hrc.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> getUsers();
}
import com.hrc.mapper.UserMapper;
import com.hrc.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper {
// 在以前都使用SqlSession来执行所有操作,现在只需要使用SqlSessionTemplate
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> getUsers() {
return sqlSession.getMapper(UserMapper.class).getUsers();
}
}
- 测试
import com.hrc.mapper.UserMapper;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper mapper = context.getBean("userMapper", UserMapper.class);
mapper.getUsers().forEach(System.out::println);
}
}
Spring-MyBatis还有一种整合方式,就是直接实现SqlSessionDaoSupport类
<!--这是整合MyBatis的第二种方式,继承SqlSessionDaoSupport类
它是一个抽象类,翻阅源码可以发现,此类中内置SqlSessionTemplate,
但是需要一个SqlSessionFactory,所以在交由Spring容器进行管理时,
需要注入资源的属性是SqlSessionFactory
-->
<bean id="mapper" class="com.hrc.mapper.impl.UserMapperImplement">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
import com.hrc.mapper.UserMapper;
import com.hrc.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImplement extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> getUsers() {
// 由于继承了SqlSessionDaoSupport类,可以直接使用getSqlSession方法得到进一步简化
return getSqlSession().getMapper(UserMapper.class).getUsers();
}
}
Spring中的声明式事务
回顾事务的基础知识
- 要么同时成功,要么同时失败
- 事务在项目开发中占据非常重要的位置,因为涉及到事务的一致性问题,所以必须认真对待
- 确保完整性和一致性
事务的ACID原则
- 原子性(Atomicity):确保一个业务当中的所有操作,要么同时成功,要么同时失败
- 一致性(Consistency):事务的运行不改变数据当中的一致性,举个例子,对于银行转账来说,a用户有1000元,b用户有1000元,不管这个事务最后是成功还是失败,a+b的和始终都是2000
- 隔离性(Isolation):多个事务可能操作同一个资源,防止资源损坏。事务与事务之间是处于隔离的状态,一个事务不应该影响其他事务的运行效果
- 永久性(Durability):事务一旦提交,无论系统发生什么问题,都不会再被影响,被持久化的写到存储器中
Spring中的事务管理
- 声明式事务:AOP
<!--在Spring中配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--结合AOP实现事务的“织入”-->
<!--配置事务通知-->
<tx:advice id="interceptor" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<!--配置事务的传播属性-->
<tx:attributes>
<tx:method name="addUser" propagation="REQUIRED" isolation="DEFAULT"/>
<tx:method name="selectUsers"/>
<tx:method name="delUser" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/> <!-- * 代表所有方法-->
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(public * com.hrc.mapper..*.*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="txPointcut"/>
</aop:config>
- 编程式事务:需要在代码中进行事务的管理
为什么需要事务
- 如果不配置事务,可能存在数据提交不一致的情况
- 如果不在Spring中去配置事务,那么就需要在代码中手动配置事务
- 事务在项目的开发中十分重要,涉及到数据的一致性和完整性
最后再多聊一嘴Spring中事务的传播属性
传播属性
刚刚在使用xml进行事务的织入时有一个标签叫做propagation,propagation在xml中可以进行事务的传播属性的处理配置,它主要有以下几种属性
(面试可能会问到,建议最好还是记一下)
- REQUIRED:这个属性表示当前业务必须存在事务的处理,如果没有,就会自动创建一个新的事务(这个属性值在开发中属于常用,并且也是Spring默认处理的传播属性)
- SUPPORTS:如果现在有事务,那么当前业务就直接使用事务进行处理,如果没有,就以非事务的方式运行
- MANDATORY:如果当前开启了一个事务,就会以事务的方式运行,如果没有事务,就会抛出相应的异常
- REQUIRES_NEW:这个属性总是会开启新的事务,如果在开启新事务之前已经有了一个事务,就会将这个事务挂起
- NOT_SUPPORTED:总是以非事务的方式处理业务,如果当前有事务,就会将当前事务挂起
- NEVER:总是以非事务的方式处理业务,如果当前有事务,则会抛出相应的异常
- NESTED:如果当前有一个事务正在进行业务处理,那么这个属性值就会再次开启一个事务,内嵌进已经存在的事务之中,如果当前没有事务,则会以REQUIRED的方式进行处理
本文地址:https://blog.csdn.net/weixin_46468474/article/details/107647359
推荐阅读
-
Spring中的JDBCTemplate、Spring基于AOP的事务控制、Spring中的事务控制
-
spring.net中的IoC、DI和MVC
-
spring中过滤器(filter)、拦截器(interceptor)和切面(aop)的执行顺序
-
在Spring IoC中,依赖注入和依赖查找的数据来源一样吗?
-
Spring 框架的概述以及Spring中基于XML的IOC配置
-
浅谈Spring中IOC的理解和认知
-
Spring中的IOC和AOP以及Spring中的声明式事务
-
Spring boot中嵌入式Servlet容器的配置和启动原理
-
Spring中IoC两种接口和两种依赖注入方式的比较
-
Spring的注释配置和XML配置的比较 spring声明式事务注解配置和XML配置