Spring3(代理模式 Spring1案例补充 Aop 面试题)
目录
一、代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。
代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的请求转发给目标对象,同时可以在转发请求前后进行额外的处理。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
介绍
意图
为其他对象提供一种代理以控制对这个对象的访问。
主要解决的问题
- 代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。
使用场景
- 当需要在访问一个对象时进行一些控制或额外处理时。
实现方式
- 增加中间层:创建一个代理类,作为真实对象的中间层。
- 代理与真实对象组合:代理类持有真实对象的引用,并在访问时进行控制。
关键代码
- 代理类:实现与真实对象相同的接口,并添加额外的控制逻辑。
- 真实对象:实际执行任务的对象。
应用实例
- 快捷方式:Windows系统中的快捷方式作为文件或程序的代理。
- 角色扮演:孙悟空作为高翠兰的代理,猪八戒无法区分。
- 代售点:购买火车票时,代售点作为火车站的代理。
- 支票:作为银行账户资金的代理,控制资金的访问。
- Spring AOP:使用代理模式来实现面向切面编程。
优点
- 职责分离:代理模式将访问控制与业务逻辑分离。
- 扩展性:可以灵活地添加额外的功能或控制。
- 智能化:可以智能地处理访问请求,如延迟加载、缓存等。
缺点
- 性能开销:增加了代理层可能会影响请求的处理速度。
- 实现复杂性:某些类型的代理模式实现起来可能较为复杂。
使用建议
- 根据具体需求选择合适的代理类型,如远程代理、虚拟代理、保护代理等。
- 确保代理类与真实对象接口一致,以便客户端透明地使用代理。
注意事项
- 与适配器模式的区别:适配器模式改变接口,而代理模式不改变接口。
- 与装饰器模式的区别:装饰器模式用于增强功能,代理模式用于控制访问。
结构
主要涉及到以下几个核心角色:
抽象主题(Subject):
- 定义了真实主题和代理主题的共同接口,这样在任何使用真实主题的地方都可以使用代理主题。
真实主题(Real Subject):
- 实现了抽象主题接口,是代理对象所代表的真实对象。客户端直接访问真实主题,但在某些情况下,可以通过代理主题来间接访问。
代理(Proxy):
- 实现了抽象主题接口,并持有对真实主题的引用。代理主题通常在真实主题的基础上提供一些额外的功能,例如延迟加载、权限控制、日志记录等。
客户端(Client):
- 使用抽象主题接口来操作真实主题或代理主题,不需要知道具体是哪一个实现类。
什么是代理模式?
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。
通俗的来讲代理模式就是我们生活中常见的中介。
举个例子来说明:假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了
为什么要用代理模式?
中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理
类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
有哪几种代理模式?
我们有多种不同的方式来实现代理。如果按照代理创建的时期来进行分类的话
可以分为两种:
静态代理:
静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。
动态代理:
动态代理是在程序运行时通过反射机制动态创建的。
动态代理分为:
- 基于接口的动态代理(jdk自带)
- 基于子类的动态代理(第三方)
1. 静态代理
实现
创建一个接口。
public interface IWomen { public void makeEyesWithMan();//抛媚眼 public void happyWithMan();//开心 }
创建实现接口的实体类
public class PanJinLianImp implements IWomen{ public void makeEyesWithMan() { System.out.println("=======回眸一笑,抛个媚眼======"); } public void happyWithMan() { System.out.println("=========嘿嘿嘿,老娘来了~~~=========="); } }
代理
//代理 中间商 public class WangPoImp implements IWomen{ //被代理 IWomen iWomen; public WangPoImp() { } public WangPoImp(IWomen iWomen) { this.iWomen = iWomen; } public void makeEyesWithMan() { System.out.println("=======斟一壶酒,搞搞气氛======"); iWomen.makeEyesWithMan(); } public void happyWithMan() { iWomen.happyWithMan(); } }
请求
public class XiMenQing { public static void main(String[] args) { //1.目标(被代理对象) PanJinLianImp panJinLianImp = new PanJinLianImp(); //2.代理 WangPoImp wangPoImp = new WangPoImp(panJinLianImp); wangPoImp.makeEyesWithMan(); wangPoImp.happyWithMan(); } }
2. 基于接口的动态代理(jdk自带)
基于接口的动态代理:
- 特点:字节码随用随创建,随用随加载
- 作用:不修改源码的基础上对方法增强
涉及的类:Proxy
提供者:JDK官方
如何创建代理对象:
使用Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理类最少实现一个接口,如果没有则不能使用
newProxyInstance方法的参数:
- ClassLoader:类加载器
- 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
- Class[]:字节码数组
- 它是用于让代理对象和被代理对象有相同方法。固定写法。
- InvocationHandler:用于提供增强的代码
实现
接口
public interface ISinger { public void sing(); public void rap(); }
实现类
public class CaiXuKunImp implements ISinger{ public void sing() { System.out.println("========鸡你太美========"); } public void rap() { System.out.println("=======rap========="); } }
测试
public class Test01 { public static void main(String[] args) { //1.被代理对象 final CaiXuKunImp caiXuKunImp = new CaiXuKunImp(); /** * 基于接口的动态代理: * 特点:字节码随用随创建,随用随加载 * 作用:不修改源码的基础上对方法增强 * 涉及的类:Proxy * 提供者:JDK官方 * 如何创建代理对象: * 使用Proxy类中的newProxyInstance方法 * 创建代理对象的要求: * 被代理类最少实现一个接口,如果没有则不能使用 * newProxyInstance方法的参数: * ClassLoader:类加载器 * 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。 * Class[]:字节码数组 * 它是用于让代理对象和被代理对象有相同方法。固定写法。 * InvocationHandler:用于提供增强的代码 * */ ISinger jinjinren = (ISinger) Proxy.newProxyInstance(caiXuKunImp.getClass().getClassLoader(), caiXuKunImp.getClass().getInterfaces(), new InvocationHandler() { /*** * Object proxy=====》被代理对象的引用===(蔡徐坤对象) * Method method====》执行的方法========(蔡徐坤唱歌方法) * Object[] args====》执行方法的参数=====(蔡徐坤唱歌方法的参数) * Object===========》执行方法的返回值===(蔡徐坤唱歌方法的返回值) * */ /** * 作用:执行被代理对象的任何接口方法都会经过该方法 * 方法参数的含义 * proxy 代理对象的引用 * method 当前执行的方法 * args 当前执行方法所需的参数 * Object 和被代理对象方法有相同的返回值 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("跳一段舞"); Object obj = method.invoke(caiXuKunImp, args);//代表调用被代理对象的核心方法 System.out.println("打一个篮球"); return obj; } }); //3.唱歌 jinjinren.sing(); System.out.println("***********************"); jinjinren.rap(); } }
3. 基于子类的动态代理
基于子类的动态代理
- 涉及的类:Enhancer
- 提供者:第三方cglib库
- 开发环境:添加cglib依赖坐标
如何创建代理对象:
使用Enhancer类中的create方法
创建代理对象的要求:
被代理类不能是最终类
create方法的参数:
- Class:字节码
- 它是用于指定被代理对象的字节码。
- Callback:用于提供增强的代码
- 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
此接口的实现类都是谁用谁写。
我们一般写的都是该接口的子接口实现类:MethodInterceptor
实现
注入
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency> </dependencies>
接口
public interface ISinger { public void sing(); }
public class ZhouShenImp implements ISinger{ @Override public void sing() { System.out.println("========大鱼海棠========"); } }
public class Test01 { public static void main(String[] args) { 1.被代理对象 final ISinger zhouShenImp = new ZhouShenImp(); ISinger jingjiren = (ISinger) Enhancer.create(zhouShenImp.getClass(), new InvocationHandler() { /** * 2.创建代理对象 * 参数1:被代理对象的字节码 * 参数2:InvocationHandler * */ /** * 执行被代理对象的任何方法都会经过该方法 * @param proxy * @param method * @param args * 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的 * @param methodProxy :当前执行方法的代理对象 * @return * @throws Throwable */ @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("====找一下蔡徐坤====="); Object obj = method.invoke(zhouShenImp,objects); System.out.println("======和蔡徐坤一起基太美====="); return obj; } }); //3.唱歌 jingjiren.sing(); } }
二、Spring1案例补充
1. 常规实现
在Spring1中的基础上 实现 转钱功能
xml实现
项目状态:成功提交,失败不能做到一个业务方法一起回滚
分析原因:项目存在事务自动管理,且自动管理在dao层实现
解决方案:事务管理未来一定是在service层实现
方案思考:
1.dao层不在进行事务管理,自动事务提交关闭
2.业务类的每个业务方法中的多个dao操作,公用同一个连接connection对象
3.ThreadLocaldao层不变
service
接口新增
public void transfer(String sourceName,String targetName,int money);
实现类 重写该方法
@Override public void transfer(String sourceName, String targetName, int money) { try { //开启事务 transactionUtil.beginTx(); Account sourceAccount = dao.findByName(sourceName); Account targetAccount = dao.findByName(targetName); sourceAccount.setAmoney(sourceAccount.getAmoney()-money); targetAccount.setAmoney(targetAccount.getAmoney()+money); dao.updateById(sourceAccount); int a =10/0;//模拟异常 dao.updateById(targetAccount); //提交事务 transactionUtil.commitTx(); } catch (Exception e) { //回滚事务 transactionUtil.rollbackTx(); e.printStackTrace(); } finally { //关闭事务 transactionUtil.closeTx(); } }
controller同理
public void transfer(String sourceName,String targetName,int money); @Override public void transfer(String sourceName, String targetName, int money) { service.transfer(sourceName,targetName,money); }
util
ConnectionUtilpublic class ConnectionUtil { //线程区域对象 ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>(); //数据源 DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } //获取连接 public Connection createConn() { try { //在口袋里面找连接 Connection connection = connectionThreadLocal.get(); //判断 if (connection==null){ connection=dataSource.getConnection(); connectionThreadLocal.set(connection); } return connection; } catch (SQLException throwables) { throwables.printStackTrace(); } return null; } //关闭连接 public void closeConn(){ connectionThreadLocal.remove();//解绑 } }
TransactionUtil
public class TransactionUtil { //专配连接工具类 ConnectionUtil connectionUtil; public void setConnectionUtil(ConnectionUtil connectionUtil) { this.connectionUtil = connectionUtil; } //开启 public void beginTx() { try { connectionUtil.createConn().setAutoCommit(false); } catch (SQLException throwables) { throwables.printStackTrace(); } } //提交 public void commitTx() { try { connectionUtil.createConn().commit(); } catch (SQLException throwables) { throwables.printStackTrace(); } } //回滚 public void rollbackTx() { try { connectionUtil.createConn().rollback(); } catch (SQLException throwables) { throwables.printStackTrace(); } } //关闭 public void closeTx() { try { connectionUtil.createConn().close(); connectionUtil.closeConn(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
同时需要在service 实现类装配事务工具类
//装配事务工具类 TransactionUtil transactionUtil; public void setTransactionUtil(TransactionUtil transactionUtil) { this.transactionUtil = transactionUtil; }
dao层装配连接工具类
//装配连接工具类 ConnectionUtil connectionUtil; public void setConnectionUtil(ConnectionUtil connectionUtil) { this.connectionUtil = connectionUtil; }
在xml文件中注入
<!-- 注入连接工具类 --> <bean id="connectionUtil" class="com.zkt.util.ConnectionUtil"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 注入事务工具类 --> <bean id="transactionUtil" class="com.zkt.util.TransactionUtil"> <property name="connectionUtil" ref="connectionUtil"/> </bean> <!-- 注入dao --> <bean id="dao" class="com.zkt.dao.AccountDaoImp"> <property name="queryRunner" ref="queryRunner"/> <property name="connectionUtil" ref="connectionUtil"/> </bean> <!-- 注入service --> <bean id="service" class="com.zkt.service.AccountServiceImp"> <property name="dao" ref="dao"/> <property name="transactionUtil" ref="transactionUtil"/> </bean>
2. 代理实现
在util下创建代理
public class ProxyFactory { //被代理对象装配 IAccountService toServiceProxy; public void setToServiceProxy(IAccountService toServiceProxy) { this.toServiceProxy = toServiceProxy; } //装配事务 TransactionUtil transactionUtil; public void setTransactionUtil(TransactionUtil transactionUtil) { this.transactionUtil = transactionUtil; } //创建代理 public IAccountService createProxy(){ IAccountService service = (IAccountService) Proxy.newProxyInstance(toServiceProxy.getClass().getClassLoader(), toServiceProxy.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object obj = null; try { transactionUtil.beginTx(); obj = method.invoke(toServiceProxy, args); transactionUtil.commitTx(); } catch (Exception e) { e.printStackTrace(); transactionUtil.rollbackTx(); } finally { transactionUtil.closeTx(); } return obj; } }); return service; } }
事务工具类不变
连接工具类
public class ConnectionUtil { //线程区域对象 ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<Connection>(); //数据源 DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } //获取连接 public Connection createConn() { try { //在口袋里面找连接 Connection connection = connectionThreadLocal.get(); //判断 if (connection==null){ connection=dataSource.getConnection(); connectionThreadLocal.set(connection); } return connection; } catch (SQLException throwables) { throwables.printStackTrace(); } return null; } //关闭连接 public void closeConn(){ connectionThreadLocal.remove();//解绑 } }
故service的transfer 正常写就行
@Override public void transfer(String sourceName, String targetName, int money) { Account sourceAccount = dao.findByName(sourceName); Account targetAccount = dao.findByName(targetName); sourceAccount.setAmoney(sourceAccount.getAmoney() - money); targetAccount.setAmoney(targetAccount.getAmoney() + money); dao.updateById(sourceAccount); // int a = 10 / 0;//模拟异常 dao.updateById(targetAccount); }
最后 在xml文件中 注入就行
<!-- 注入service(被代理对象) --> <bean id="service" class="com.zkt.service.AccountServiceImp"> <property name="dao" ref="dao"/> </bean> <!-- 注入service(代理对象) --> <bean id="proxyService" class="com.zkt.service.AccountServiceImp" factory-bean="factory" factory-method="createProxy"/> <bean id="factory" class="com.zkt.util.ProxyFactory"> <property name="transactionUtil" ref="transactionUtil"/> <property name="toServiceProxy" ref="service"/> </bean> <!-- 注入controller(消费者) --> <bean id="controller" class="com.zkt.controller.AccountControllerImp"> <property name="service" ref="proxyService"/> </bean>
其余不变 即可实现
三、AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP 是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,
是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得
业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP 的作用及其优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
AOP 的应用
- 1.日志
- 2.事务
- 3.权限
Spring AOP 基于动态代理实现:
- 如果被代理的对象,已经实现某个接口,则 Spring AOP 会使用 JDK Proxy(反射),基于接口的方式,创建代理对象(JDK动态代理的核心是InvocationHandler接口和Proxy类);
- 如果被代理的对象,没有实现某个接口,就无法使用 JDK Proxy 去进行代理了,这时候Spring AOP 会使用 Cglib,基于继承的方式,生成一个被代理对象的子类来作为代理(Cglib动态代理的核心是MethodInterceptor接口和Enhancer类);
AOP术语:
AOP通知类型
AOP将抽取出来的共性功能称为通知;通知类型:以通知在上下文中的具体位置作为划分
- 前置通知(Before)
- 后置通知(After)
- 返回通知(After-returning)
- 异常通知(After-throwing)
- 环绕通知(Around)
AOP连接点(Join point)
AOP将所有的方法都视为连接点,不管是接口里面的抽象方法,还是实现类里面的重写方法,都是连接点AOP切点(Pointcut)
AOP将可能被抽取共性功能的方法称为切入点。切入点是连接点的子集AOP目标对象(Target):
就是挖掉功能的方法对应的类生的对象,这种对象是无法直接完成最终工作的
AOP织入(Weaving):
就是将挖掉的功能回填的动态过程
AOP切面:
切点+通知
实现步骤:
- 1.添加依赖,aop与aspectj表达式的依赖
- 2.创建spring的主配置文件,bean内的命名空间要添加aop的
- 3.创建业务代码并编写日志记录代码(事务管理代码)
- 4.将业务层与日志记录层注入spring容器
- 5.<aop:config>--aop配置
- aop:aspect--aop切面
- aop:before--通知内容与通知类型
切点表达式配置语法:
execution(修饰符 返回值 包名称.类名称.方法名称(参数列表))
eg:
execution(public void com.apesource.service.ServiceImp.findAll())
1.修饰符可以省略代表任意
execution(返回值 包名称.类名称.方法名称(参数列表))
2.返回值可以使用“*”代表任意
execution(* 包名称.类名称.方法名称(参数列表))
3.包名可以使用“*”代表任意名称
execution(* *.*.*.类名称.方法名称(参数列表))
4.包名可以使用“..”代表任意个数
execution(* *...类名称.方法名称(参数列表))
5.类名与方法名可以使用“*”代表任意
execution(* *...*.*(参数列表))
6.参数列表可以使用".."代表任意个数任意类型
execution(* *...*.*(..))
如果有参数
int======>int
String===>java.lang.String
xml实现日志记录
poi.xml 导入坐标
AOP 不需要到导 因为context包含了
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
service
public interface IAccountService { public void save(int i); public void update(); public int delete(); } public class AccountServiceImp implements IAccountService { public void save(int i) { System.out.println("业务层的新增方法:" + i); } public void update() { System.out.println("业务层的修改方法"); // int a = 10/0; } public int delete() { System.out.println("业务层的删除方法"); return 0; } }
util
public class Logger { public void beforeMethod() { System.out.println("日志类logger===前置通知"); } public void returnMethod() { System.out.println("日志类logger===返回通知"); } public void throwMethod() { System.out.println("日志类logger===异常通知"); } public void afterMethod() { System.out.println("日志类logger===后置通知"); } }
applicationContext.xml
<!-- 注入业务层 --> <bean id="accountServiceImp" class="com.zkt.service.AccountServiceImp"/> <!-- 注入日志记录层(通知) --> <bean id="logger" class="com.zkt.util.Logger"/> <!-- 配置AOP --> <aop:config> <!-- 配置切面 --> <aop:aspect id="aopAspect" ref="logger"> <!-- 切点 --> <aop:pointcut id="dian" expression="execution(* com.zkt.service.AccountServiceImp.* (..))"/> <!-- 通知 --> <aop:before method="beforeMethod" pointcut-ref="dian"/> <aop:after-returning method="returnMethod" pointcut-ref="dian"/> <aop:after-throwing method="throwMethod" pointcut-ref="dian"/> <aop:after method="afterMethod" pointcut-ref="dian"/> </aop:aspect> </aop:config>
测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:applicationContext.xml") public class Test01 { @Autowired public IAccountService service; @Test public void show1(){ service.update(); // System.out.println("==================="); // service.save(1); // System.out.println("==================="); // service.delete(); } }
注解实现日志记录
导入坐标同理
加入注解
util使用环绕通知实现
@Component @Aspect//切面 public class Logger { @Pointcut("execution(* com.zkt.service.AccountServiceImp.* (..))") public void dian() { } // @Before("dian()") // public void beforeMethod() { // System.out.println("日志类logger===前置通知"); // } // // @AfterReturning("dian()") // public void returnMethod() { // System.out.println("日志类logger===返回通知"); // } // // @AfterThrowing("dian()") // public void throwMethod() { // System.out.println("日志类logger===异常通知"); // } // // @After("dian()") // public void afterMethod() { // System.out.println("日志类logger===后置通知"); // } //环绕通知 @Around("dian()") public Object AroundLogger(ProceedingJoinPoint pjp) { Object returnobj = null;//保存主业务方法的返回值 try { //1.前置通知 System.out.println("环绕通知===》前置通知"); Object[] objs = pjp.getArgs();//主业务方法的参数 returnobj = pjp.proceed(objs);//调用主业务方法 //3.后置通知 System.out.println("环绕通知===》返回通知"); } catch (Throwable tw) { //4.异常通知 System.out.println("环绕通知===》异常通知"); } finally { //5.最终通知 System.out.println("环绕通知===》后置通知"); } return returnobj; } }
测试 异常情况
正常情况
四、面试题
原文地址:https://blog.csdn.net/qq_52897007/article/details/140489796
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!