spring学习笔记(下)
文章目录
11、代理模式
为什么要学习代理模式?因为这是springAOP的底层
代理模式的分类:
- 静态代理
- 动态代理
11.1 静态代理
角色分析:
- 抽象角色:一般使用接口或抽象类
- 真实角色:被代理的角色
- 代理角色:代理真实角色,代理时一般会做一些附属操作
- 客户:访问代理角色
代码步骤:
- 接口
public interface Rent {
public void rent();
}
- 真实角色
public class Landlord implements Rent{
public void rent() {
System.out.println("房东出租房屋");
}
}
- 代理角色
public class Agent implements Rent{
private Landlord landlord;
public Agent() {
}
public Agent(Landlord landlord) {
this.landlord = landlord;
}
public void rent(){
seeHouse();
heTong();
landlord.rent();
}
public void seeHouse(){
System.out.println("中介带你看房");
}
public void heTong(){
System.out.println("签租赁合同");
}
}
- 客户访问代理角色
public class Client {
public static void main(String[] args) {
//房东要租房子
Landlord landlord = new Landlord();
//中介代理房东租房子,但有一些附属操作
Agent agent = new Agent(landlord);
agent.rent();
}
}
代理模式的好处:
- 使真实角色的操作更加纯粹,不去关注一些公共业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展时,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率降低
加深理解:
业务层接口
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
业务层实现
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 query() {
System.out.println("进行一系列操作查询了一个用户");
}
}
客户使用
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.add();
}
}
现在,公司有需求,要求在增删改查中增加打印日志的功能
- 我们可以在业务层实现类的代码中直接添加打印日志的功能,但改变了业务层实现类的代码,而这是开发中的大忌!(修改了原有的代码,可能出现一些错误使得原本正常的程序不能运行)
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("log:增加了一个用户");
System.out.println("进行一系列操作增加了一个用户");
}
......
}
- 使用代理
public class UserServiceAgency implements UserService{
private UserServiceImpl userService;
public void setUserService(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 query() {
log("query");
userService.query();
}
//添加打印日志的功能
public void log(String method){
System.out.println("log: "+method);
}
}
客户
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
//使用代理
UserServiceAgency userServiceAgency = new UserServiceAgency();
userServiceAgency.setUserService(userService);
userServiceAgency.add();
}
}
11.2 动态代理
-
动态代理的代理类是自动生成的,不是我们自己写好的
-
动态代理分为两大类:基于接口的,基于类的
-
- 基于接口:JDK动态代理
-
- 基于类:cglib
-
- java字节码实现:javasist
需要了解两个类:Proxy:代理 ,InvocationHandler:调用处理程序
InvocationHandler:是由代理实例的 调用处理程序实现的接口
Proxy:提供了创建动态代理类和实例的静态方法
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 invoke = method.invoke(rent,args);
return invoke;
}
public void seeHouse(){
System.out.println("随中介去看房子");
}
}
public class Client {
public static void main(String[] args) {
//真实角色
Landlord landlord = new Landlord();
//代理角色,现在没有,通过处理器动态创建
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//处理要调用的接口对象
handler.setRent(landlord);
//获取代理类 这里的proxy是动态生成的,我们并没有写
Rent proxy = (Rent) handler.getProxy();
proxy.rent();
}
}
动态代理的好处:
- 使真实角色的操作更加纯粹,不去关注一些公共业务
- 公共业务交给代理角色,实现了业务的分工
- 公共业务发生扩展时,方便集中管理
- 一个动态代理类代理的是一个接口,一般对应一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
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);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过反射得到方法名
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String msg){
System.out.println("[DeBug]"+msg);
}
}
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色,现在没有,通过处理器动态创建
ProxyInvocationHandler handler = new ProxyInvocationHandler();
handler.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService) handler.getProxy(); //动态生成代理类
proxy.add();
}
}
12、AOP
12.1 AOP的概念
-
AOP(Aspect-oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
-
AOP通过提供另一种思考程序结构的方式来补充面向对象编程(OOP),OOP中模块化的关键单元是类,而AOP中模块化的关键单元是“切面”(横切关注点)
-
切面往往是一动作片段领域,跨越多个模块的方法或功能,例如事务管理的模块化,日志,缓存
-
换而言之,OOD/OOP面向名词领域,AOP面向动词领域
横切关注点:跨越多个模块的方法或功能,即与业务逻辑无关,但需要我们关注的部分。例如:日志,缓存,安全,事务等
切面(aspect):横切关注点被模块化的特殊对象,即是一个类
通知(advice):切面必须要完成的工作,即是类中的一个方法
目标(target):被通知的对象
代理(proxy):向目标对象应用通知后 创建的对象(生成的代理类)
切入点(pointCut):执行的地点
连接点(jointPoint):程序执行过程中的一个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
spring AOP中,通过advice定义横切逻辑,spring支持5种类型的advice,即在不改变原有代码的情况下,去增加新的功能
通知类型 | 连接点 | 实现接口 |
---|---|---|
前置通知 | 方法前 | MethodBeforeAdvice |
后置通知 | 方法后 | MethodAfterReturningAdvice |
环绕通知 | 方法前后 | MethodInterceptor |
异常抛出通知 | 方法抛出异常 | ThrowsAdvice |
引介通知 | 类中增加新的方法属性 | IntroductionInterceptor |
12.2 AOP在spring中的作用
- 提供声明式企业服务。最重要的此类服务是 声明式事务管理。
- 让用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。
12.3 使用spring实现AOP
使用AOP织入,需要导入一个依赖包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
方式一:使用spring的API接口
前置通知
public class BeforeLog implements MethodBeforeAdvice {
/**
* method:要执行的目标对象的方法
* args:参数
* target:目标对象
* */
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行前");
}
}
后置通知
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行后,返回结果为"+returnValue);
}
}
注意:使用xml配置aop时,需要在xml中添加aop约束
<?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">
<bean id="userService" class="com.xxx.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.xxx.log.BeforeLog"/>
<bean id="afterLog" class="com.xxx.log.AfterLog"/>
<!--配置aop-->
<aop:config>
<!--切入点--> <!--execution(要执行的位置)--> <!--UserServiceImpl类的任何方法【.*】,方法中可以有任意个参数【(..)】-->
<aop:pointcut id="pointcut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<!-- 切入beforeLog, 在pointcut切入点-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<!-- afterLog, 在pointcut切入点-->
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
注意:动态代理代理的是接口
public class Test06 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口!
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
方式二:自定义来实现AOP(主要是切面定义)
自定义类
public class DiyPointCut {
public void beforeAdvice(){
System.out.println("前置通知");
}
public void afterAdvice(){
System.out.println("后置通知");
}
}
<?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">
<bean id="userService" class="com.xxx.service.UserServiceImpl"/>
<!--注册自定义类-->
<bean id="diyPointcut" class="com.xxx.diy.DiyPointCut"/>
<aop:config>
<!--自定义切面-->
<aop:aspect ref="diyPointcut">
<!--切入点-->
<aop:pointcut id="pointcut" expression="execution(* com.xxx.service.UserServiceImpl.*(..))"/>
<!--通知-->
<aop:before method="beforeAdvice" pointcut-ref="pointcut"/>
<aop:after method="afterAdvice" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
public class Test06 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口!
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
方式三:使用注解实现
自定义切面
@Aspect //标注这个类是一个切面
public class AnnotationPointCut {
@Before("execution(* com.xxx.service.UserServiceImpl.*(..))")
public void beforeAdvice(){
System.out.println("前置通知");
}
@After("execution(* com.xxx.service.UserServiceImpl.*(..))")
public void afterAdvice(){
System.out.println("后置通知");
}
@Around("execution(* com.xxx.service.UserServiceImpl.*(..))")
//给定一个参数,代表要获取处理的切入点
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
joinPoint.proceed();//执行切入点的方法
System.out.println("环绕后");
}
}
<bean id="annotationPointCut" class="com.xxx.diy.AnnotationPointCut"/>
<!--开启注解支持-->
<!--默认基于接口的JDK动态代理 :proxy-target-class="false" 基于类的cglib:proxy-target-class="true"-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
public class Test06 {
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理代理的是接口!
UserService userService = context.getBean("userService", UserService.class);
userService.add();
/*结果:
环绕前
前置通知
进行一系列操作添加了一个用户
后置通知
环绕后
*/
}
}
13、整合Mybatis
- 导入相关jar包
- junit
- mysql
- mybatis
- spring相关
- aop织入
- mybatis-spring【new】
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.15</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
-
编写配置文件
-
测试
13.1 回忆mybatis
- 编写实体类
- 编写核心配置文件
- 编写接口
- 编写mapper
- 测试
13.2 mybatis-spring
- SqlSessionFactory
在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建。
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--绑定mybatis配置文件,mybatis下的配置都可以在这里配置-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/UserMapper.xml"/>
</bean>
SqlSessionFactory 有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。
- 编写数据源
<!--DataSource 使用spring的数据源替换mybatis的配置数据源-->
<!--这里使用spring提供的JDBC:org.springframework.jdbc.datasource -->
<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/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</bean>
- SqlSessionTemplate
在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。 一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。 使用 MyBatis-Spring 之后,你不再需要直接使用 SqlSessionFactory 了,因为你的 bean 可以被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。
SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。 SqlSessionTemplate 是线程安全的,可以被多个 DAO 或映射器所共享使用。
<!--SqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--SqlSessionTemplate没有set方法,只能用构造器方法注入-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
现在,这个SqlSessionTemplate bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性
- 需要给接口加实现类,添加SqlSession 属性
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSessionTemplate;
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
public List<User> queryUser() {
return sqlSessionTemplate.getMapper(UserMapper.class).queryUser();
}
}
- 将SqlSessionTemplate 注入实现类
<bean id="userMapper" class="com.xxx.mapper.UserMapperImpl">
<property name="sqlSessionTemplate" ref="sqlSession"/>
</bean>
- 测试
public class Test07 {
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> users = userMapper.queryUser();
for (User user : users) {
System.out.println(user);
}
}
}
使用SqlSessionDaoSupport创建SqlSessionTemplate
SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。调用 getSqlSession() 方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> queryUser() {
return getSqlSession().getMapper(UserMapper.class).queryUser();
}
}
使用 SqlSessionDaoSupport 可以不需要自己创建 SqlSessionTemplate,直接通过 sqlSessionFactory 自动创建SqlSessionTemplate
<bean id="userMapper2" class="com.xxx.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
根据源码可以知道,其实还是通过set注入属性的方式获取SqlSessionTemplate
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSessionTemplate sqlSessionTemplate;
public SqlSessionDaoSupport() {
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = this.createSqlSessionTemplate(sqlSessionFactory);
}
}
14、声明式事务
14.1 回顾事务
事务的ACID原则
- 原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
- 一致性:事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
- 隔离性:一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性:一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
14.2 spring中的事务
- 声明式事务:交由容器管理事务
- 编程式事务:在代码中处理事务
结合AOP实现声明式事务的织入:
- 配置声明式事务
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 配置事务通知
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给哪些方法配置事务-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
- 事务切入
<!--把事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.xxx.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>