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 和被代理对象方法有相同的返回值*/@Overridepublic 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{@Overridepublic 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*/@Overridepublic 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);
实现类 重写该方法
@Overridepublic 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);@Overridepublic 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() {@Overridepublic 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 正常写就行
@Overridepublic 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 {@Autowiredpublic IAccountService service;@Testpublic 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;} }
测试 异常情况
正常情况
四、面试题
16. Spring框架中的Bean生命周期
- spring bean的生命周期总体分四个阶段:实例化=>属性注入 =>初始化=>销毁
- step1 实例化bean:根据配置文件中 bean 的定义,利用 java reflection 反射技术创建 bean的实例
- step2 注入对象依赖的属性值(或对象)
- step3 处理各种Aware接口:spring 会检测该 bean 是否实现了 xxxaware 接口,通过Aware类型的接口,可以让spring框架为当前 bean 注入相应的内容
- 如果 bean 实现 beannameaware 接口,会调用它实现的 setbeanname(string beanid)方 法,注入 bean 的名字
- 如果 bean 实现 beanclassloaderaware 接口,调用 setbeanclassloader()方法,注入classloader 对象的实例
- 如果 bean 实现 beanfactoryaware 接口,会调用它实现的 setbeanfactory()方法,注入的是 spring工厂。如果 bean 实现 applicationcontextaware 接口,会调用 setapplicationcontext()方法,注入 spring 上下文
- step4 执行BeanPostProcessor前置处理:如果想对 bean 进行一些自定义的前置处理,那么可以让 bean 实现了 beanpostprocessor 接口,将会在该阶段调用 postprocessbeforeinitialization(object obj,string s)
- Step5 执行initializingbean初始化方法:如果 bean 实现了 InitializingBean接口,执行afeterPropertiesSet() 方法
- step6 执行init-method自定义初始化方法:如果 bean 在 spring 配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法
- step7 执行beanpostprocessor后置处理:如果这个 bean 实现了beanpostprocessor接口, 用 postprocessAfterInitialization(object ob,string s)方法,由于这个方法是在 bean初始化结束后调用
以上几个步骤完成后, bean 已经被正确创建,可以正常使用这个bean了
- step8 执行disposablebean销毁bean:当 bean 不再需要时,会经过清理阶段,如果 bean 实现了 disposablebean 这个接口,会调用其实现的 destroy()方法执行销毁
- step9 执行destroy-method自定义初始化方法:如果 bean 在 spring 配置文件中配置了 destroy-method 属性,则会自动调用其配置的初始化方法
17. spring 框架如何解决循环依赖?
循环依赖问题是指∶ 类与类之间的依赖关系形成了闭环,就会导致循环依赖问题的产生。
Spring使用三级缓存来解决循环依赖
18. Spring 框架中有哪些注解?
用于声明 bean 的注解:
- @Component:定义通用bean的注解,可标注任意类为 bean。如果一个 bean 不知道属于 哪个层,可以使用 @component 注解标注
- @Repository:定义数据访问层bean的注解
- @Service:定义业务层bean的注解
- @Controller:定义控制器bean的注解
用于注入的注解:
- @Autowired:按类型自动注入
- @Qualifier:按名称自动注入
声明配置、扫描、启用特性的注解:
- @Configuration:声明配置类
- @ComponentScan:组件扫描
- @EnablesScheduling:启用任务调度
- @EnableAspectJAutoProxy:启用自动代理工厂
19. Spring 框架中用到的设计模式
相关文章:
Spring3(代理模式 Spring1案例补充 Aop 面试题)
一、代理模式 在代理模式(Proxy Pattern)中,一个类代表另一个类的功能,这种类型的设计模式属于结构型模式。 代理模式通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端…...
Github报错:Kex_exchange_identification: Connection closed by remote host
文章目录 1. 背景介绍2. 排查和解决方案 1. 背景介绍 Github提交或者拉取代码时,报错如下: Kex_exchange_identification: Connection closed by remote host fatal: Could not read from remote repository.Please make sure you have the correct ac…...
LabVIEW在CRIO中串口通讯数据异常问题
排查与解决步骤 检查硬件连接: 确保CRIO的串口模块正确连接,并且电缆无损坏。 确认串口模块在CRIO中被正确识别和配置。 验证串口配置: 在LabVIEW项目中,检查CRIO目标下的串口配置,确保波特率、数据位、停止位和校验…...
ALTERA芯片解密FPGA、CPLD、PLD芯片解密解密
Altera是世界一流的FPGA、CPLD和ASIC半导体生产商,所提供的解决方案与传统DSP、ASSP和ASIC解决方案相比,缩短了产品面市时间,提高了性能和效能,降低了系统成本。针对Altera芯片解密,益臻芯片解密中心经过多年的芯片解…...
[RK3588-Android12] 关于如何取消usb-typec的pd充电功能
问题描述 RK3588取消usb-typec的pd充电功能 解决方案: 在dts中fusb302节点下usb_con: connector子节点下添加如下熟悉: 打上如下2个补丁 diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c index c8a4e57c9f9b..173f8cb7…...
分布式 I/O 系统 BL200 Modbus TCP 耦合器
BL200 耦合器是一个数据采集和控制系统,基于强大的 32 位微处理器设计,采用 Linux 操作系统,支持 Modbus 协议,可以快速接入现场 PLC、SCADA 以及 ERP 系统, 内置逻辑控制、边缘计算应用,适用于 IIoT 和工业…...
Java面试题--JVM大厂篇之Serial GC在JVM中有哪些优点和局限性
目录 引言: 正文: 一、Serial GC概述 二、Serial GC的优点 三、Serial GC的局限性 结束语: 引言: 在Java虚拟机(JVM)中,垃圾收集器(Garbage Collector, GC)是关键组件之一,负责自动管理内…...
【人工智能】机器学习 -- 贝叶斯分类器
目录 一、使用Python开发工具,运行对iris数据进行分类的例子程序NaiveBayes.py,熟悉sklearn机器实习开源库。 1. NaiveBayes.py 2. 运行结果 二、登录https://archive-beta.ics.uci.edu/ 三、使用sklearn机器学习开源库,使用贝叶斯分类器…...
深入理解 React 的 useSyncExternalStore Hook
深入理解 React 的 useSyncExternalStore Hook 大家好,今天我们来聊聊 React 18 引入的一个新 Hook:useSyncExternalStore。这个 Hook 主要用于与外部存储同步状态,特别是在需要确保状态一致性的场景下非常有用。本文将深入探讨这个 Hook 的…...
河南萌新联赛2024第(一)场:河南农业大学
C-有大家喜欢的零食吗_河南萌新联赛2024第(一)场:河南农业大学 (nowcoder.com) 思路:匈牙利算法的板子题. 二部图 int n; vector<int> vct[505]; int match[505],vis[505]; bool dfs(int s){for(auto v:vct[s]){if(vis[v]) continue;…...
K8S 上部署 Emqx
文章目录 安装方式一:1. 快速部署一个简单的 EMQX 集群:2. 部署一个持久化的 EMQX 集群:3. 部署 EMQX Edge 集群和 EMQX 企业版集群: 安装方式二:定制化部署1. 使用 Pod 直接部署 EMQX Broker2. 使用 Deoloyment 部署 …...
[React]利用Webcomponent封装React组件
[React]利用Webcomponent封装React组件 为什么这么做 我个人认为,最重要的点是可以很方便地跨框架挂载和卸载wc元素(至少我在项目里是这么玩的),此外,基于wc的css沙箱以及它的shadowRoot机制,可以提供一套…...
Linux C服务需要在A服务和B服务都启动成功后才能启动
需求 C服务需要在A服务和B服务都启动成功后才能启动 服务编号服务名服务Anginx.service服务Bmashang.service服务Credis.service 实验 如果您想要 redis.service 在 nginx.service 和 mashang.service 都成功启动后才能启动,那么需要在 redis.service 的服务单元…...
VSCODE 下 openocd Jlink 的配置笔记
title: VSCODE 下 openocd Jlink 的配置笔记 tags: STM32HalCubemax 文章目录 内容VSCODE 下 openocd Jlink 的配置笔记安装完成后修改jlink的配置文件然后修改你的下载器为jlink烧录你的项目绝对会出现下面的问题那么打开下载的第一个软件 (点到这个jlink右键&…...
JVM--HostSpot算法细节实现
1.根节点枚举 定义: 我们以可达性分析算法中从GC Roots 集合找引用链这个操作作为介绍虚拟机高效实现的第一个例 子。固定可作为GC Roots 的节点主要在全局性的引用(例如常量或类静态属性)与执行上下文(例如 栈帧中的本地变量表&a…...
【Unity实战100例】Unity声音可视化多种显示效果
目录 一、技术背景 二、界面搭建 三、 实现 UIAudioVisualizer 基类 四、实现 AudioSampler 类 五、实现 IAudioSample 接口 六、实现MusicAudioVisualizer 七、实现 MicrophoneAudioManager 类 八、实现 MicrophoneAudioVisualizer 类 九、源码下载 Unity声音可视化四…...
[Cesium for Supermap] 加载3dTiles,点击获取属性
代码: // 设为椭球var obj [6378137.0, 6378137.0, 6356752.3142451793];Cesium.Ellipsoid.WGS84 Object.freeze(new Cesium.Ellipsoid(obj[0], obj[1], obj[2]));var viewer new Cesium.Viewer(cesiumContainer);var scene viewer.scenescene.lightSource.ambi…...
【stm32项目】基于stm32智能宠物喂养(完整工程资料源码)
基于STM32宠物喂养系统 前言: 随着人们生活幸福指数的提高,越来越多的家庭选择养宠物来为生活增添乐趣。然而,由于工作等原因,许多主人无法及时为宠物提供充足的食物与水。为了解决这一问题,我设计了一款便捷的宠物喂…...
选择Maya进行3D动画制作与渲染的理由
如果你对3D动画充满热情并追求成为专业3D动画师的梦想,你一定听说过Maya——近年来3D动画的行业标准。Maya被3D艺术家广泛使用,你是否想知道为什么Maya总是他们的首选?下面一起来了解下。 一、什么是Maya? 由Autodesk开发的Maya是…...
Promise应用
创建一个 Promise 对象 let promise showLabelText() {return new Promise((resolve, reject) > {axios({method: "post",url: "/code/expose/interface/queryActionPlan",data: { situationOneId: 19999, labels: [1, 2, 3] }}).then(response > {…...
51单片机嵌入式开发:13、STC89C52RC 之 RS232与电脑通讯
STC89C52RC 之 RS232与电脑通讯 第十三节课,RS232与电脑通讯1 概述2 Uart介绍2.1 概述2.2 STC89C52UART介绍2.3 STC89C52 UART寄存器介绍2.4 STC89C52 UART操作 3 C51 UART总结 第十三节课,RS232与电脑通讯 1 概述 RS232(Recommended Stand…...
当代政治制度(练习题)
当代政治制度(练习题) Rz整理 仅供参考 干部鉴定必须坚持德才兼备原则,考核内容包括(A.德 B.廉 C.能 D.勤 F.绩 ) A.德 B.廉 C.能 D.勤 E.信 F.绩我国干部任用的方式主要包括(A.考任 C.委任 D.聘任 F.选任…...
前端pc和小程序接入快递100(跳转方式和api方式)====实时查询接口
文章目录 跳转方式微信小程序(我以uniapp为例)pc api接入说明关于签名计算成功示例 跳转方式 没有任何开发成本,直接一键接入 可以直接看官方文档 https://www.kuaidi100.com/openapi/api_wxmp.shtml 微信小程序(我以uniapp为例…...
电脑永久性不小心删除了东西还可以恢复吗 电脑提示永久性删除文件怎么找回 怎么恢复电脑永久删除的数据
永久删除电脑数据的操作,对于很多常用电脑设备的用户来说,可以说时有发生!但是,因为这些情况大都发生在不经意间,所以每每让广大用户感觉到十分苦恼。永久删除也有后悔药,轻松找回电脑中误删的文件。恢复文…...
LeetCode热题100刷题16:74. 搜索二维矩阵、33. 搜索旋转排序数组、153. 寻找旋转排序数组中的最小值、98. 验证二叉搜索树
74. 搜索二维矩阵 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int row matrix.size();int col matrix[0].size();for(int i0;i<row;i) {//先排除一下不存在的情况if(i>0&&matrix[i][0]>target…...
C++仿函数
在C中,我们经常需要对类中的元素进行比较,例如在排序、查找等操作中。为了使类更加灵活,我们可以通过自定义比较函数来实现不同的比较方式。在本文中,我们将探讨如何在类中使用仿函数和 Lambda 表达式来定义自定义比较函数。 1. …...
文献阅读:tidyomics 生态系统:增强组学数据分析
文献介绍 文献题目: The tidyomics ecosystem: enhancing omic data analyses 研究团队: Stefano Mangiola(澳大利亚沃尔特和伊丽莎霍尔医学研究所)、Michael I. Love(美国北卡罗来纳大学教堂山分校)、Ant…...
MySQL运维实战之Clone插件(10.1)使用Clone插件
作者:俊达 clone插件介绍 mysql 8.0.17版本引入了clone插件。使用clone插件可以对本地l或远程的mysql实例进行clone操作。clone插件会拷贝innodb存储引擎表,clone得到的是原数据库的一个一致性的快照,可以使用该快照数据来启动新的实例。cl…...
【系统架构设计】数据库系统(三)
数据库系统(三) 数据库模式与范式数据库设计备份与恢复分布式数据库系统分布式数据库的概念特点分类目标 分布式数据库的架构分布式数据库系统与并行数据库系统 数据仓库数据挖掘NoSQL大数据 数据库模式与范式 数据库设计 备份与恢复 分布式数据库系统…...
免费视频批量横版转竖版
简介 视频处理器 v1.3 是一款由是貔貅呀开发的视频编辑和处理工具,提供高效便捷的视频批量横转竖,主要功能: 导入与删除文件:轻松导入多个视频文件,删除不必要的文件。暂停与继续处理:随时暂停和继续处理。…...
纯css的wordpress主题/南昌seo营销
一、问题描述 首先,我在用Gmapping建图的时候发现扫描出来的地图更新速度很慢然后还有就是地图的完整性不高容易变形扭曲,我们在一般在路径规划前需要用适当的建图算法把地图创建出来然后保存方可进行全局路径规划,但是如果地图创建出来扭曲完…...
印度购物网站排名/线上营销推广的公司
本文介绍瑞萨RH850/F1L用户手册(user manual)的CAN接口部分的中文翻译。 博主会持续更新该用户手册,直到整个翻译完成,有兴趣的朋友可持续关注. 版权声明 本博文系欧科曼汽车电子所有,转载请注明出处。 欧科曼汽车电子致力于瑞萨MCU及周边相…...
南阳网站建设多少钱/百度搜索广告怎么投放
前言 上一篇文章:➣SpringCloud Alibaba之Seata入门以及踩坑(一)老顾介绍了seata相关的准备工作,以及版本的选择;今天老顾就来介绍一下seata的使用。以及在使用过程中遇到的问题。 案例背景 今天老顾介绍的案例场景也…...
成都手机号码销售网站建设/北京seo公司工作
原文地址:Go-翻过的一些面试题目 1、以下代码会输出什么?请简要说明。 var c make(chan int) var a intfunc f() {a 1<-c }func main() {go f()c <- 0print(a) } 能正确输出1,不过主协程会阻塞 f() 函数的执行。 2、以下代码会输…...
discuz注册/免费网站排名优化在线
这几天无聊,就去研究排序算法,话不多说,用事实说话。using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Diagnostics;namespace 排序算法大PK {class Program{s…...
上海外贸网站制作/搜索推广营销
一、Netty分层设计 Netty 采用了比较典型的三层网络架构进行设计,逻辑架构图如下所示: #第一层,Reactor 通信调度层,它由一系列辅助类完成,包括 Reactor 线程 NioEventLoop 以及其父类、NioSocketChannel/NioServerSo…...