Spring是一个轻量级、低侵入的框架
一、Spring之IOC(控制反转)
IOC:Inversion of Control控制反转:将对象的创建、销毁、初始化等一系列生命周期的过程交给Spring容器来管理;
Spring容器:ApplicationContext;
Spring的主配置文件为:ApplicationContext.xml;
1、启动一个基本的Spring容器就是依靠上面的两个东西,代码如下:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
基本的配置文件applicationContext.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- bean:将一个类的创建过程交给Spring容器管理 id:唯一标识符 class:指定类的路径 --> <bean id="dog" class="com.jiguiquan.www.ioc.Dog"></bean> </beans>
2、Spring中单例和多例
scope常用值其实有4个: singleton:单例(默认为单例) prototype:多例 request:寿命为一次请求 session:寿命为一次会话 |
3、Spring中的懒加载与非懒加载
懒加载和非懒加载的方式,各有各的优缺点: 懒加载:对象使用的时候再去创建,节约资源;但是不利于提前发现项目错误; 非懒加载:容器启动时立马创建对象,消耗资源;但是有利于提前发现项目错误; |
4、Spring中对象的初始化和销毁
对象的初始化方法:比较容易理解,它发生在对象创建完成之后,即先执行构造方法,在执行初始化方法; 对象的销毁方法:它发生在spring容器关闭的时候,通过将context强转为ClassPathXmlApplicationContext对象后就可以调用.close()方法关闭spring容器了; |
补充:
a、已经通过Spring创建的对象如何获取? 使用:
Dog dog1 = (Dog)context.getBean("dog");b、scope为singleton单例模式创建的bean:默认为非懒加载;
scope为prototype多例模式创建的bean:默认为懒加载;(想想也是,多例的话,将会会出现很多对象,容器启动的时候无法知道)
二、Spring之DI(依赖注入)
DI:Dependency Injection依赖注入:给对象的属性赋值,共有两种方法;
-
通过构造方法赋值;
-
通过get/set方法赋值;
1、通过构造方法给对象的基本属性赋值:
![]() |
2、通过get/set方法给对象的基本属性赋值:
![]() |
3、通过DI中的get/set方法给对象的List/Set/Map/properties等复杂属性赋值:
新建一个复杂对象Person:
public class Person { private String name; private Panda pet; private List list; private Set set; private Map map;// key value private Properties props;// .properties ...... }
给Person对象中的list、set、map、properties类型的复杂属性赋值:
<bean id="person" class="cn.java.di1.Person"> <property name="name" value="王二麻子"></property> ——给普通属性赋值,name/value <property name="pet" ref="smallPanda"></property> ——给自定义对象属性赋值,name/ref <property name="list"> ——给list类型属性赋值,name/list/value,其中遇到自定义对象还是用ref <list> <value>list1</value> <value>中国</value> <ref bean="smallPanda"/> </list> </property> <property name="set"> ——给set类型属性赋值,name/set/value,其中遇到自定义对象还是用ref <set> <value>set1</value> <value>china</value> <ref bean="smallPanda"/> </set> </property> <property name="map"> ——给map类型属性赋值,name/map/entry/key/value <map> <entry key="name" value="李四"></entry> <entry key="age" value="10"></entry> </map> </property> <property name="props"> ——给配置文件类型operties属性赋值,name/props/prop/key <props> <prop key="driver">com.mysql.jdbc.Driver</prop> ——一个prop就是一条配置记录 <prop key="url">jdbc:oracle:@thin:localhost:1521:orcl</prop> </props> </property> </bean>
三、Spring之AOP
AOP:Aspect Oriented Programming 面向切面编程:OOP的延续。将系统中非核心的业务提取出来,进行单独处理。比如事务,日志等;
AOP可以实现:对功能的扩展不需要通过修改源码的方式实现;
大白话来谈谈AOP和OOM的对比:
-
OOP面向对象解决了层次的问题,比如下级继承上级来增强上级等等;
-
AOP面向切面解决了前后左右级别的问题,比如分别给一个对象的不同方法,加入不同的额外功能,特点是通过代理方式对目标类在运行期间织入功能;
以下是在网上另一处找到的文字,似乎更容易理解一点:
AOP是什么? AOP,面向切面编程,是对OOP的补充。从网上看到的一句话:这种在运行时,动态的将代码切入到类的指定方法或者指定位置上的编程思想,就是面向切面的编程。这是其中的一种方式,在运行时动态添加。还有另外一种是在编译代码的时候,将代码切入到指定的方法或者位置上去,这是静态添加的方式。 使用 我们在实际的业务中都会有一些公共逻辑,比如日志的记录,事务的管理等等,而如果每次都把日志和事务的代码手动写到业务逻辑前后,重复代码就相当可怕,而如果这些额外代码有修改,必须要每个都修改,这是相当不明智的。AOP可以帮我们解决这些问题。 实现 其实AOP本身并不能帮我们解决那些问题,AOP就是一种思想,而帮我们解决的是具体的AOP的实现,比如aspectj,jboss AOP,以及我们最这里要讲的Spring AOP; Spring AOP在Spring1.0的时候是自己实现的AOP框架,在2.0之后就开始集成了aspectj。现在我们所说的Spring AOP就是Spring加Aspectj这种方式。 |
1、实际生活中的例子:——手机银行App
通过此实际应用,引入AOP中的几个基本概念: 1、切面:a(Securty)、c(Rizhi)、d(Clean),这几个类,上面的四个模块想象成4张纸,除了主业务模块外,一个切面就是一张纸,执行顺序纸上打洞(从上往下); 2、通知:切面中的方法 isSecurity()、log()、clearResource()就是通知 a) 前置通知:在核心类的前面的叫前置通知; b) 后置通知:在核心类后面的叫后置通知; c) 环绕通知: d) 异常通知: 3、连接点:[BankServiceImpl]核心业务类中的目标方法,连接点用来区分前置和后置的,没有连接点,就无法区分前置通知和后置通知; 4、织入:将目标方法和通知整合起来的过程就叫做织入; 5、代理:完成“织入”工作的主体就叫做代理(proxy)——这里就体现出了AOP中使用的设计模式(经典 代理模式) |
2、AOP面向切面的具体配置过程(Spring的厉害之处就在于能将AOP思想通过配置的形式实现出来):
第一步:导入jar包(3个):
第二步:配置aop提示模板:
第三步:配置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-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- <bean/> definitions here --> </beans>
第四步:创建切面bean(首先要将4个类对象交给Spring去管理):
<!-- 配置安全切面bean --> <bean id="security" class="cn.java.aop.Security"></bean> <!-- 配置核心业务类bean --> <bean id="bankServiceImpl" class="cn.java.aop.BankServiceImpl"></bean> <!-- 配置日志类bean --> <bean id="rizhi" class="cn.java.aop.Rizhi"></bean> <!-- 配置清除缓存类bean --> <bean id="clean" class="cn.java.aop.Clean"></bean>
第五步:配置aop:(切入点+前置通知+后置通知等):
<!-- 配置aop(切面是指类层,通知和连接点,切入点都是指方法层) --> <aop:config> <!-- 配置切入点,".*"代表这个类下面的“所有方法”都当做切入点,“(..)”代表这些切入点可能带参数,也可能不带; --> <aop:pointcut expression="execution (* cn.java.aop.BankServiceImpl.*(..))" id="aaa"/> <!-- 配置切面 --> <aop:aspect ref="security"> <!-- aop:before代表前置通知,method代表通知的方法名,pointcut代表前置于哪个切入点前面 --> <aop:before method="isSecurity" pointcut-ref="aaa"/> </aop:aspect> <aop:aspect ref="rizhi"> <aop:after method="log" pointcut-ref="aaa"/> </aop:aspect> <aop:aspect ref="clean"> <aop:after method="clearResouce" pointcut-ref="aaa"/> </aop:aspect> </aop:config>
备注:1、配置通知时候,想越先执行的通知,离切入点越近;(方法一)
2、配置aop切面时候,使用order属性,数值越大,权重越高,越先被执行;
(如果以后想去掉日志模块的功能,代码一行都不用修改,只需要注释掉rizhi的aop:aspect配置)——搭积木——低侵入
增加一个功能,也同样,写一个新的模块——>增加一个切面bean——>再增加一个aop:aspect切面配置
3、带返回值的切入点和带参数的切面通知:
<aop:aspect ref="receive" order="3"> <!-- ruturning代表aaa这个切入点会有个返回值,这个返回值将被printMoney这个通知方法里面的money这个形参接收 --> <aop:after-returning method="printMoney" pointcut-ref="aaa" returning="money"/> </aop:aspect> BankServiceImpl bsi = (BankServiceImpl)context.getBean("bankServiceImpl"); Windows窗口中输出结果: bsi.getMoney(); //getMoney方法有返回值,会被Receive切面中的后置通知方法printMoney中的形参money接收到; bsi.zhuanMoney(); //zhuanMoney没有返回值,直接不调用Receive切面中的方法,因为此方法需要有输入值(good); 备注:为了解决多个切入点的返回值不一样的问题,在切面通知里面接收参数时候可以统一使用Object作为参数类型; 为了解决业务类的各连接点可能会返回不同的数据类型,所以我们可以将 int m修改为 Object m,这样就比较通用了! |
4、环绕通知(重要应用——权限)
我们给上面的例子,增加一个新的需求(环绕通知),只有环绕通知通过后,才可以进行后面的所有切面的执行
判断用户名和密码是否正确(权限过滤器)——————>环绕通知: | a) 检测手机系统环境是否安全 Securty—->isSecurity() | 建行手机APP b) 1、查询余额 2、 转账 3、投资理财 BankServiceImpl核心类(目标类–>目标方法) | c) 记录用户操作日志 Rizhi—->log() | d) 清空缓存记录 Clean—->clearResource() 验证类的代码:Auth.java public class Auth { private String username = "admin"; private String password = "123"; public void isUserExist(ProceedingJoinPoint pjp) throws Throwable { if ("admin".equals(username)&&"123".equals(password)) { //通过则放行 System.out.println("登录成功!"); pjp.proceed(); //有这行代码,代表放行 }else { //用户名或密码错误 System.out.println("亲,用户名或密码错误,请重试"); } } } 配置实现aop.xml: <!-- 配置环绕切面通知 --> <aop:aspect ref="auth"> <aop:around method="isUserExist" pointcut-ref="aaa"/> </aop:aspect> 问:为什么使用“环绕通知”,而不使用前置通知实现? 答:之所以用环绕通知,而不用前置通知是因为,前置通知只前置,却不阻拦,前置完,后面的代码依旧执行,环绕通知通过“pjp.proceed()”决定后面的代码是否放行; |
5、异常通知(aop:throwing):
-
专门用来处理异常情况,比如记录异常日志、跳转到指定页面等;
-
只有方法发生异常的时候,才会去请求这个切面异常通知,正常时候是不会请求这个通知的;
验证的代码Global.java:
public class Global { public void handExpt(Throwable ex) { System.out.println("哎呀,我出错了"); // System.out.println(ex.getMessage()); /// by zero // System.out.println(ex.getClass()); //class java.lang.ArithmeticException // System.out.println(ex.getCause()); //null System.out.println(ex.toString()); //java.lang.ArithmeticException: / by zero //实际生产过程中的处理是“记录异常错误日志”+“跳转到指定页面”; } }
配置文件(aop.xml):
<!-- 配置异常处理切面通知 --> <aop:aspect ref="globalExpt"> <!-- 如果aaa切入点执行时候发生错误,统一去找globalExpt切面里面的handExpt方法,并把抛出的异常对象传给形参ex; --> <aop:after-throwing method="handExpt" pointcut-ref="aaa" throwing="ex"/> </aop:aspect>
四、Spring考点补充
1、Spring框架中涉及到的设计模式有哪些?简单列举几个?
第一种:简单工厂模式: 简单工厂模式的实质是由一个工厂类根据传入的参数,动态决定应该创建哪一个产品类。 第二种:单例模式: Spring IOC模式就是单例模式,如果想使用多例模式可以使用scope="prototype"来创建多例对象; 第三种:代理模式 Spring AOP就是通过代理模式(proxy)将各个切面中的各个通知与目标方法整合起来(这个过程叫织入); 第四种:观察者模式(Observer) 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 spring中Observer模式常用的地方是listener监听器的实现。如ApplicationListener。 |
2、简单讲一下设计模式?
设计模式共有23中(GOF23),其中:创建:结构:行为 = 5:7:11 创建型(5种) 1. Factory Method(工厂方法) 2. Abstract Factory(抽象工厂) 3. Builder(建造者) 4. Prototype(原型) 5. Singleton(单例) 结构型(7中) 6. Adapter Class/Object(适配器) 7. Bridge(桥接) 8. Composite(组合) 9. Decorator(装饰) 10. Facade(外观) 11. Flyweight(享元) 12. Proxy(代理) 行为型(11种) 13. Interpreter(解释器) 14. Template Method(模板方法) 15. Chain of Responsibility(责任链) 16. Command(命令) 17. Iterator(迭代器) 18. Mediator(中介者) 19. Memento(备忘录) 20. Observer(观察者) 21. State(状态) 22. Strategy(策略) 23. Visitor(访问者) |
3、讲一讲Spring中Bean的生命周期:
通过这张图能大致看懂spring的生命周期,详解:
1、instantiate bean对象实例化
2、populate properties 封装属性
3、如果Bean实现BeanNameAware执行setBeanName
4、如果Bean实现BeanFactoryAwar或ApplicationContextAwar设置工厂setBeanFactory或上下文对象setApplicationContext
5、如果存在类实现BeanPostProcessor(后处理Bean),执行postProcessBeforeInitialization
6、如果Bean实现InitializingBean执行afterPropertiesSet
7、调用自定义的init-method方法
8、如果存在类实现BeanPostProcessor(处理Bean),执行postProcessAfterInitialization
9、执行业务处理
10、如果Bean实现DisposableBean执行destroy
11、调用自定义的destroy-method
第一步 就是对实例化bean,调用构造函数来创建实例,第二步 是根据配置,进行相应属性的设置,依赖注入就是在这一步完成的。
第三步 和 第四步 是让spring去了解咱们的spring容器,第五步 和 第八步 可以针对指定的Bean进行功能增强,这时一般是采用的动态代理,(两种动态代理方式:jdk动态代理和cglib动态代理)。
第六步 和 第十步 是通过实现指定的接口来完成init(初始化)和destory(销毁)操作。但是我们在通常情况下不会使用这两步,
因为我们可以通过 第七步 和 第十一步,在配置文件中设置相应的初始化和销毁方法。
总结:
对于springbean的生命周期,我们需要关注的主要有两个方法:
1、增强bean的功能可以使用后处理Bean,BeanPostProcessor
2、如果需要初始化或销毁操作,我们可以使用init-method方法和destory-method方法。
同时还需要注意一点:destory-method方法是只针对于scope=singleton的时候才有效果!