一 spring
1.Ioc/Di
IOC/DI的内容参考博客:
https://www.cnblogs.com/Mr-Rocker/p/7721824.html
(1)理解IOC/DI
(a)Ioc—Inversion of Control
即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象(这里的对
象不只是java对象,指一切外部资源,如图片、文本等)交给容器控制,而不是传统的在你的对象内部直接控制。
理解IOC要弄清楚两个概念:
概念一:有反转就有正转,那么什么是正转?什么是反转(反转的含义)
正转,就是传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象。
反转:就是由容器来帮忙创建及注入依赖对象。
概念二:谁控制谁,又控制什么?(控制的含义)
传统的应用中程序是通过new主动创建依赖对象,而IoC是有专门一个容器来创建这些对象。所以
是由IOC容器控制所依赖的对象。
(b)DI—Dependency Injection
即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入
到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一
个灵活、可扩展的平台。
首先要清楚,DI是怎么来的?
由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系
),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确
描述了“被注入对象依赖IoC容器配置依赖对象”。
其次弄清楚:谁依赖谁,为什么需要依赖,谁注入谁,注入了什么
-- 谁依赖于谁:应用程序依赖于IoC容器;
-- 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
-- 谁注入谁:IoC容器注入应用程序某个对象,应用程序依赖的对象;
-- 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。
(2)依赖注入和控制反转是同一概念吗?
不是同一概念, 但是它们两个描述的是同一件事件,但是是从不同的角度来说,控制反转是从IoC/DI容器的角
度;依赖注入是从应用程序的角度。
控制反转的描述:IoC/DI容器反过来控制应用程序,控制应用程序锁所需要的外部资源(比如:外部资源)
依赖注入的描述:应用程序依赖IoC/DI容器,依赖它注入所需要的外部资源。
(3)IOC/DI给我们带来的好处
IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良
的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容
器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这
样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
个人总结:它把对象创建和对象实例的注入分离出来,由Spring去做,使得架构更精良。
(4)Ioc/Di原理
没有找到答案,网上都在放屁,如果面试问道就回答由容器管理bean实现依赖注入、反射之类云云。
2.Spring Aop
(1)了解为什么会有Aop,Aop是为了解决什么问题而产生的,浏览下面的文章
https://blog.csdn.net/javazejian/article/details/56267036
(2)Aop原理
Aop的原理是使用了代理模式。代理模式可以做到在不修改目标对象的功能前提下,对目标功能扩展。
Spring Aop使用的代理是jdk动态代理和Cglib代理。
http://blog.csdn.net/javazejian/article/details/56267036(简单易懂的文档)。该文档可以在阅读下文前看也可以在阅
读下文之后看。
(a)静态代理,直接看例子
/** * 接口 */ public interface IUserDao { void save(); } /** * 接口实现 * 目标对象 */ public class UserDao implements IUserDao { public void save() { System.out.println("----已经保存数据!----"); } } /** * 代理对象,静态代理 */ public class UserDaoProxy implements IUserDao{ //接收保存目标对象 private IUserDao target; public UserDaoProxy(IUserDao target){ this.target=target; } public void save() { System.out.println("开始事务..."); target.save();//执行目标对象的方法 System.out.println("提交事务..."); } } /** * 测试类 */ public class App { public static void main(String[] args) { //目标对象 UserDao target = new UserDao(); //代理对象,把目标对象传给代理对象,建立代理关系 UserDaoProxy proxy = new UserDaoProxy(target); proxy.save();//执行的是代理的方法 } }
静态代理缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,同时一旦接口增加方法,目
标对象与代理对象都要维护,所以有了下面的动态代理。
(b) 动态代理
动态代理也叫jdk代理,不用实现接口。
代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我
们指定创建代理对象/目标对象实现的接口的类型)。
所在包:java.lang.reflect.Proxy
使用方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h )
ClassLoader loader:指定当前目标对象使用类加载器,获取加载器的方法是固定的。
Class<?>[] interfaces:目标对象实现的接口的类型,使用泛型方式确认类型。
InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的
方法作为参数传入。
下面看动态代理的例子:
/** *接口 */ public interface Say { void sayHello(String words); } /** *实现类 **/ public class SayImpl implements Say { @Override public void sayHello(String words) { System.out.println("hello:" + words); } } /** * 创建动态代理对象 动态代理不需要实现接口,但是需要指定接口类型 */ public class TestInvocationHandler implements InvocationHandler { private Object target; public TestInvocationHandler(Object target) { this.target=target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("invoke begin"); System.out.println("method :"+ method.getName()+" is invoked!"); method.invoke(target,args); System.out.println("invoke end"); return null; } } /** *测试类 **/ public static void main(String[] args) { TestInvocationHandler testInvocationHandler = new TestInvocationHandler(new SayImpl()); Say say = (Say)Proxy.newProxyInstance(SayImpl.class.getClassLoader(), SayImpl.class.getInterfaces(), testInvocationHandler ); say.sayHello("my dear"); } 执行结果: invoke begin method :sayHello is invoked! hello:my dear invoke end
静态代理和动态代理区别之简单说法:
静态代理:编译时就确认了代理类
动态代理:运行时由反射确认代理类,并且能代理各种类型的对象
静态代理和动态代理的区别之详细说法:
静态代理源码由程序员创建,再编译代理类。所谓静态也就是在程序运行前就已经存在 代理类的字节码文件,
代理类和委托类的关系在运行前就确定了;动态代理类的源码是在程序运行期间由JVM根据反射等机制动态
的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
(c)Cglib代理
上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一
个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫
做Cglib代理。例子如下:
/** * 目标对象,没有实现任何接口 */ public class UserDao { public void save() { System.out.println("----已经保存数据!----"); } } /** * Cglib子类代理工厂 * 对UserDao在内存中动态构建一个子类对象 */ public class ProxyFactory implements MethodInterceptor{ //维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } //给目标对象创建一个代理对象 public Object getProxyInstance(){ //1.工具类 Enhancer en = new Enhancer(); //2.设置父类 en.setSuperclass(target.getClass()); //3.设置回调函数 en.setCallback(this); //4.创建子类(代理对象) return en.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始事务..."); //执行目标对象的方法 Object returnValue = method.invoke(target, args); System.out.println("提交事务..."); return returnValue; } }
Cglib代理和JDK动态代理的区别:
JDK动态代理只能对实现了接口的类生成代理,而不能针对类;CGLIB是针对类实现代理,主要是对指定的
类生成一个子类,覆盖其中的方法(继承)当Bean实现接口时,Spring就会用JDK的动态代理;当Bean没有实现
接口时,Spring使用CGlib是实现;可以强制使用CGlib(在spring配置中加入
<aop:aspectj-autoproxy proxy-target-class="true"/>)CGlib比JDK快 使用CGLib实现动态代理,CGLib底层采用
ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对
声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。在对JDK动态代理与CGlib动态代理的代
码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
(3)Aop中术语
配合:https://blog.csdn.net/yuanlaijike/article/details/79493801
https://www.jianshu.com/p/5015f212e4e4
http://www.cnblogs.com/hongwz/p/5764917.html(主要看其中对Aop术
语的解释,结合本篇博客整理的,可以容易理解术语)
(a)关注点
又“名横切关注点”,是影响应用多处的功能(安全、事务、日志)我的理解:是我们所关心的共性的功能,比
如用户日志记录、用户的验证
(b)切面
横切关注点被模块化为特殊的类,这些类称为切面。类是对物体特征的抽象,切面就是对横切关注点的抽象。
我的理解:关注点的模块化,就像类一种抽象概念。比如我们把日志记录、用户验证等功能抽象出来进行设
计。面向对像是对事务属性和行为进行抽象。而面向切面是对共性功能进行抽象,所以说是oop的
一种补充。
这样做的好处
-- 关注点在需要增加或删除这些共性功能的时候不需对目标模块进行修改,实现了快速插拔
-- 使得系统架构更优良,分而治之,关注点只需要关注自身要实现的共性的功能,服务模块只需要关注自身
的核心功能,这样设计胜过共性功能糅杂进服务模块。
(c)连接点
程序执行的某个特定位置:如类开始初始化前、类初始化后、类某个方法调用前、调用后、方法抛出异常后。
一个类或一段程序代码拥有一些具有边界性质的特定点,这些点中的特定点就称为“连接点”。Spring仅支持
方法的连接点,即仅能在方法调用前、方法调用后、方法抛出异常时以及方法调用前后这些程序执行点织入
增强。连接点由两个信息确定:第一是用方法表示的程序执行点;第二是用相对点表示的方位。
连接点是一个应用执行过程中能够插入一个切面的点。连接点可以是调用方法时、抛出异常时、甚至修
改字段时、切面代码可以利用这些点插入到应用的正规流程中程序执行过程中能够应用通知的所有点。
解释:所谓连接点,也就是目标对象或者代理对象之中的方法(方法调用前、方法调用后、方法抛出异常
时以及方法调用前后这些程序执行点)。为什么说2个都 可以呢?因为如果是jdk实现的动态代理的
话,那么目标对象和代理对象要实现共同的接口,如果是cglib实现的动态代理的话,那么代理对象
类是目标对象类的子类。都是一个方法啦。所以这么理解就OK的啦。
(d)切入点
对连接点进行拦截的定义。上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几
十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几
个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选
连接点,选中那几个你想要的方法。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不
是一对一的关系,一个切点可以匹配多个连接点。
(e)引入
引入允许我们向现有的类中添加方法或属性。
(f)织入
织入是将切面应用到目标对象来创建的代理对象过程.
(g)通知
所谓通知指的就是指拦截到连接点之后要执行的代码。执行的代码就是切面要完成的工作,在 AOP 术语中,切
面的工作被称为通知。
工作内容:通知定义了切面是什么以及何时使用。除了描述切面要完成的工作,通知还解决何时执行这个工作。
Spring 切面可应用的 5 种通知类型:
Before——在方法调用之前调用通知。
After——在方法完成之后调用通知,无论方法执行成功与否。
After-returning——在方法执行成功之后调用通知。
After-throwing——在方法抛出异常后进行通知。
Around——通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
(h)最后上一张图,配合上面(a)、(b)、(c)、(d)、(e)、(f)一起理解。
参考文章:
http://blog.csdn.net/github_34889651/article/details/51321499
http://blog.csdn.net/qq_27093465/article/details/53351403
(4)Spring中如何使用Aop,下面是一些例子
XML的实现方式
例一,基于Spring的AOP简单实现
定义一个接口:
public interface HelloWorld { void printHelloWorld(); void doPrint(); }
定义两个实现类:
public class HelloWorldImpl1 implements HelloWorld { public void printHelloWorld() { System.out.println("Enter HelloWorldImpl1.printHelloWorld()"); } public void doPrint() { System.out.println("Enter HelloWorldImpl1.doPrint()"); return ; } }
public class HelloWorldImpl2 implements HelloWorld { public void printHelloWorld() { System.out.println("Enter HelloWorldImpl2.printHelloWorld()"); } public void doPrint() { System.out.println("Enter HelloWorldImpl2.doPrint()"); return ; } }
横切关注点,这里是打印时间:
public class TimeHandler { public void printTime() { System.out.println("CurrentTime = " + System.currentTimeMillis()); } }
aop.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" /> <bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" /> <bean id="timeHandler" class="com.xrq.aop.TimeHandler" /> <aop:config> <aop:aspect id="time" ref="timeHandler"> <aop:pointcut id="addAllMethod" expression="execution(* com.xrq.aop.HelloWorld.*(..))" /> <aop:before method="printTime" pointcut-ref="addAllMethod" /> <aop:after method="printTime" pointcut-ref="addAllMethod" /> </aop:aspect> </aop:config> </beans>
写一个main函数调用一下:
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml"); HelloWorld hw1 = (HelloWorld)ctx.getBean("helloWorldImpl1"); HelloWorld hw2 = (HelloWorld)ctx.getBean("helloWorldImpl2"); hw1.printHelloWorld(); System.out.println(); hw1.doPrint(); System.out.println(); hw2.printHelloWorld(); System.out.println(); hw2.doPrint(); } 运行结果: CurrentTime = 1446129611993 Enter HelloWorldImpl1.printHelloWorld() CurrentTime = 1446129611993 CurrentTime = 1446129611994 Enter HelloWorldImpl1.doPrint() CurrentTime = 1446129611994 CurrentTime = 1446129611994 Enter HelloWorldImpl2.printHelloWorld() CurrentTime = 1446129611994 CurrentTime = 1446129611994 Enter HelloWorldImpl2.doPrint() CurrentTime = 1446129611994
例二,增加一个横切关注点,打印日志,Java类为
public class LogHandler { public void LogBefore() { System.out.println("Log before method"); } public void LogAfter() { System.out.println("Log after method"); } }
<?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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <bean id="helloWorldImpl1" class="com.xrq.aop.HelloWorldImpl1" /> <bean id="helloWorldImpl2" class="com.xrq.aop.HelloWorldImpl2" /> <bean id="timeHandler" class="com.xrq.aop.TimeHandler" /> <bean id="logHandler" class="com.xrq.aop.LogHandler" /> <aop:config> <aop:aspect id="time" ref="timeHandler" order="1"> <aop:pointcut id="addTime" expression="execution(* com.xrq.aop.HelloWorld.*(..))" /> <aop:before method="printTime" pointcut-ref="addTime" /> <aop:after method="printTime" pointcut-ref="addTime" /> </aop:aspect> <aop:aspect id="log" ref="logHandler" order="2"> <aop:pointcut id="printLog" expression="execution(* com.xrq.aop.HelloWorld.*(..))" /> <aop:before method="LogBefore" pointcut-ref="printLog" /> <aop:after method="LogAfter" pointcut-ref="printLog" /> </aop:aspect> </aop:config> </beans>
测试类不变,打印结果:
4.Spring事务
(1)首先看下数据库的事务
(a)A、C、I、D。
原子性(Atomic):指事务由原子的操作序列组成,所有的操作要么全部成功要么全部失败回滚。
一致性(Consistent):事务的执行不能破坏数据库的完整性和一致性,一个事务在执行前和执行后数据库都必
须处于一致性状态,例如在做多表操作时,多个表要么都是事务后新值要么都是事务后
的旧值。
。
隔离性(Isolated):并发执行的事务彼此无法看到对方的中间状态。
持久性(Durable):事务完成后所做的改动都会被持久化,即使发生灾难性的失败。通过日志和同步备份可以在故
障发生后重建数据。
关于原子性和一致性的区别:
张三给李四转账100元,需要先重张三的账户上减去100元,在给李四的账户上增加100元,如果张三减去
100失败那么整个事物就回滚,这就是原子性的体现;假设张三李四的账户在转账前都是1000元,那么转账
后张三账户为900元,李四账户为1100元,这就是一致性的体现。
(b)事务的隔离级别
MySql默认的隔离级别是REPEATABLE-READ
Read uncommit(读未提交 )
可以读取到其它事务未提交的内容,这是最低的隔离级别。在这个隔离级别三个事务并发
的问题都有可能会发生。
幻读、不可重复读和脏读都允许
Read commit(读已提交)
就是只能读取到其它事务已提交的数据,这个隔离级别可以解决胀读问题
允许幻读、不可重复读,不允许脏读
Repeatable read(可重复读)
可以保证整个数据过程中对事物的多次数据读取结果是相同的,这个级别可以解决脏读和
不可重复读的问题。该级别是MySql 默认的隔离级别。
允许幻读,不允许不可重复读和脏读
Serializable(串行化)
所有事物操作都依次顺序执行,这个级别会导致并发度下降性能最差,不过这个级别可以
解决前面提到的所有性能问题
幻读、不可重复读和脏读都不允许
幻读
是指一个事务中执行两次完全的查询第二次查询返回的结果集和第一次查询不同,与不可重复读的
区别是不可重复读是读同一条记录两次读取的指不同,而幻读是记录的增加或者删除导致两次想同
条件获取的记过记录数不同。事务发生了新增或删除。
脏读
一个事务在处理过程中读取了另外一个事务未提交的数据。例如账户A转账给B 500元,B 余额增加
后但事务还未完成提交,此时另外的请求中获取到的是B增加的余额,这就发生了脏读。
不可重复读
是指对于数据库中的某个数据,一个事务范围内多次查询返回了不同的数据值,这是因为多次查询
之间有其它事务修改了数据并进行了提交。事务发生了修改。
(2)Spring事务
(a)Spring事务的原理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,
Spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,
可以按照以下步骤进行:
(i)获取连接 Connection con = DriverManager.getConnection()
(ii)开启事务con.setAutoCommit(true/false);
(iii)执行CRUD
(iv)提交事务/回滚事务 con.commit() / con.rollback();
(v)关闭连接 conn.close();
使用Spring的事务管理功能后,我们可以不再写步骤(ii)和(iv)的代码,而是由Spirng 自动完成。那么
Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体
上理解Spring的事务管理实现原理了。下面简单地介绍下,注解方式为例子
(b)Spring 事务的传播
REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个
事务,否则自己新建一个新的事务(默认,和SUPPORTS一起记)
SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被
调用,该方法就在没有事务的环境下执行(和默认分一类吧,暂叫它默认2吧)。
REQUIRESNEW:总会发起一个新得事务。不管是否存在事务,该方法总会为自己发起一个新的事 务。如
果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建(新事务,零碎没组)。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果
方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行(
不需要事务,零碎没组)。
MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事
务的环境下被调用,容器抛出例外(必须要在有事务的环境中执行,和NEVER一起记)。
NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常
执行(必须要在没有有事务的环境中执行)。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属
性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会
对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。
5.spring 常用标签
(1)@Controller、@Service、@Respository,分别是Controller、Service、Dao的注解。
(2)@Component
@component就是说把这个类交给Spring管理。是一个泛化的概念,仅仅表示一个组件 (Bean) ,可以作用在任何
层次。因为在持久层、业务层和控制层中,分别采用@Repository、@Service和@Controller对分层中的类进行注
解,而用@Component对那些比较中立的类进行注解。
(3)@Resource和@Autowired
两者均做bean注入时使用的,@Resource的作用相当于@Autowired。@Resource是按name进行注入,而
@Autowired是按type进行注入,
@Resource示例如下:
public class TestServiceImpl { @Resource(name="userDao") private UserDao userDao; // 用于字段上 @Resource(name="userDao") public void setUserDao(UserDao userDao) { this.userDao = userDao;
注:将Resource放在set方法上更符合面向对象的思想,对set方法操作而不是直接对属性进行操作。
(3)@PostConstruct 和 @PreDestroy
用于实现初始化和销毁bean之前进行的操作
6.Spring Bean的生命周期
购写本节,参考文章:
https://blog.csdn.net/w_linux/article/details/80086950
https://blog.csdn.net/fuzhongmin05/article/details/73389779
(1)最简单的说法,为了方便记忆(2)看一下,(2)才是重点
(a)bean定义
在配置文件里面用<bean></bean>来进行定义。
(b)bean初始化
有两种方式初始化:
在配置文件中通过指定init-method属性来完成
实现org.springframwork.beans.factory.InitializingBean接口
(c)bean调用
有三种方式可以得到bean实例,并进行调用
(d)bean销毁
销毁有两种方式
使用配置文件指定的destroy-method属性
实现org.springframwork.bean.factory.DisposeableBean接口
(2)简单版,重点,面试的时候按照这个答
(a)实例化bean对象(通过构造方法或者工厂方法,这里有点费解,为什么没有通过setter方法)
(b)设置对象属性(setter等)(依赖注入)
(c)检查Aware接口,设置相关依赖。如果Bean实现了BeanNameAware接口,spring将bean的id传给
setBeanName()方法;如果Bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将
BeanFactory实例传进来。
(d)将Bean实例传递给Bean的前置处理器的
postProcessBeforeInitialization(Object bean, String beanname)方法
(e)调用Bean的初始化方法。(初始化bean有两种方式,在配置文件中通过指定init-method属性来完成和实现
org.springframwork.beans.factory.InitializingBean接口。图上有画)
(f)将Bean实例传递给Bean的后置处理器的
postProcessAfterInitialization(Object bean, String beanname)方法使用Bean。
(g)使用。此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上
下文被销毁。
(h)容器关闭之前,调用Bean的销毁方法(bean的销毁方法有两种:使用
配置文件指定的destroy-method属性和实现org.springframwork.bean.factory.DisposeableBean接口。图上
有画)
(2)详细版,闲的蛋疼时候在看
bean的生命周期流程图:
(a)Spring对bean进行实例化,默认bean是单例;
(b)Spring对bean进行依赖注入;
(c)如果bean实现了BeanNameAware接口,spring将bean的id传给setBeanName()方法;
(d)如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进
来;如果bean实现了 ApplicationContextAware接口,它的setApplicationContext()方法将被调用,将应
用上下文的引用传入到bean中;
(e)如果bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用;
(f)如果bean实现了InitializingBean接口,spring将调用它的afterPropertiesSet接口方法,类似的如果bean使
用了init-method属性声明了初始化方法,该方法也会被调用;
(g)如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用;
(h)此时bean已经准备就绪,可以被应用程序使用了,他们将一直驻留在应用上下文中,直到该应用上下文
被销毁;
(i)若bean实现了DisposableBean接口,spring将调用它的distroy()接口方法。同样的,如果bean使用了
destroy-method属性声明了销毁方法,则该方法被调用;
7.bean的作用域
Spring Bean共有五种作用域,其中request、session、global session三种作用域仅在基于web的应用中使用(不必
关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
(1)singleton
当一个bean的作用域为singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请
求,只要id与该bean定义相匹配,则只会返回bean的同一实例。
(2)prototype
Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器
的getBean() 方法)时都会创建一个新的bean实例。根据经验,对所有有状态的bean应该使用prototype作用域,
而对无状态的bean则应该使用 singleton作用域
(3)request
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个
bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。
(4)session
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形
下有效。
(5)global session
global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。
Portlet规范定义了全局Session的概念,它被所有构成某个 portlet web应用的各种不同的portlet所共享。在
global session作用域中定义的bean被限定于全局portlet Session的生命周期范围内。如果你在web中使用
global session作用域来标识bean,那么web会自动当成session类型来使用。
8.Spring 注入方式
(1)setter注入
(2)构造器注入
(3)静态工厂方法注入
(4)实例工厂方法注入
10.比较重要的一些面试题
(1)BeanFactory 接口和 ApplicationContext 接口有什么区别 ?
(a)ApplicationContext 接口继承BeanFactory接口,Spring核心工厂是BeanFactory,BeanFactory采取延迟加载,第
一次getBean时才会初始化Bean,ApplicationContext是会在加载配置文件时初始化Bean。
(b)ApplicationContext是对BeanFactory扩展,它可以进行国际化处理、事件传递和bean自动装配以及各种不同应用
层的Context实现。开发中基本都在使用ApplicationContext,web项目使用WebApplicationContext ,很少用到BeanFactory。
(2)spring配置bean实例化有哪些方式?
(a)使用类构造器实例化(默认无参数)
(b)使用静态工厂方法实例化(简单工厂模式)
(c)使用实例工厂方法实例化(工厂方法模式)
11.Spring Quartz、Spring Task、elastic-job
(1)Spring Quartz的特点:
https://blog.csdn.net/mseeworld/article/details/53331197
(2)当当的elastic-job
二 Spring MVC
1.Spring MVC原理
(1)概述
Spring MVC是基于Servlet的。
若基于某个框架开发一个模型2的应用程序,我们要负责编写一个
Dispatcher Servlet(如果忘记该例子书上找)。其中Dispatcher Servlet必须能
够做如下事情:
(a)根据URI调用相应的action。
(b)实例化正确的控制器类
(c)根据请求参数来构造表单bean
(d)调用控制器对象的相应方法
(e)转向到一个视图(JSP页面)
Spring MVC是一个包含了Dispatcher Servlet的MVC框架,它调用控制器方法
并转发到视图。这是使用Spring MVC的第一个好处:不需要编写Dispatcher
Servlet。以下是Spring MVC具有的能加速开发的功能列表:
(a)Spring MVC提供了一个Dispatcher Servlet,无需额外开发
(b)Spring MVC实例化控制器,并根据用户输入来构造bean。
(c)Spring MVC可以自动绑定用户输入,并正确转换数据类型。例如
Spring MVC能自动解析字符串,并设置float或decimal类型的属性
(d)Spring MVC支持国际化和本地化。支持根据用户区域显示多国语言。
(e)Spring MVC支持多种视图技术。如JSP、Velocity和FreeMarker。
(f)Spring MVC提供校验用户输入功能。
(g)Spring MVC中使用基于XML的配置文件,可以编辑,二无需重新编译应
用程序。
(h)Spring MVC是Spring框架的一部分,可以利用Spring提供的其它能力。
(2)Spring MVC原理和工作流程
(a)简单版
(i)客户端请求提交到DispatcherServlet
(ii)由DispatcherServlet控制器查询一个或多个HandlerMapping,找到
处理请求的Controller
(iii)DispatcherServlet将请求提交到Controller
(iv)Controller调用业务逻辑处理后,返回ModelAndView
(v)DispatcherServlet查询一个或多个ViewResoler视图解析器,找到
ModelAndView指定的视图
(vi)视图负责将结果显示到客户端
(b)完整版
(1)发起请求到前端控制器(DispatcherServlet);
(2)前端控制器请求HandlerMapping查找Handler,可以根据xml配置、
注解进行查找;
(3)处理器映射器HandlerMapping向前端控制器返回Handler;
(4)前端控制器调用处理器适配器去执行Handler;
(5)处理器适配器去执行Handler;
(6)Handler执行完成给适配器返回ModelAndView;
(7)处理器适配器向前端控制器返回ModelAndView(是springmvc框架
的一个底层对象,包括Model和View);
(8)前端控制器请求视图解析器去进行视图解析,根据逻辑视图名称解
析真正的视图(jsp...);
(9)视图解析器向前端控制器返回View;
(10)前端控制器进行视图渲染,视图渲染就是将模型数据(在
ModelAndView对象中)填充到request域中。
(11)前端控制器向用户响应结果。
2.Spring MVC功能介绍
(1)DispatcherServlet
org.springframework.web.servlet.DispatcherServlet。要使用这个Servlet需要
它配置在web.xml中
<!-- Spring MVC 的Servlet,它将加载WEB-INF/springDispatcherServlet-servlet.xml 的配置文件,以启动Spring MVC模块--> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping>
DispatcherServlet会使用Spring MVC诸多默认组件,此外,初始化时它
会寻找一个在应用程序WEB-INF目录下的配置文件,该配置文件的命名
规则如下:
servletName-servlet.xml
其中,servletName是web.xml中DispatcherServlet的名称,按上例,应
该为:SpringMVC-servlet.xml(上文中servlet的名字是springmvc,是否
不区分大小写有机会验证一下,一般都是写成spring-mvc.xml)。此外也
可以把Spring MVC的配置文件放到应用程序目录的任何地方,用servlet
的<init-param>指定,示例如下:
<servlet> <servlet-name>DispatcherServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:spring/abc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
其中“classpath*:spring/abc.xml”,也可以换成绝对路径。
(2)Controller接口
在Spring2.5以前,开发一个控制器的唯一方法是实现
org.springframework.web.servlet.mvc.Controller接口,这个接口公开了一个
handleRequest方法。下面是该方法的签名:
public ModelAndView handleRequest(HttpServletRequest request,HttpServletResponse response)throws Exception;
Controller接口的实现类只能处理一个动作,而一个基于注解的控制器可以同时
支持多个请求处理动作,并且无需实现任何接口,使用@Controller注解。
(3)View Resolver(视图解析器)
Spring MVC中的视图解析器负责解析视图,可以通过在配置文件中定义一个
View Resolver来配置视图解析器,示例如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property key="prefix" value="/WEB-INF/jsp/"/> <property key="suffix" value=".jsp" /> </bean>
通过上面的方法就可以将视图路径由原理的“/WEB-INF/jsp/myPage.jsp”改写成
“myPage”。ViewResolver接口其内部只有一个接口方法,具体如下:
View resolveViewName(String viewName, Locale locale) throws Exception;
由上可知其是通过解析ViewName来得到View视图对象。
注:InternalResourceViewResolver只是视图解析器中的一种,JSP+JSTL这一
套用它,但是其它视图如Fremakker、velocity。
(4)Spring MVC支持的返回方式
* ModelAndView
* Model
* ModelMap
* Map
* View
* String
* void
(a)Model和ModelAndView
(i)Model
Model是每次请求中都存在的默认参数,利用其addAttribute()方法即可
将服务器的值传递到jsp页面中;Model的生命周期只是一个请求的处理
过程,请求处理完,Model也就销毁了。
(ii)ModelAndView
包含model和view两部分,使用时需要自己实例化,利用ModelMap用
来传值,也可以设置view的名称
例一,使用Model传值
public String getAllBooks(Model model){ logger.error("/list-books"); List<Book> books= bookService.getAllBooks(); model.addAttribute("books", books); return "BookList"; }
例二,使用ModelAndView来传值。
使用ModelAndView传递值有两种方法,不同方法在jsp页面的
取值方式不同,同时设置了view的名称
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { LibraryException le=null; if(ex instanceof LibraryException){ le=(LibraryException)ex; }else{ le=new LibraryException("系统未知异常!"); } ModelAndView modelAndView=new ModelAndView(); modelAndView.addObject("exception",le.getMessage()); modelAndView.getModel().put("exception",le.getMessage()); modelAndView.setViewName("error"); return modelAndView; }
jsp中${requestScope.exception1}可以取出exception1的值;
jsp中${exception2}可以取出exception2的值
关于Model和ModelAndView的区别想进一步了解可以查看博客:
https://blog.csdn.net/wangmeng951011/article/details/53121483
通过ModelAndView构造方法可以指定返回的页面名称,也可以通过
setViewName()方法跳转到指定的页面。上面的例子是通过
setViewName()方法指定跳转页面,通过构造函数指定跳转页面示例
如下:
@RequestMapping("/hello") public ModelAndView helloWorld() { String message = "Hello World, Spring 3.x!"; return new ModelAndView("hello", "message", message); }
(b)Map
@RequestMapping(value="/showdog") public String hello1(){ return "hello"; }
@RequestMapping(value="/print") @ResponseBody public String print(){ String message = "Hello World, Spring MVC!"; return message; }
返回json的例子(使用Jackson):
public class MyDateConverter implements Converter<String,Date> { @Override public Date convert(String source) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); try { return sdf.parse(source); } catch (ParseException e) { e.printStackTrace(); } return null; }
补充一下Converter接口的说明,其定义如下:
<!-- 日期转换工厂 --> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.converter.MyDateConverter"> <constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg> </bean> </list> </property> </bean>
注意要给annotation-driven元素的conversion-service属性赋上bean
名称:
<mvc:annotation-driven conversion-service="myDateConverter"> </mvc:annotation-driven>
注册这个Converter服务,然后对于所有使用了@RequestMapping
注解的方法参数,都可以自动将String按规定的格式转换为Date类
型。
完整的一个配置样例如下:
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd "> <context:component-scan base-package="controller"/> <context:component-scan base-package="Service"/> <!-- <mvc:annotation-driven>元素注册用于支持基于注解的控制器的请求处理方法的Bean对象。 详解:https://my.oschina.net/HeliosFly/blog/205343 --> <mvc:annotation-driven conversion-service="conversionService"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="converter.MyConverter"> <constructor-arg type="java.lang.String" value="MM-dd-yyyy"/> </bean> </list> </property> </bean> </beans>
(b)Formatter
Formatter就像Converter一样,也是将一种类型转换成另一种类型。但
是Formatter更适合Web层,而Converter则可以用在任意层种。为了转
换Spring MVC应用程序表单种的用户输入,始终应选择Formatter而不
是Converter。
Formatter的使用方式,通过例子来讲解。
(i)首先编写一个实现org.springframework.format.Formatter接口的
Java类,该类是将String 转换成Date。
package app06b.formatter import org.springframework.format.Formatter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class DateFormatter implements Formatter<Date>{ private String datePattern; private SimpleDateFormat dateFormat; public DateFormatter(String datePattern) { this.dateFormat = dateFormat; dateFormat = new SimpleDateFormat(datePattern); dateFormat.setLenient(false); } public Date parse(String s, Locale locale) throws ParseException { try { SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern); dateFormat.setLenient(false); return dateFormat.parse(s); } catch (ParseException e) { throw new IllegalArgumentException("invalid date format. Please use this pattern\"" + datePattern + "\""); } } public String print(Date date, Locale locale) { return dateFormat.format(date); } }
这里插入一下对Formatter接口的说明:
<?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:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd "> <context:component-scan base-package="controller"/> <context:component-scan base-package="Service"/> <!-- <mvc:annotation-driven>元素注册用于支持基于注解的控制器的请求处理方法的Bean对象。 详解:https://my.oschina.net/HeliosFly/blog/205343 --> <mvc:annotation-driven conversion-service="conversionService"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> <!--需要配置此项来扫描Formatter--> <context:component-scan base-package="app06b.formatter"/> <bean id="formatterConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="formatters"> <set> <bean class="formatter.DateFormatter"> <constructor-arg type="java.lang.String" value="MM-dd-yyyy"/> </bean> </set> </property> </bean> </beans>
注意,需要为这个Formatter添加component-scan元素和给
annotation-driven元素的conversion-service属性赋上bean名称。
(7)文件上传
文件上传方式有Servlet上传文件、ApacheCommonsFileUpload上传文件
(apache)和MultipartFile接口(Spring MVC)。但是无论采用哪一种方
式上传,前端的写法都一样。文件上传时需要注意两点:一是表单提交
Form内容enctype必须加上,且内容必须是multipart/form-data,表示二
进制方式提交二是提交方式必须是post。
单文件上传:
<form action="${pageContext.request.contextPath}/fileUpload" method="POST" enctype="multipart/form-data"> username:<input type="text" name="username" /><br> 上传文件:<input type="file" name="fileName" /><br> <input type="submit" value="文件上传" /><br> </form>
多文件上传(仅限于H5):
<!-- 配置前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
springmvc-config.xml
function loginAction() {
// 获取用户输入的账号和密码
var name = $('#count').val();
var password = $('#password').val();
$.ajax({
url : 'account/login.do',
type : 'post',
// data对象中的属性名要和服务端控制器的参数名一致 login(name, password)
data : {
'name' : name,
'password' : password
},
dataType : 'json',
success : function(result) {
if (result.state == 0) {
// 登录成功,设置cookie并跳转edit.html
addCookie('userId', result.data.id);
addCookie('nick', result.data.nick);
location.href = 'edit.html';
} else {
// 登录失败
var msg = result.message;
$('#sig_in').next().html(msg);
$('#sig_in').next().css("color", "red");
}
},
error : function(e) {
alert("系统异常");
}
});
$('#password').val("");
}
Java代码:
<script type="text/javascript"> $(document).ready(function(){ $.ajax({ type:'POST', url:'<%=path%>/user/ceshi.do', dataType:"json", contentType:"application/json", data:JSON.stringify([{id:"1",name:"cehshi1"},{id:"2",name:"ceshi2"}]), success:function(){ } }); </script>
Java代码:
var testList=[];
var user={};
user.id=1;
user.name='jack';
testList.push(user);
var user2={};
user2.id=2;
user2.name='tom';
testList.push(user2);
$.ajax({
// headers必须添加,否则会报415错误
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
type: 'POST',
dataType: "json", //表示返回值类型,不必须
data: JSON.stringify(testList),
url: '/test/postList',
success: function(){
alert('success');
}
});
Java代码:
<form action="/gadget/testRequestParam" method="post"> 参数inputStr:<input type="text" name="inputStr"> 参数intputInt:<input type="text" name="inputInt"> </form>
Java代码:
// required=false表示不传的话,会给参数赋值为null,required=true就是必须要有 @RequestMapping("testRequestParam") public String filesUpload(@RequestParam(value="aa", required=true) String inputStr, HttpServletRequest request)
如果用@RequestMapping注解的参数是int基本类型,但是required=false,
这时如果不传参数值会报错,因为不传值,会赋值为null给int,这个不可以
@RequestMapping("testRequestParam") public String filesUpload(@RequestParam(value="aa", required=true) String inputStr, @RequestParam(value="inputInt", required=false) int inputInt ,HttpServletRequest request) { // ......省略 return "index"; }
错误信息:
“Consider declaring it as object wrapper for the corresponding primitive type.”
解决办法:用Integer替代int。
在看一个例子,用于了解参数自动类型转换,重点在该类的注释上面:
/** * 如果请求参数中的 userId 是纯数字,那么使用 @RequestParam * 时,可以根据自己的需求将方法参数类型设置为 Long、Integer、 * String,它将自动进行类型转换 */ @RequestMapping(value="/user/show") public ModelAndView show(@RequestParam(value="userId",defaultValue="1") Long userId) { // 创建 ModelAndView 对象,并设置视图名称 ModelAndView mv = new ModelAndView("show"); // 添加模型数据 mv.addObject("msg", "User ID:" + userId); return mv; }
@ResponseBody
@ResponseBody是作用在方法上的,@ResponseBody 表示该方法的返回结果直
接写入 HTTP response body 中,一般在异步获取数据时使用【也就是AJAX】,
在使用 @RequestMapping后,返回值通常解析为跳转路径,但是加上
@ResponseBody 后返回结果不会被解析为跳转路径,而是直接写入
HTTP response body 中。 比如异步获取 json 数据,加上 @ResponseBody 后,
会直接返回 json 数据。@RequestBody 将 HTTP 请求正文插入方法中,使用适合
的 HttpMessageConverter 将请求体写入某个对象。下面是Spring MVC返回JSON
的例子:
例一,返回ModelAndView,其中包含map集
/* * 返回ModelAndView类型的结果 * 检查用户名的合法性,如果用户已经存在,返回false,否则返回true(返回json数据,格式为{"valid",true}) */ @RequestMapping(value = "/checkNameExistsMethod2", produces = "application/json;charset=UTF-8") //这里的produces值在不设置的情况下将根据返回结果自动决定 @ResponseBody public ModelAndView checkNameValidMethod2(@RequestParam String name) { boolean result = true; //... Map<String, Boolean> map = new HashMap<>(); map.put("valid", result); return new ModelAndView(new MappingJackson2JsonView(), map); }
例二,返回String类型的json,这里有两种方式:
方式一:使用jackson-databind-x.x.x.jar包中的ObjectMapper将Map型
数据改写为String并返回。
/* * 返回String类型的结果 * 检查用户名的合法性,如果用户已经存在,返回false,否则返回true(返回json数据,格式为{"valid",true}) */ @RequestMapping(value = "/checkNameExistsMethod1", produces = "application/json;charset=UTF-8") @ResponseBody public String checkNameValidMethod1(@RequestParam String name) { boolean result = true; //... Map<String, Boolean> map = new HashMap<>(); map.put("valid", result); ObjectMapper mapper = new ObjectMapper(); String resultString = ""; try { resultString = mapper.writeValueAsString(map); } catch (JsonProcessingException e) { e.printStackTrace(); } return resultString; }
方式二,直接返回字符串,主要key/value值必须使用含有转义字符\的双引
号,单引号无效。
/* * 返回String类型的结果 * 检查用户名的合法性,如果用户已经存在,返回false,否则返回true(返回json数据,格式为{"valid",true}) */ @RequestMapping(value = "/checkNameExistsMethod1", produces = "application/json;charset=UTF-8") @ResponseBody public String checkNameValidMethod1(@RequestParam String name) { boolean result = true; String resultString = "{\"result\":true}"; //注意一定是双引号 "{\"result\":\"success\"}" return resultString; }
例三,返回任何预定义class类型的结果:
@RequestMapping(value = "/findEmployeebyName") @ResponseBody public Employee findEmployeebyName(String name) { List<Employee> lstEmployees = employeeService.getAllEmployees(); for (Employee employee : lstEmployees) { if (employee.getName().equals(name)) return employee; } return null; }
例四,使用HttpServletResponse对象的response.getWriter().write(xxx)方法
@RequestMapping(value="/hello5.do") public void hello(HttpServletResponse response) throws IOException{ UserInfo u1=new UserInfo(); u1.setAge(15); u1.setUname("你好"); UserInfo u2=new UserInfo(); u2.setAge(152); u2.setUname("你好2"); Map<String,UserInfo> map=new HashMap<String, UserInfo>(); map.put("001", u1); map.put("002", u2); String jsonString = JSON.toJSONString(map); response.setCharacterEncoding("utf-8"); response.getWriter().write(jsonString); response.getWriter().close(); }
(4)@ModelAttribute
@ModelAttribute使用场景如下:
* 注释在无返回值方法上,方法上未使用@RequestMapping
* 注释在有返回值方法上,方法上未使用@RequestMapping
* 注释在方法上,并且方法上使用了@RequestMapping
* 注释在方法的参数上
@ModelAttribute注解作用在方法上或者方法的参数上,表示将被注解的方法的
返回值或者是被注解的参数作为Model的属性加入到Model中,然后Spring框架
自会将这个Model传递给ViewResolver。简单的说@ModelAttribute最主要的作
用是将数据添加到模型对象中,用于视图页面展示时使用。@ModelAttribute等
价于 model.addAttribute("attributeName", abc); 但是根据@ModelAttribute注释
的位置不同,和其他注解组合使用,致使含义有所不同
(a)@ModelAttribute注释在无返回值方法上,方法上未使用@RequestMapping
被@ModelAttribute注释的方法会在此controller每个方法执行前被执行
@Controller public class HelloWorldController { @ModelAttribute public void populateModel(@RequestParam String abc, Model model) { model.addAttribute("attributeName", abc); } @RequestMapping(value = "/helloWorld") public String helloWorld() { return "helloWorld"; } }
上例中会先执行populateModel方法,然后在执行helloWorld方法执行,并
参数abc被带到Model中后接着带入helloWorld方法中,当返回视图
“helloWorld”后,Model会被带到页面上。
当把populateModel方法和helloWorld方法合二为一将如下例(这是我们常
用的方法):
@Controller public class HelloWorldController { @RequestMapping(value = "/helloWorld") public String helloWorld(@RequestParam String abc, Model model) { model.addAttribute("attributeName", abc); return "helloWorld"; } }
(b)注释在有返回值方法上,方法上未使用@RequestMapping
@ModelAttribute public Account addAccount(@RequestParam String number) { Account account = accountManager.findAccount(number); return account ; }
由于model属性的名称没有指定,它由返回类型隐含表示,如这个方法返
回Account类型,那么这个model属性的名称是account。这段相当于如下
代码:
model.addAttribute("account", account);
但是这样用很不爽,如果返回值是int,那么上段代码相当于:
model.addAttribute("int",value);这时可以使用@ModelAttribute(value=“”)
或@ModelAttribute(“”)
示例如下:
@ModelAttribute("attributeName") public Account addAccount(@RequestParam String number) { Account account = accountManager.findAccount(number); return account ; }
这时上段代码相当于:model.addAttribute("attributeName",account);
(c)注释在方法上,并且方法参数上使用了@RequestMapping
用@ModelAttribute注解@RequestMapping方法的场景很少会用到,因
为这种场景下的返回值就不是视图了而是ModelAttribute的value值,如
果不是视图,那么@RequestMapping注解的方法也失去了本身的含义。
(d)@ModelAttribute注释在方法的参数上
public String test1(@ModelAttribute("user") UserModel user)
它的作用是将该绑定的命令对象以“user”为名称添加到模型对象中供视图页
面展示使用。我们此时可以在视图页面使用${user.username}来获取绑定的
命令对象的属性。
关于@ModelAttribute想更细致的了解,可以参考文章:
https://blog.csdn.net/catoop/article/details/51171108(该文章中可能有些错误)
https://jingyan.baidu.com/article/a3aad71a158d4bb1fa00967b.html
(5)@SessionAttribute
@ModelAttribute注解作用在方法上或者方法的参数上,表示将被注解的方法的
返回值或者是被注解的参数作为Model的属性加入到Model中,然后Spring框架
自会将这个Model传递给ViewResolver。Model的生命周期只有一个http请求的
处理过程,请求处理完后,Model就销毁了。如果想让参数在多个请求间共享,
那么可以用到要说到的@SessionAttribute注解。
注:SessionAttribute只能作用在类上。用法,看例子解说:
@Controller @RequestMapping("sc") @SessionAttributes("name") public class SessionController { @RequestMapping("session") public String sessions(Model model,HttpSession session){ model.addAttribute("name", "winclpt"); session.setAttribute("myName", "chke"); return "session"; }
上面的代码将Model中的name参数保存到了session中(如果Model中没有
name参数,而session中存在一个name参数,那么SessionAttribute会讲这个
参数塞进Model中)
原理理解:它的做法大概可以理解为将Model中的被注解的attrName属性保存
在一个SessionAttributesHandler中,在每个RequestMapping的方
法执行后,这个SessionAttributesHandler都会将它自己管理的“属性”
从Model中写入到真正的HttpSession;同样,在每个
RequestMapping的方法执行前,SessionAttributesHandler会将
HttpSession中的被@SessionAttributes注解的属性写入到新的
Model中。
如果想删除session中共享的参数,可以通过SessionStatus.setComplete(),
这句只会删除通过@SessionAttribute保存到session中的参数。
SessionAttribute有两个参数:
String[] value:要保存到session中的参数名称
Class[] typtes:要保存的参数的类型,和value中顺序要对应上
可以这样写:@SessionAttributes(types = {User.class,Dept.class},value={“attr1”,”attr2”})
(6)@AutoWired和@Service
因该注意使用@Service时需要一个<component-scan/>来扫描依赖基本包
<context:component-scan base-package="com.Service...."/>
其它略,这两个标签太熟悉了。
(7)@DateTimeFormat和@JsonFormat(非Spring标签)
(a)@DateTimeFormat
通过例子来讲解:
(i)定义一个pojo,它有一个 java.util.Date 类型的属性 date。
import java.util.Date; public class DateVo { private Date date; public void setDate(Date date){ this.date = date; } public Date getDate(){ return date; } }
(ii)定义一个Controller
@RestController @RequestMapping("/date/") public class DateController { @RequestMapping("test") public DateVo getDate(DateVo vo){ System.out.println("date1:"+vo.getDate()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String date = sdf.format(vo.getDate()); System.out.println("date2:"+date); DateVo vo2 = new DateVo(); vo2.setDate(new Date()); return vo2; } }
(iii)访问 /date/test ,并传入参数:2018-08-02 22:05:55。发现不能访
问,报异常,错误信息如下:
这时,就可以使用 Spring 的 @DateTimeFormat注解格式化参数,
来解决上述问题。
(iv)改造 DateVo:
public class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") private Date date; public void setDate(Date date){ this.date = date; } public Date getDate(){ return date; } }
再像上面一样访问 /date/test ,并传入参数:2018-08-02 22:05:55,
将在控制台上打印:
date1:Thu Aug 02 22:05:55 CST 2018
date2:2018-08-02 22:05:55
可以看到,加入 @DateTimeFormat 注解后参数可以被接收到了,
但日期时间的格式还是需要自己再手动转换一下。这是因为
@DateTimeFormat 注解的 pattern 属性值指定的日期时间格式并
不是将要转换成的日期格式,这个指定的格式是和传入参数的日
期格式,假如注解为:
@DateTimeFormat(pattern="yyyy/MM/dd HH:mm:ss")
则传入的参数应该是这样的:
2018/08/02 22:05:55
否则会抛出异常。
注:可对java.util.Date、java.util.Calendar、java.long.Long
时间类型进行标注。
(b)出参格式化
在上述示例中,调用接口的返回结果为:
"date": "2018-08-01T14:25:31.296+0000"
这个格式并不是我们想要的,那么如何将其进行格式化?这时就需要
用到 jackson 的 @JsonFormat 注解。
(i)改造 DateVo:
public class DateVo { @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") @JsonFormat( pattern = "yyyy-MM-dd HH:mm:ss" ) private Date date; public void setDate(Date date){ this.date = date; } public Date getDate(){ return date; } }
继续访问 /date/test ,并传入参数:2018-08-02 22:05:55,可以
看到接口返回的结果为:
"date": "2018-08-01 14:32:57"
虽然时间格式正确了,但实际上当前时间是
“2018-08-01 22:32:57” ,早了8个小时。因为,jackson在序列化
时间时是按照国际标准时间GMT进行格式化的,而在国内默认时
区使用的是CST时区,两者相差8小时。
所以,@JsonFormat 注解还要再加一个属性:
@JsonFormat( pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8" ) private Date date;
(ii)关于JsonFormat的说明
因为 @JsonFormat 注解不是 Spring 自带的注解,所以使用该注
解前需要添加 jackson 相关的依赖包。当然,如果是 SpringBoot
项目就不需要自己手动添加依赖了,因为在 spring-boot-start-web
下已经包含了 jackson 相关依赖。
三 MyBatis
1. #和$的区别
(1)区别介绍
#{ }:解析为一个 JDBC 预编译语句(prepared statement)的参数标记符。
例如,Mapper.xml中如下的 sql 语句:
select * from user where name = #{name};
动态解析为:
select * from user where name = ?;
一个 #{ } 被解析为一个参数占位符 ? 。而${ } 仅仅为一个纯碎的 string 替换,在动态 SQL 解析阶段将
会进行变量替换。例如,Mapper.xml中如下的 sql:
select * from user where name = ${name};
当我们传递的参数为 "Jack" 时,上述 sql 的解析为:
select * from user where name = "Jack";
(2)用法
(a)能用#尽量用#
(b)表名作为变量时,必须使用 ${ }
这是因为,表名是字符串,使用 sql 占位符替换字符串时会带上单引号 '',这会导致 sql 语法错误,
例如:
select * from #{tableName} where name = #{name};
预编译之后的sql 变为:
select * from ? where name = ?;
假设我们传入的参数为 tableName = "user" , name = "Jack",那么在占位符进行变量替换后,sql
语句变为:
select * from 'user' where name='Jack';
上述 sql 语句是存在语法错误的,表名不能加单引号 ''(注意,反引号 ``是可以的)。
(1)#
(a)#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型
转换,
(b)#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。
(c)如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称