代理模式
这就是SpringAOP的底层
静态代理
面向对象的设计原则(7大原则)
- 开闭原则: 是总纲,告诉我们要对扩展开放,对修改关闭;
通过“抽象约束、封装变化”来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。
- 里氏替换原则: 告诉我们不要破坏继承体系;
子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
- 依赖倒置原则:告诉我们不要破坏继承体系;
要面向接口编程,不要面向实现编程。
- 更加细致的要求:
(1)每个类尽量提供接口或抽象类,或者两者都具备。
(2)变量的声明类型尽量是接口或者是抽象类。
(3)任何类都不应该从具体类派生。
(4)使用继承时尽量遵循里氏替换原则。
- 单一职责原则: 告诉我们实现类/方法要职责单一;
一个类只负责一项职责,一个方法只负责处理一项事情
- 接口隔离原则: 告诉我们在设计接口的时候要精简单一;
客户端不应该被迫依赖于它不使用的方法。
- 和单一职责原则的区别:
它和单一职责原则差不多,一个接口只服务于一个子模块或业务逻辑。只是单一职责是侧重于约束类和方法。而借口隔离侧重约束接口。
- 更加细致的要求:
(1)接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
(2)为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
(3)了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同,深入了解业务逻辑。
(4)提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。
- 迪米特法则:告诉我们要降低耦合度;
“只与你的直接朋友交谈,不跟“陌生人”说话”。即如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。又叫作最少知识原则
-- 从依赖症和被依赖这的角度的实现:
• 从依赖者的角度来说,只依赖应该依赖的对象;
• 从被依赖者的角度说,只暴露应该暴露的方法。
-- 更加细致的要求:
(1)在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
(2)在类的结构设计上,尽量降低类成员的访问权限。
(3)在类的设计上,优先考虑将一个类设置成不变类。
(4)在对其他类的引用上,将引用其他对象的次数降到最低。
(5)不暴露类的属性成员,而应该提供相应的访问器(set和get方法)。
(6)谨慎使用序列化(Serializable)功能.
- 合成复用原则:告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。(又叫组合/聚合复用原则)
动态代理
利用反射机制在运行时创建代理类。
接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口。
--动态代理具体步骤:
通过实现 InvocationHandler 接口创建自己的调用处理器;
通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
package com.depressiom.pojo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @ClassName ProxyInvocatioHandler
* @Description 自动生成代理类
* @Date 2022/12/5
* @Author depressiom
*/
public class ProxyInvocatioHandler 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);
}
//处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target,args);
}
}
AOP
AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
AOP在Spring中的作用
提供声明事务,允许用户自定义切面
-
横切关注点:跨越引用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但我们需要关注的部分,就是横切关注点,如日志,安全,缓存,事务等。。
-
切面(ASPECT):横切:关注点被关注点模块化的特殊对象,即,它是一个类。
-
通知(Advice):切面必须要完成的工作,即,它是类中的一个方法。
-
目标(Target):被通知对象.
-
代理(Proxy):向目标对象对应通知之后创建的对象。
-
切入点(JointCut):切面通知执行的“地点”的定义
-
连接点(JointPoint):与切入点匹配的执行点
使用Spring实现AOP
方式1:使用Spring API的接口 【API接口的实现】
接口
package com.depressiom.service;
/**
* @ClassName UserService
* @Description 接口
* @Date 2022/12/6
* @Author depressiom
*/
public interface UserService {
public void ADD();
public void DELETE();
public void UPDATE();
public void QUERY();
}
实现类
package com.depressiom.service;
/**
* @ClassName UserServiceImp
* @Description 实现
* @Date 2022/12/6
* @Author depressiom
*/
public class UserServiceImp implements UserService{
@Override
public void ADD() {
System.out.println("添加了一个方法");
}
@Override
public void DELETE() {
System.out.println("删除了一个方法");
}
@Override
public void UPDATE() {
System.out.println("修改了一个方法");
}
@Override
public void QUERY() {
System.out.println("查询了一个方法");
}
}
log 日志 一前 一后
package com.depressiom.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @ClassName Log
* @Description 日志
* @Date 2022/12/6
* @Author depressiom
*/
public class Log implements MethodBeforeAdvice {
/***
*
* @param method 要执行的目标对象方法
* @param args 参数
* @param target 目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
package com.depressiom.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
* @ClassName AfterLog
* @Description After日志
* @Date 2022/12/6
* @Author depressiom
*/
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"方法,返回结果为:"+returnValue);
}
}
applicationcontext.xml
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册beans -->
<bean id="userService" class="com.depressiom.service.UserServiceImp"/>
<bean id="log" class="com.depressiom.log.Log"/>
<bean id="afterLog" class="com.depressiom.log.AfterLog"/>
<!-- 方式一:使用原生Spring API接口 -->
<!-- 配置AOP : 需要导入aop的约束 -->
<aop:config>
<!-- 切入点 expression:表达式 execution(要执行的位置!***) -->
<aop:pointcut id="pointcut" expression="execution(* com.depressiom.service.UserServiceImp.*(..))"/>
<!-- 执行环绕 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
import com.depressiom.service.UserService;
import com.depressiom.service.UserServiceImp;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @ClassName Mytest
* @Description 测试
* @Date 2022/12/6
* @Author depressiom
*/
public class Mytest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
//动态代理 代理的是接口
//UserServiceImp userService = context.getBean("userService",UserServiceImp.class);
UserService userService = (UserService) context.getBean("userService");
userService.ADD();
}
}
方式2:自定义 来实现 AOP【切面的定义】
自定义切入点
package com.depressiom.diy;
/**
* @ClassName DiyPointCut
* @Description 自定义切入点
* @Date 2022/12/6
* @Author depressiom
*/
public class DiyPointCut {
public void before(){
System.out.println("======方法执行前=======");
}
public void after(){
System.out.println("======方法执行后=======");
}
}
applicationcontext.xml
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册beans -->
<bean id="userService" class="com.depressiom.service.UserServiceImp"/>
<bean id="log" class="com.depressiom.log.Log"/>
<bean id="afterLog" class="com.depressiom.log.AfterLog"/>
<!-- 方式一:使用原生Spring API接口 -->
<!-- 配置AOP : 需要导入aop的约束 -->
<!-- <aop:config>-->
<!-- <!– 切入点 expression:表达式 execution(要执行的位置!***) –>-->
<!-- <aop:pointcut id="pointcut" expression="execution(* com.depressiom.service.UserServiceImp.*(..))"/>-->
<!-- <!– 执行环绕 –>-->
<!-- <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>-->
<!-- <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>-->
<!-- </aop:config>-->
<!-- 方式二:自定义类 -->
<bean id="diy" class="com.depressiom.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面: ref:要引用的类 -->
<aop:aspect ref="diy">
<!--
切入点表达式:execution(* 包名.*.*(..))
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:方法返回类型, *号表示所有的类型。
3、包名:表示需要拦截的包名。
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面( )里面表示方法的参数,两个句点表示任何参数
其中除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
-->
<aop:pointcut id="pointcut" expression="execution(* com.depressiom.service.UserServiceImp.*(..))"/>
<!-- 通知 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
方式3:使用注解实现
package com.depressiom.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @ClassName AnnotationPointCut
* @Description 使用注解方式实现AOP
* @Date 2022/12/6
* @Author depressiom
*/
@Aspect
public class AnnotationPointCut {
@Before("execution(* com.depressiom.service.UserServiceImp.*(..))")
public void before(){
System.out.println("======方法执行前=======");
}
@After("execution(* com.depressiom.service.UserServiceImp.*(..))")
public void after(){
System.out.println("======方法执行后=======");
}
/**
* 在环绕增强中,我们可以给定一个参数,代表我们需要获取处理切入的点
*/
@Around("execution(* com.depressiom.service.UserServiceImp.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
System.out.println(joinPoint.getSignature()); //获得签名
//执行方法
Object proceed = joinPoint.proceed();
System.out.println("环绕后");
}
}
<!-- 方式三: -->
<bean id="annotationPointCut" class="com.depressiom.diy.AnnotationPointCut"/>
<!-- 开启注解支持! JDK(默认 proxy-target-class="false") cglib-->
<aop:aspectj-autoproxy/>
整合mybatis-spring官网
设置dataSource
<!-- DataSource:使用Spring的数据源替换mybatis的配置 -->
<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/blog"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</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/depressiom/dao/BlogMapper.xml"/>
</bean>
sqlSession
<!-- SqlSessionTemplate: 就是sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!-- 只能使用构造器注入,没有set方法 -->
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
将mapper接口 实现
package com.depressiom.dao;
import com.depressiom.pojo.Blog;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
/**
* @ClassName BlogMapperImp
* @Description 实现类
* @Date 2022/12/6
* @Author depressiom
*/
public class BlogMapperImp implements BlogMapper{
private SqlSessionTemplate sqlsession;
// bean set注入
public void setSqlsession(SqlSessionTemplate sqlsession) {
this.sqlsession = sqlsession;
}
/**
* 之前都使用sqlSession执行,现在使用SqlSessionTemplate
* @return
*/
@Override
public List<Blog> getList() {
BlogMapper mapper = sqlsession.getMapper(BlogMapper.class);
return mapper.getList();
}
}
测试
@Test
public void test() throws IOException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationcontext.xml");
BlogMapperImp mapper = context.getBean("mapper",BlogMapperImp.class);
for (Blog blog : mapper.getList()) {
System.out.println(blog);
}
}
第二种方法替代SqlSessionTemplate
set注入mapper
声明式事务
事务管理对于企业应用而言至关重要。它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性。就像银行的自助取款机,通常都能正常为客户服务,但是也难免遇到操作过程中机器突然出故障的情况,此时,事务就必须确保出故障前对账户的操作不生效,就像用户刚才完全没有使用过取款机一样,以保证用户和银行的利益都不受损失。
事务特性
-
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用;
-
一致性(Consistency):一旦事务完成(不管是成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏;
-
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏;
-
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中;
事务类型
-
编程式事务:通过编程代码在业务逻辑时需要时自行实现,粒度更小;
-
声明式事务:通过注解或XML配置实现;
-
JDBC事务:即为上面说的数据库事务中的本地事务,通过connection对象控制管理;
-
JTA事务:指Java事务API(Java Transaction API),是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商(如WebSphere Application Server)提供实现,JTA事务比JDBC更强大,支持分布式事务;
-
本地事务:普通事务,独立一个数据库,能保证在该数据库上操作的ACID;
-
分布式事务:涉及两个或多个数据库源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组成),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;
-
数据库分为本地事务和全局事务
-
Java事务类型分为JDBC事务和JTA事务
-
按是否通过编程分为声明式事务和编程式事务
声明式事务
<?xml version="1.0" encoding="UTF8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.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">
<!-- DataSource:使用Spring的数据源替换mybatis的配置 -->
<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/blog"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<!-- 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/depressiom/dao/BlogMapper.xml"/>
</bean>
<bean id="blogMapperImp" class="com.depressiom.dao.BlogMapperImp">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- 配置声明式事务 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<!-- 结合AOP实现事务的织入 -->
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 给哪些方法配置事务 -->
<!-- 配置事务的传播特性 propagation -->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED"/>
<tx:method name="delete" propagation="REQUIRED"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置事务切入 -->
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.depressiom.dao.BlogMapperImp.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<aop:aspectj-autoproxy/>
</beans>
事务的传播特性propagation 的七种属性
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 这是默认的传播行为。表示当前方法必须运行在事务中,如果当前事务存在,就直接使用这个事务,否则,开启一个新的事务。简单地说就是,如果存在事务,就用该事务,否则就创建一个事务拿来用。 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在自己的事务中,会启动一个新的事务,如果存在当前事务,在执行期间就会挂起当前事务。 |
PROPAGATION_SUPPORTED | 表示当前方法不需要事务上下文,但如果存在当前事务的话,那么就在这个事务中执行。简单地说,如果存在事务就用,不存在事务就非事务执行。 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,就直接抛异常。 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中,如果存在当前事务,那么在方法运行期间,会直接挂起当前事务。 |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中,如果当前有事务运行,会直接抛异常。 |
PROPAGATION_NESTED | 表示如果当前已存在一个事务,那么该方法会嵌套在事务中运行,嵌套的事务可以独立于当前事务单独提交或回滚;如果当前不存在事务,那么就与PROPAGATION_REQUIRED一样。 |
package com.depressiom.dao;
import com.depressiom.pojo.Blog;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
/**
* @ClassName BlogMapperImp
* @Description 实现类
* @Date 2022/12/6
* @Author depressiom
*/
public class BlogMapperImp extends SqlSessionDaoSupport implements BlogMapper{
//private SqlSessionTemplate sqlsession;
//// bean set注入
//public void setSqlsession(SqlSessionTemplate sqlsession) {
// this.sqlsession = sqlsession;
//}
/**
* 之前都使用sqlSession执行,现在使用SqlSessionTemplate
* @return
*/
@Override
public List<Blog> getList() {
Blog blog =new Blog(3,"人与自然");
BlogMapper mapper = getSqlSession().getMapper(BlogMapper.class);
mapper.addBlog(blog);
mapper.deletBlog(2);
return mapper.getList();
}
@Override
public int addBlog(Blog blog) {
return getSqlSession().getMapper(BlogMapper.class).addBlog(blog);
}
@Override
public int deletBlog(int id) {
return getSqlSession().getMapper(BlogMapper.class).deletBlog(id);
}
}