菜鸟教程
http://www.runoob.com/w3cnote/basic-knowledge-summary-of-spring.html
http://qilixiang012.iteye.com/blog/2091295
框架
Spring
学习版本: 4.3.0
框架认识
框架学起来比较简单
但也有难的地方,难是难在底层原理
用了框架之后,写代码就非常简单,因为框架会完成一部分代码,
我们只要按框架的规则去使用就可以了
学习框架的方法:
与学习初级基础有所不同,
基础学习都是学习基本语法,完成简单的案例,逐步理解面向对象的编程思想.
框架属于中高级的学习,要运用面向对象的进行编程,接口编程
能够自已完成业务.熟悉业务.能够架构一个项目,属性框架的原理
框架学习程度:
相用框架
理解框架原理、走源码
自己编写框架部分
框架的由来
Md1
Md2
Jsp/servlet 的 mvc回顾
当一个方法中有部分代码在不断重复时,抽取出来做一个方法
当很多类在操作同一段代码时,抽出来做一个类
当很多类在做同一类事情的时候,抽出来做一个jar包或做成框架
这就是框架
框架,替程序员完成一部分代码,从而提高开发效率
框架就是一个模板,
Web应用的框架:webwork,jsf, struts1, struts2,springmvc
Mvc框架的工作
Servlet:
将页面请求映射到java类,也就是后台
接收并处理页面提交的数据
调用业务逻辑方法处理业务结果,将数据封装准备渲染到页面
控制页面跳转
Mvc的V层:
将页面请求映射到java类,也就是后台
获取并封装好页面提交的数据
渲染数据到页面
控制页面跳转
Spring下载
http://repo.springsource.org/libs-release-local/org/springframework/spring
向下拉选中需要下载的版本。
点击相应需要下载的zip,window版本直接下zip即可
Spring
Spring 是一个开源框架,Spring 是一个IOC(DI)和AOP容器框架.
特点:
1. 轻量级:spring是非侵入性的,基于spring开发的应用中的对象可以不依赖spring的API
2. 依赖注入(DI:dependency injection , IOC) :颠覆了传统的new对象的方式
3. 面向切面编程(AOP: aspect oriented programming):模块化横向关注点,代码更简洁
4. 容器:spring是一个容器,因为它包含并且管理应用对象的生命周期
5. 框架:spring实现了使用简单的组件配置组合成一个复杂的应用,在spring中可以使用xml和java注解组合这些对象
6. 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上spring自身也提供了展现层的spring mvc和持久层的spring JDBC)
Spring tool suite 安装
1. https://spring.io/tools/ggts/all
Spring tool suite 是一个eclipse插件,利用该插件可以更方便的在eclipse平台上开发基于spring的应用
直接下载包:根据eclipse的版本下载下应的工具包,将包下载好了之后可以安装到eclipse中
2. 安装到eclipse:
3. 选择IDE的选项目
安装完之后eclipse提示是否重启eclipse,选择yes,重启动就安装好了
检验是否安装好,查看到以下两个页面,就表示已安装成功,如若安装不成功可以按第一步开始重新安装.
重启eclipse后会有欢迎页面:
另,还可以到此处查看:
附:也可以利用资源路径,在线下载安装:复制url在eclipse中安装:安装方法: Eclipse的Help->Install New Software
|
搭建第一个spring开发环境
导入jar包:
commons-logging-1.1.3.jar spring-beans-4.3.0.RELEASE.jar spring-context-4.3.0.RELEASE.jar spring-core-4.3.0.RELEASE.jar spring-expression-4.3.0.RELEASE.jar |
配置文件:
一个典型的spring项目需要创建一个或多个bean配置文件,这些配置文件用于在spring ioc容器里配置bean,bean的配置文件可以放在classpath下,也可以放在其他目录下:applicationContext.xml
创建配置文件时可以选择spring进行搜索,找到spring bean configuration file ,这个选项就是前面装了spring tool suite的效果.有助于开发的方便与快捷.(当然也可以不装,直接手写配置文件的头文件内容.) |
<?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.xsd">
<!-- 配置了bean --> <bean id="helloWorld" class="com.yr.spt.HelloWorld"> <!-- <property name="name" value="工"></property> --> </bean> </beans> |
Java类:
package com.yr.spt;
public class HelloWorld { private String name; public void setName(String name) { //System.out.println(name); this.name = name; } public void hello(){ System.out.println(name); } //public HelloWorld(){ //System.out.println("构造方法"); //} } |
package com.yr.spt;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main { public static void main(String[] args) { /* * new的方式创建类(bean)运行 * HelloWorld h = new HelloWorld(); h.setName("aaa");*/ /** * spring 容器的方式注入bean(类) * 1,创建spring的ioc容器对象 * 2,从ioc容器中获取bean实例 */ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");//1 HelloWorld h = (HelloWorld)ctx.getBean("helloWorld");//2 h.setName("aaa"); h.hello(); } } |
ApplicationContext 做了什么
把HelloWorld 类中注释的代码解开
把配置文件applicationContext.xl中注释的内容解开
然后把Main 类改造成以下内容:
package com.yr.spt;
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main { public static void main(String[] args) { /* * new的方式创建类(bean)运行 * HelloWorld h = new HelloWorld(); h.setName("aaa");*/ /** * spring 容器的方式注入bean(类) * 1,创建spring的ioc容器对象 * 2,从ioc容器中获取bean实例 */ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");//1
} } |
运行后看到的结果是:
|
说明spring的ioc容器对象applicationContext调用了bean的构造方法和set方法
IOC和DI
IOC(Inversion of Control):其思想是反转资源获取的方向,传统的资源查找方式是要求
组件向容器发起请求查找资源, 容器适时的返回资源,而应用了IOC之后,则是容器主动的将资源推送给它所管理的组件,组件所要做的只是选择一种合适的方式来接受资源.这种行为也被称为查找的被动形式.
DI(Dependency Injection):是IOC的另一种表述方式,即组件以一些预先定义好的方式(如:setter方法)接受来自如容器的资源注入,相对于IOC而言,这种表述方式更直接
举例说明:
通过上面的例子可以看到,new的方式是很正常的一种做法,
而IOC容器的做法就不是,当需要将B中的a属性赋予A对象的实例时,IOC容器做好了
在spring ioc容器里配置bean
<!-- 配置bean --> <bean id="helloWorld" class="com.yr.spt.HelloWorld"> </bean> |
以bean节点的形式配置 id:bean的名字,在ioc容器中必须唯一,如果没有指定,spring自动将类名作为bean的名字,id可以指定多个名字,名字之间用逗号分开,或用空格分开 class:ioc容器是用反射的方式来创建bean,所以需要bean有无参的构造方法(不提供就是无参的) |
详解ApplicationContext
ApplicationContext代表spring ioc容器,在spring ioc容器读取bean配置、创建bean实例之前必须对它进行实例化,只有在容器实例化后,才可以从ioc容器里获取bean实例并使用,ApplicationContext在初始化上下文时就实例化所有的单例的bean
1. Spring 提供了两种类型的ioc容器实现
1) BeanFactory:ioc容器的基本实现,是spring框架的基础,面向spring本身
2) ApplicationContext提供了更多高级特性,是beanFactory的子接口.面向使用spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory,当然,这两种方式的配置文件是相同的
2. ApplicationContext的主要实现类:
1) 实现类:
i. ClassPathXmlApplicationContext:从类路径下加载配置文件
ii. FileSystemXmlApplicationContext从文件系统中加载配置文件
2) 子类
i. ConfigurableApplicationContext 继承了ApplicationContext,增加了两个主要的方法:refresh()和close(),让applicationContext具有启动和刷新和关闭上下文的能力.
ii. WebApplicationContext是专门为web应用而准备的,它允许从相对于web根目录的路径中完成初始化工作
3. 从ApplicationContext获取bean
在”搭建第一个spring开发环境”中就知道通过getBean方法来从ioc容器中获取bean实例,但是在ApplicationContext接口中并没有getBean方法,那么bean方法在ApplicationContext的父接口BeanFactory中.
注:通过getBean(“bean 的id”) 或getBean(类名.class)都可以,前者例子中已用到,表示通过ID获得配置好的bean的实例,后者表示是通过类类型获取,而这种方式需要IOC容器中(配置文件中只配一个)只有一个该类型的bean.
依赖注入的方式
Spring 支持3种依赖注入的方式
1. 属性注入(最常用的方式)
<!-- 配置了bean --> <bean id="helloWorld" class="com.yr.spt.HelloWorld"> <property name="name" value="工"></property> </bean> |
property 的属性name的值就是bean中的属性名,value就是为这个属性赋值 |
2. 构造器注入
<!-- 构造方式配置bean --> <bean id="user" class="com.yr.spt.User"> <constructor-arg value="zhangsan" index=”0” type=”java.lang.String”></constructor-arg> <constructor-arg value="10" index=”1” type=”java.lang.Integer”></constructor-arg> </bean> |
package com.yr.spt;
public class User { private String name; private Integer age; public User(String name,Integer age){ this.name = name; this.age = age; }
@Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } }
|
public static void main(String[] args) { User u = (User)ctx.getBean("user"); System.out.println(u.toString()); } 结果: User [name=zhangsan, age=10] |
使用构造器注入,可以使用 index和type属性来指定具体初始化的是哪一个属性,不提供index和type时是以顺序初始化属性的,但如果构造方法有多个时,而类型也有相同的时候就可以会混乱,所以结合index和type可以解决构造混乱的问题 |
3. 工厂方法注入(很少使用,不推荐,知道即可)
特殊字符值注入
如果节点值内容包含特殊字符,需要用<![CDATA[ ]]>包围
<!-- 构造方式配置bean --> <bean id="user" class="com.yr.spt.User"> <constructor-arg index="0" type=”java.lang.String”> <value><![CDATA[<!my126@126.com]]></value> </constructor-arg> <constructor-arg value="10" ></constructor-arg> <constructor-arg name="hw" ref="helloWorld" ></constructor-arg> </bean> |
人名: <!my126@126.com中有特殊字符>, 所以需要用<![CDATA[ ]]>包围,否则xml内容不正确 |
Bean相互引用
User:
package com.yr.spt;
public class User { private String name; private Integer age; public User(String name,Integer age){ this.name = name; this.age = age; }
@Override public String toString() { return "User [name=" + name + ", age=" + age + "]"; } } |
Order:
package com.yr.spt;
public class Order { private String orderName; private User user; public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Order [orderName=" + orderName + ", user=" + user + "]"; } }
|
配置文件: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.xsd"> <!-- 构造方式配置bean --> <bean id="user" class="com.yr.spt.User"> <constructor-arg value="zhangsan"></constructor-arg> <constructor-arg value="10" ></constructor-arg> </bean> <bean id="order" class="com.yr.spt.Order"> <property name="orderName" value="订单1"></property> <property name="user" ref="user"></property> </bean> </beans> |
ref可以关联到另一个bean |
使用:
public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Order o = (Order)ctx.getBean("order"); System.out.println(o.toString()); } |
Order [orderName=订单1, user=User [name=zhangsan, age=10]] |
List属性
在上面的例子中改造order
package com.yr.spt;
import java.util.List;
public class Order { private String orderName; private User user; private List<Pro> proList; public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public List<Pro> getProList() { return proList; } public void setProList(List<Pro> proList) { this.proList = proList; } @Override public String toString() { return "Order [orderName=" + orderName + ", user=" + user + ", proList=" + proList + "]"; } }
|
package com.yr.spt;
public class Pro { private String name; private String price; public Pro(String name,String price){ this.name = name; this.price = price; } @Override public String toString() { return "Pro [name=" + name + ", price=" + price + "]"; } }
|
配置文件中添加配置:
<bean id="order" class="com.yr.spt.Order"> <property name="orderName" value="订单1"></property> <property name="user" ref="user"></property> <property name="proList"> <list> <bean class="com.yr.spt.Pro"> <constructor-arg value="苹果"></constructor-arg> <constructor-arg value="2"></constructor-arg> </bean> <bean class="com.yr.spt.Pro"> <constructor-arg value="桃"></constructor-arg> <constructor-arg value="22"></constructor-arg> </bean> </list> </property> </bean> |
结果:
Order [orderName=订单1, user=User [name=zhangsan, age=10, hw=com.yr.spt.HelloWorld@5c5a1b69], proList=[Pro [name=苹果, price=2], Pro [name=桃, price=22]]] |
值可以正确设置
属性配置(p:)
首先导入p属性我标签:
|
User
package com.yr.spt;
public class User { private String name; private Integer age; private HelloWorld hw;
public void init() { System.out.println("init method..."); } public void destroy() { System.out.println("destroy method..."); }
public User() { System.out.println("user 构造方法"); }
public User(String name, Integer age) { this.name = name; this.age = age; }
public User(String name, Integer age, HelloWorld hw) { this.name = name; this.age = age; this.hw = hw; }
public String getName() { return name; }
public void setName(String name) { System.out.println("set name : "+name); this.name = name; }
public Integer getAge() { return age; }
public HelloWorld getHw() { return hw; }
public void setHw(HelloWorld hw) { this.hw = hw; }
@Override public String toString() { return "User [name=" + name + ", age=" + age + ", hw=" + hw + "]"; }
}
|
package com.yr.spt;
public class HelloWorld { private String name; public void setName(String name) { System.out.println(name); this.name = name; } public void hello(){ System.out.println(name); } public HelloWorld(){ System.out.println("构造方法"); } @Override public String toString() { return "HelloWorld [name=" + name + "]"; } }
|
添加配置文件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" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd"> <bean id="helloworld" class="com.yr.spt.HelloWorld" p:name="HelloWorld自动注入" scope="prototype"></bean> <bean id="userp" class="com.yr.spt.User" p:hw-ref="helloworld" p:name="myusername" ></bean>
</beans>
|
Main
public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); User o = (User)ctx.getBean("userp"); System.out.println(o.toString()); } |
效果:
User [name=myusername, age=null, hw=HelloWorld [name=HelloWorld]] |
属性自动配置
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd"> <bean id="hw" class="com.yr.spt.HelloWorld" p:name="HelloWorld"></bean> <bean id="user" class="com.yr.spt.User" autowire="byName" p:name="123"></bean>
</beans>
|
autowire="byName" 当autowire的值为byName时,表示User对象中的对象属性将以名称的方式自动注入,注入时到IOC容器中找与属性名相同的bean User对象中有一个hw, 这时配置文件中有一个bean也是hw并且也是HelloWorld对象,那么这就自注入了 |
User:
package com.yr.spt;
public class User { private String name; private Integer age; private HelloWorld hw;
public User() { }
public User(String name, Integer age) { this.name = name; this.age = age; }
public User(String name, Integer age, HelloWorld hw) { this.name = name; this.age = age; this.hw = hw; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public HelloWorld getHw() { return hw; }
public void setHw(HelloWorld hw) { this.hw = hw; }
@Override public String toString() { return "User [name=" + name + ", age=" + age + ", hw=" + hw + "]"; }
}
|
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-auto.xml"); User u = (User)ctx.getBean("user"); System.out.println(u.toString()); } |
效果:
User [name=123, age=null, hw=HelloWorld [name=HelloWorld自动注入]] |
继承注入
和类的继承概念一致
添加配置文件beans-extends.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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd"> <bean id="user1" class="com.yr.spt.User" p:age="12" p:name="name1" ></bean> <bean id="user2" class="com.yr.spt.User" parent="user1" ></bean> <!-- <bean id="user1" p:age="12" p:name="name1" abstract="true"></bean> <bean id="user2" class="com.yr.spt.User" parent="user1" ></bean> -->
</beans>
|
abstract="true"表示是抽象的,不能被实例化,当添加这个属性后,可以没有class parent="user1" 表示user2继承user1的属性并使用user1的值,与java中的继承是一个意思 |
Main:
public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-extends.xml");//1 User u = (User)ctx.getBean("user1"); System.out.println(u.toString()); User u1 = (User)ctx.getBean("user2"); System.out.println(u1.toString()); } |
效果:
User [name=name1, age=12, hw=null] -> user1 User [name=name1, age=12, hw=null] -> user2继承了user1,所以值一样 |
bean的作用域
在bean节点中还有一个属性叫scope,表示bean的作用域生成方式.
Spring ioc容器创建bean的实例时默认是单例的,也就是说容器中始终只有一个bean的实例.但是可以通过scope来改变创建方式和作用域
Prototype:表示多例,向容器中拿一次就创建一个新实例给获取者 Singleton:表示单例,容器中始终只有一个实例对象 Request:表示bean的作用域在request,(与struts2整合时用得上) Session:表示bean的作用域在session(很少用) |
配置数据源
导入jar包
c3p0-0.9.1.2.jar mysql-connector-java-5.1.39-bin.jar //数据库驱动 |
applicationContent.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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<!-- 引入外部配置文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean>
</beans>
|
db.properties
jdbc.user=root jdbc.password=root jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql:///mysqltest
jdbc.initPoolSize=5 jdbc.maxPoolSize=10
|
Main
public class Main { public static void main(String[] args) throws SQLException { /** * spring 容器的方式注入bean(类) * 1,创建spring的ioc容器对象 * 2,从ioc容器中获取bean实例 */ ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource ds = (DataSource)ctx.getBean("dataSource"); System.out.println(ds.getConnection());
} } |
效果:
|
Spel
了解即可
<!-- 测试 SpEL: 可以为属性进行动态的赋值(了解) --> <bean id="a" class="com.yr.spt.User"> <property name="userName" value="aaaa"></property> </bean> <bean id="b" class="com..yr.spt.User" > <property name="userName" value="bbbbbb"></property> <property name="aname" value="#{a.userName}"></property> </bean> |
Bean生命周期和后置处理器
后置处理器可以对bean的内容改造
配置文件:
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<bean id="users" class="com.yr.spt.User" init-method="init" destroy-method="destroy"> <property name="name" value="配置文件设置的名称"></property> </bean> <bean class="com.yr.spt.MyBeanPostProcessor" > </bean>
</beans>
|
Main
public static void main(String[] args) throws SQLException { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User)ctx.getBean("users"); System.out.println(user.getName()); ctx.close(); } |
User
package com.yr.spt;
public class User { private String name; private Integer age; private HelloWorld hw;
public void init() { System.out.println("init method..."); } public void destroy() { System.out.println("destroy method..."); }
public User() { System.out.println("user 构造方法"); }
public User(String name, Integer age) { this.name = name; this.age = age; }
public User(String name, Integer age, HelloWorld hw) { this.name = name; this.age = age; this.hw = hw; }
public String getName() { return name; }
public void setName(String name) { System.out.println("set name : "+name); this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public HelloWorld getHw() { return hw; }
public void setHw(HelloWorld hw) { this.hw = hw; }
@Override public String toString() { return "User [name=" + name + ", age=" + age + ", hw=" + hw + "]"; }
}
|
处理器
package com.yr.spt;
import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor { /** * 该方法在 init 方法之前被调用 * @param arg0: 实际要返回的对象 * @param arg1: bean 的 id 值 */ @Override public Object postProcessBeforeInitialization(Object arg0, String arg1) throws BeansException { if(arg1.equals("users")){ //看到效果后,把这里注释再看看效果 System.out.println("postProcessBeforeInitialization..." + arg0 + "," + arg1); } if (arg0 instanceof User) { User user = (User)arg0; System.out.println(user.getName()); } return arg0; }
//该方法在 init 方法之后被调用 @Override public Object postProcessAfterInitialization(Object arg0, String arg1) throws BeansException { if(arg1.equals("users")){//看到效果后,把这里注释再看看效果 System.out.println("postProcessAfterInitialization..." + arg0 + "," + arg1); } if (arg0 instanceof User) { User user = (User)arg0; System.out.println(user.getName()); user.setName("改了"); } return arg0; } }
|
效果
|
静态工厂方法
实例工厂方法
通过FactoryBean配置
在使用ioc中其他对象时,通过factoryBean配置bean是最好的
在项目中和其他技术整合时,配置的bean都是要通过factoryBean来注入和获取的
注解配置bean
Spring ioc提供了注解的方式配置Bean
@Component: 基本注解,标识了一个受spring管理的组件
@Repository:标识数据持久层
@Service标识业务层
@Controller标识表现层(视图层)
Spring IOC会自动到classpath中扫描组件,如未指定bean的名称,默认以类名首字母小写的全类名做为bean的名字,如要标明bean的名字,可以这样写,例:@Service(“userSer”).
配置好注解后,还需要在配置文件中配置被扫描的包,这样框架才会自动扫描
<context:component-scan base-package="要扫描的包路径" use-default-filters="是否扫描指定包路径下的全部有注解的类" >
在<context:component-scan 下还可以通过<context:exclude-filter> <context:include-filter>来进一步配置需要被扫描注入的bean
<!--use-default-filters=false表示不默认扫描指定包com.yr的全部--> <context:component-scan base-package="com.yr" use-default-filters="false" > <!-- <context:exclude-filter type="annotation" 只扫描@Controller注解expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" 只扫描@ControllerAdvice注解 expression="org.springframework.web.bind.annotation.ControllerAdvice"/> --> <context:include-filter type="assignable" <!--只扫描UserService--> expression="com.yr.spt.service.UserService"/> </context:component-scan> |
例:
package com.yr.spt;
import java.sql.SQLException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yr.spt.dao.UserDao; import com.yr.spt.service.UserService;
public class Main { public static void main(String[] args) throws SQLException {
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml"); UserService userService = (UserService)ctx.getBean("userService"); System.out.println(userService); UserDao userDao = (UserDao)ctx.getBean("userDao"); System.out.println(userDao); ctx.close(); } }
|
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<context:component-scan base-package="com.yr" use-default-filters="false" > <!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/> --> <context:include-filter type="assignable" expression="com.yr.spt.service.UserService"/> </context:component-scan> |
效果:
配置文件中只配置了扫描UserService那么 UserDao userDao = (UserDao)ctx.getBean("userDao"); System.out.println(userDao); 将会扫描不到,报异常: org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'userDao' is defined
|
Bean的自动装载
<context:component-scan>元素还有自动注册AutowiredAnnotationBeanPostProcessor实例,该例会自动装配使用了@Autowired和@Resource,@Inject的注解的属性. 用了自动装载之后,就可以直接使用bean属性,下现的例子展示了自动装载了bean之后,直接使用userService即可;不再需要使用:ctx.getBean("bean的id");的方式获取对象
@Controller public class UserController { @Autowired private UserService userService; //如果这里只用@Autowired,UserService的类上一定要标识被springioc容器管理 }
|
@Service //被spring ioc容器所管理 public class UserService { } |
@Autowired(required=false)
在使用@Autowired时需要注意,用@Autowired注解的属性所对应的对像一定要是在spring ioc容器中有的,否则,就会抛异常,说找到相应的bean名称.
如果不属性所对应的bean不允许被管理,这时如果还希望用@Autowired注解,那么就需要这样写:
@Controller public class UserController { @Autowired(required=false) private UserService userService; //这时添加了(required=false),那么UserService的类上可以不加容器的注解 }
|
@Qualifier("orderServiceImpl")
使用@Qualifier("orderServiceImpl")可以区分同一个接口下的不同的实现类的实例,如下例:IService有两个实现类,在controller中使用时,如果没有注解@ualifier无法明确知道iService是哪个类的例,代码:
|
明确了在controller所取的实例
Service接口和实现类
package com.yr.spt.service; public interface IService { void add(); }
|
package com.yr.spt.service;
import org.springframework.stereotype.Service;
@Service public class UserServiceImpl implements IService {
@Override public void add() { System.out.println("UserServiceImpl"); }
} |
package com.yr.spt.service;
import org.springframework.stereotype.Service;
@Service public class OrderServiceImpl implements IService{
@Override public void add() { System.out.println("OrderServiceImpl"); }
} |
Controller
package com.yr.spt.controller;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Controller;
import com.yr.spt.service.IService; import com.yr.spt.service.UserServiceImpl;
@Controller public class UserController { @Autowired(required=false) private UserServiceImpl userServiceImpl; @Autowired @Qualifier("orderServiceImpl") private IService iService; public void a(){ iService.add(); } }
|
配置文件
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<context:component-scan base-package="com.yr.spt" > </beans> |
Main
package com.yr.spt;
import java.sql.SQLException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yr.spt.controller.UserController;
public class Main { public static void main(String[] args) throws SQLException { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserController u = (UserController)ctx.getBean("userController"); u.a(); ctx.close(); } } |
泛型依赖注入
Spring 4.x中可以在子类中为子类注入其对应的泛型父类的变量引用
父类是泛型,没有注解spring ioc容器管理,但是其子类有被spring ioc管理,那么会因为被注入的子类所对应的泛型而将父类的其他子类,但是为同一泛型类的子类注入到父类的引用中
父类
package com.yr.spt.di;
public class BaseDao<T> {
}
|
package com.yr.spt.di;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseService<T> { @Autowired private BaseDao<T> baseDao; public void add(){ System.out.println("baseService<T> " + baseDao); } }
|
子类
package com.yr.spt.di;
import org.springframework.stereotype.Repository;
import com.yr.spt.User;
@Repository public class User1Dao extends BaseDao<User>{
} |
package com.yr.spt.di;
import org.springframework.stereotype.Service;
import com.yr.spt.User;
@Service public class UserService extends BaseService<User>{
}
|
Main
package com.yr.spt;
import java.sql.SQLException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.yr.spt.controller.UserController;
public class Main { public static void main(String[] args) throws SQLException { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); UserService us = (UserService)ctx.getBean("userService"); us.add(); ctx.close(); } } |
效果:
baseService<T> com.yr.spt.di.User1Dao@32a068d1 |
两个父类并没有注解,只有子类注解了,因为userService被装载了,并userService的泛型类为<User> ,则在装栽BaseDao<T>时,自动根据泛型User类将BaseDao<T>的User1Dao<User>注入到BaseService<T>类中的baseDao的属性中. |
AOP
动态代理
(动态代理的实现比较麻烦,要求高)
写一个方法实现+-*/这个功能,并用需要在运算过程中打出计算前后(也就是过程)的日志.用动态代理来实现
接口
package com.yr.spt.aop;
public interface ArithmeticCalculator {
int add(int i, int j); int sub(int i, int j); int mul(int i, int j); int div(int i, int j); }
|
实现类:
package com.yr.spt.aop;
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {
@Override public int add(int i, int j) { int result = i + j; return result; }
@Override public int sub(int i, int j) { int result = i - j; return result; }
@Override public int mul(int i, int j) { int result = i * j; return result; }
@Override public int div(int i, int j) { int result = i / j; return result; } }
|
日志代理类(重点了解)
package com.yr.spt.aop;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays;
public class ArithmeticCalculatorLoggingProxy { //要代理的对象 private ArithmeticCalculator target; public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) { super(); this.target = target; }
//返回代理对象 public ArithmeticCalculator getLoggingProxy(){ ArithmeticCalculator proxy = null; /* * 因为这个对象不是new出来的,而是代理出来的,那么需要告诉虚拟机,由谁来负责加载 * 这里由ClassLoader来加载 */ ClassLoader loader = target.getClass().getClassLoader(); //代理对象的类型,即需要代理谁,这里需要代理ArithmeticCalculator Class [] interfaces = new Class[]{ArithmeticCalculator.class}; //当调用代理对象的方法时,该执行被代理对象中的那些代码 InvocationHandler h = new InvocationHandler() { /** * proxy: 代理对象。 一般不使用该对象,当调用代理对象的方法时会调用invoke方法,然后又调用被调用的方法,如此会死循环 * method: 正在被调用的方法 * args: 调用方法传入的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); //打印日志 System.out.println("[before] The method " + methodName + " begins with " + Arrays.asList(args)); //调用目标方法 Object result = null; try { //前置通知 result = method.invoke(target, args); //返回通知, 可以访问到方法的返回值 } catch (NullPointerException e) { e.printStackTrace(); //异常通知, 可以访问到方法出现的异常 } //后置通知. 因为方法可以能会出异常, 所以访问不到方法的返回值 //打印日志 System.out.println("[after] The method ends with " + result); return result; } }; /** * loader: 代理对象使用的类加载器。 * interfaces: 指定代理对象的类型. 即代理代理对象中可以有哪些方法. * h: 当具体调用代理对象的方法时, 应该如何进行响应, 实际上就是调用 InvocationHandler 的 invoke 方法 */ proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h); return proxy; } }
|
Main
package com.yr.spt.aop;
public class Main { public static void main(String[] args) { ArithmeticCalculator arithmeticCalculator = new ArithmeticCalculatorImpl(); arithmeticCalculator = new ArithmeticCalculatorLoggingProxy(arithmeticCalculator).getLoggingProxy(); int result = arithmeticCalculator.add(11, 12); System.out.println("result:" + result); result = arithmeticCalculator.div(21, 3); System.out.println("result:" + result); } }
|
效果
[before] The method add begins with [11, 12] [after] The method ends with 23 result:23 [before] The method div begins with [21, 3] [after] The method ends with 7 result:7
|
Spring AOP
AOP(Aspect Oriented Programming)面向切面编程是一种新的方法论,是对OOP(Object Oriented Programming)面向对象编程的补充.
AOP主要编程对象是切面(aspect),重点横切关注点.在应用AOP编程时,仍然需要定义公共功能,但可以明确定义这个功能在哪里,以什么方式应用,并且不必修改受影响的类(被切的类),这样横切关注点就能被模块化到特殊的对象(切面)里.
AOP好处
1. 每个事物逻辑位于一个位置,代码不分散,便于维护和升级
2. 业务模块更监简洁,只包含核心业务代码
ü 非AOP实现
对于上面的例子,如果也不使用动态代理的方式,那么需要记录计算的过程,则需要这样实现:
在每个方法中的业务(计算结果)代码前后都需要加上打应日志的代码,而且代码都是相同的功能
ü AOP实现
将验证和日志相关代码提出来.其中这些验证,日志等等其他这些与业务无关的但又参与在业务过程中的一个个需求就称为横切关注点,这些横切关注点就叫切面.把这些关注点抽取出来放在一起就叫抽取横切关注点.关注点提出来之后,与业务逻辑就分开了,当关注点与业务逻辑组合在一起运行的这个过程就叫面向切面编程(抽取横切关注点是为了编程的方便,而运行的过程其实还是融合在一起)
AOP术语
1. 切面(aspect):横切关注点被模块代的特殊对象,比如验证,日志都叫横切关注点,当验证和日志被模块化之后形成的类就叫切面
2. 通知(advice):切面必须要完成的工作,也就是切面(类)里的每一个方法都叫通知
3. 目标(target):被通知的对象,与就是业务,在上面的例子中,+-*/这4个方法或这4个方法所在的类都叫目标
4. 代理(proxy):向目标对象应用通之后创建的对象,也就是由谁去通知目标方法.上面的例子中的代码就是这个概念
5. 连接点(joinpoint):程序执行的某个特定位置,是一个物理存在.比如类的方法调用前和调用后,抛异常后等的位置.
a) 连接点的两个信息决定
i. 方法表示的的程序执行点
ii. 相对点表示的方位
如:ArithmeticCalculator.add()方法执行前的连接点是:执行点为ArithmeticCalculator.add()方法,方位为该方法执行前的位置
System.out.println(“这一行代码这个位置就是方位”); result = method.invoke(target, args); //执行方法 |
6. 切点(pointcut):客观来讲连接点看得到摸得到,切点看不到摸不到,每个类都有多个连接点,如类ArithmeticCalculator所有的方法实际上就是连接点,连接点就是类中客观存在的事务,AOP通过切点定位到特定的连接点.类比:连接点相当于数据库中表的一条条记录,客观存在的,而切点相当于查询条件,通过查询条件获取记录.切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的定位条件.
AspectJ
通知
aspectJ是java社区里最完整最流程的AOP框架,在spring 2.0+版本中,可以使用基于AspectJ注解或基于xml配置的AOP
Spring自己也有实现AOP框架,但AspectJ更好用
1. 前置通知(@Before):在目标方法执行之前执行
2. 后置通知(@After):在目标方法之后执行,无论是否抛异常,不可以访问到目标方法的执行结果
3. 返回通知(@AfterReturning):在方法法正常结束受执行,可以访问到目标方法执行的结果
4. 异常通知:执行目标方法抛异常时执行,并可以指定异常类型,表示抛指定的异常才执行此通知,不抛异常或不抛指定异常不通知
5. 环绕通知:环绕着方法执行.环绕通知需要携带 ProceedingJoinPoint 类型的参数.环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法.且环绕通知必须有返回值, 返回值即为目标方法的返回值
导包:
com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar commons-logging-1.1.3.jar spring-aop-4.3.0.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar spring-beans-4.3.0.RELEASE.jar spring-context-4.3.0.RELEASE.jar spring-core-4.3.0.RELEASE.jar spring-expression-4.3.0.RELEASE.jar |
配置文件
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<context:component-scan base-package="com.yr.spt" > </context:component-scan> <!-- 使 AspectJ 的注解起作用 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans> |
编写切面(类)
// @Order(2) //优先级, @Aspect //通过添加 @Aspect 注解声明这个 bean 是一个切面 @Component //切面必须是 IOC 中的 bean: 实际添加了 @Component 注解 public class LoggingAspect {
/* //前置通知: @Before("execution(public int com.atguigu.spring.aop.ArithmeticCalculator.*(int, int))") @Before 表示在目标方法执行之前执行 @Before(这里为切入点的表达式) execution(修饰符 返回类型 com.yr.spt.aop.*.*(int, int)) *在相应的位置上表示表示任意类型,如修饰符用*代替所有类型,也可以写public,表示public的方法会被切面并进前置通知 */ @Before("execution(public int com.yr.spt.aop.ArithmeticCalculator.*(int, int))") public void beforeMethod(JoinPoint joinPoint){ //在通知中访问连接细节: 可以在通知方法中添加 JoinPoint 类型的参数, 从中可以访问到方法的签名和方法的参数 String methodName = joinPoint.getSignature().getName(); Object [] args = joinPoint.getArgs(); System.out.println("[切面 @Before]The method " + methodName + " begins with " + Arrays.asList(args)); } /** * @After 表示后置通知: 在方法执行之后(无论是否抛异常)执行的代码. 注:在后置方法中还不能访问目标方法的结果 * @param joinPoint */ @After("execution(* com.yr.spt.aop.*.*(..))") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @After]The method " + methodName + " ends"); }
/** @AfterReturning 返回通知 * 在方法法正常结束受执行的代码 * 返回通知是可以访问到方法的返回值的! */ @AfterReturning(value="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))", returning="result") public void afterReturning(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @AfterReturning]The method " + methodName + " ends with " + result); }
/** * 在目标方法出现异常时会执行的代码. * 可以访问到异常对象; 且可以指定在出现特定异常时再执行通知代码 */ @AfterThrowing(value="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))", throwing="e") public void afterThrowing(JoinPoint joinPoint, Exception e){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @AfterThrowing]The method " + methodName + " occurs excetion:" + e); } /** * 在目标方法出现异常时会执行的代码. * 可以访问到异常对象; 在抛null异常时才会执行此通知代码 */ @AfterThrowing(value="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))", throwing="e") public void afterThrowingNull(JoinPoint joinPoint, NullPointerException e){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @AfterThrowing]The method " + methodName + " occurs excetion:" + e); }
/** * 环绕通知需要携带 ProceedingJoinPoint 类型的参数. * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法. * 且环绕通知必须有返回值, 返回值即为目标方法的返回值 */ @Around("execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))") public Object aroundMethod(ProceedingJoinPoint pjd){ Object result = null; String methodName = pjd.getSignature().getName(); try { //前置通知 System.out.println("[切面 @Around]The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs())); //执行目标方法 result = pjd.proceed(); //返回通知 System.out.println("[切面 @Around]The method " + methodName + " ends with " + result); } catch (Throwable e) { //异常通知 System.out.println("[切面 @Around]The method " + methodName + " occurs exception:" + e); throw new RuntimeException(e); } //后置通知 System.out.println("[切面 @Around]The method " + methodName + " ends"); return result; } } |
Main
package com.yr.spt.aop;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator"); System.out.println(arithmeticCalculator.getClass().getName()); int result = arithmeticCalculator.add(8, 9); System.out.println("result:" + result); result = arithmeticCalculator.div(21, 3); System.out.println("result:" + result); ctx.close(); } } |
效果:
|
通过AOP的实现,目标方法中没有任何体现,而功能却实现了
切入点
切入点表达式(@Pointcut):使用 @Pointcut 来声明切入点表达式,该方法中再不需要添入其他的代码,其他通知直接使用方法名来引用当前的切入点表达式
这样可以实现切面的切入点的重用
把上面切面改为:
package com.yr.spt.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component;
//通过添加 @Aspect 注解声明一个 bean 是一个切面! @Aspect @Component public class LoggingAspect { /** * 定义一个方法, 用于声明切入点表达式. 一般地, 该方法中再不需要添入其他的代码. * 使用 @Pointcut 来声明切入点表达式. * 后面的其他通知直接使用方法名来引用当前的切入点表达式. */ @Pointcut("execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))") public void declareJointPointExpression(){} //execution(修饰符 返回类型* com.yr.spt.aop.*.*(int, int)) *表示任意类型 //@Before("execution(public int com.yr.spt.aop.ArithmeticCalculator.*(int, int))") @Before("declareJointPointExpression()") public void beforeMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); Object [] args = joinPoint.getArgs(); System.out.println("[切面 @Before]The method " + methodName + " begins with " + Arrays.asList(args)); } /** * @After 表示后置通知: 在方法执行之后(无论是否抛异常)执行的代码. 注:在后置方法中还不能访问目标方法的结果 * @param joinPoint */ //@After("execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))") @After("declareJointPointExpression()") public void afterMethod(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @After]The method " + methodName + " ends"); } /** * 在方法法正常结束受执行的代码 * 返回通知是可以访问到方法的返回值的! */ //@AfterReturning(value="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))", //returning="result") @AfterReturning(value="declareJointPointExpression()", returning="result") public void afterReturning(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @AfterReturning]The method " + methodName + " ends with " + result); } /** * 在目标方法出现异常时会执行的代码. * 可以访问到异常对象; 且可以指定在出现特定异常时再执行通知代码 */ //@AfterThrowing(value="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))", //throwing="e") @AfterThrowing(value="declareJointPointExpression()", throwing="e") public void afterThrowing(JoinPoint joinPoint, Exception e){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @AfterThrowing]The method " + methodName + " occurs excetion:" + e); } /** * 在目标方法出现异常时会执行的代码. * 可以访问到异常对象; 在抛null异常时才会执行此通知代码 */ /*@AfterThrowing(value="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))", throwing="e")*/ @AfterThrowing(value="declareJointPointExpression()", throwing="e") public void afterThrowingNull(JoinPoint joinPoint, NullPointerException e){ String methodName = joinPoint.getSignature().getName(); System.out.println("[切面 @AfterThrowing]The method " + methodName + " occurs excetion:" + e); } /** * 环绕通知需要携带 ProceedingJoinPoint 类型的参数. * 环绕通知类似于动态代理的全过程: ProceedingJoinPoint 类型的参数可以决定是否执行目标方法. * 且环绕通知必须有返回值, 返回值即为目标方法的返回值 */ //@Around("execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))") @Around("declareJointPointExpression()") public Object aroundMethod(ProceedingJoinPoint pjd){ Object result = null; String methodName = pjd.getSignature().getName(); try { //前置通知 System.out.println("[切面 @Around]The method " + methodName + " begins with " + Arrays.asList(pjd.getArgs())); //执行目标方法 result = pjd.proceed(); //返回通知 System.out.println("[切面 @Around]The method " + methodName + " ends with " + result); } catch (Throwable e) { //异常通知 System.out.println("[切面 @Around]The method " + methodName + " occurs exception:" + e); throw new RuntimeException(e); } //后置通知 System.out.println("[切面 @Around]The method " + methodName + " ends"); return result; } }
|
切入点的优先级
@Order用来标识切面面的优先级.数字越小优先级越高
再添加一个其切面
package com.yr.spt.aop;
import java.util.Arrays;
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component;
@Order(1) @Aspect @Component public class VlidationAspect { /** 重用了LoggingAspect类中declareJointPointExpression()的切面通知表达式 */ @Before("com.yr.spt.aop.LoggingAspect.declareJointPointExpression()") public void validateArgs(JoinPoint joinPoint){ System.out.println("-->validate:" + Arrays.asList(joinPoint.getArgs())); } }
|
效果:
|
配置文件配置AOP
在配置文件中添加以下内容,并把LoggingAspect和VlidationAspect类中的注解全部去掉
<!-- AOP 配置目标bean 和 切面bean --> <bean id="arithmeticCalculatorImpl" class="com.yr.spt.aop.ArithmeticCalculatorImpl"></bean> <bean id="loggingAspect" class="com.yr.spt.aop.LoggingAspect"></bean> <bean id="vlidationAspect" class="com.yr.spt.aop.VlidationAspect"></bean> <!-- AOP 配置通知 --> <aop:config> <aop:pointcut expression="execution(* com.yr.spt.aop.ArithmeticCalculator.*(..))" id="pointcut"/> <aop:aspect ref="loggingAspect" order="1"> <aop:before method="beforeMethod" pointcut-ref="pointcut"/> <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="e"/> <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/> <aop:around method="aroundMethod" pointcut-ref="pointcut"/> </aop:aspect> <aop:aspect ref="vlidationAspect" order="2"> <aop:before method="validateArgs" pointcut-ref="pointcut"/> </aop:aspect> </aop:config> |
Spring对JDBC的支持
配置数据源在前面章节已配置这里拿过来用即可
为了使jdbc更加易于使用,spring 在jdbc api上定义了一个抽象层,以此建立一个jdbc存取框架.作为spring jdbc框架的核心,JdbcTemplate类的设计目的是为不同类型的jdbc操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式,可以在尽可能保留灵活性的情况下,将数据库存取的工作量降到最底.
Spring JDBC抽象框架完成了以下工作:
1. 指定数据库连接参数
2. 打开数据库连接
3. 声明SQL语句
4. 预编译并执行SQL语句
5. 遍历查询结果(如果需要的话)
6. 处理每一次遍历操作
7. 处理抛出的任何异常
8. 处理事务
9. 关闭数据库连接
Spring JDBC抽象框架由四个包构成:core、 dataSource、object以及support
1. org.springframework.jdbc.core包由JdbcTemplate类以及相关的回调接口(callback interface)和类组成。
2. org.springframework.jdbc.datasource包由一些用来简化DataSource访问的工具类,以及各种DataSource接口的简单实现(主要用于单元测试以及在J2EE容器之外使用JDBC)组成。工具类提供了一些静态方法,诸如通过JNDI获取数据连接以及在必要的情况下关闭这些连接。它支持绑定线程的连接,比如被用于DataSourceTransactionManager的连接。
3. org.springframework.jdbc.object包由封装了查询、更新以及存储过程的类组成,这些类的对象都是线程安全并且可重复使用的。它们类似于JDO,与JDO的不同之处在于查询结果与数据库是“断开连接”的。它们是在org.springframework.jdbc.core包的基础上对JDBC更高层次的抽象。
4. org.springframework.jdbc.support包提供了一些SQLException的转换类以及相关的工具类。
在JDBC处理过程中抛出的异常将被转换成org.springframework.dao包中定义的异常。因此使用Spring JDBC进行开发将不需要处理
JdbcTemplate类
JdbcTemplate是core包的核心类。它替我们完成了资源的创建以及释放工作,从而简化了我们对JDBC的使用。它还可以帮助我们避免一些常见的错误,比如忘记关闭数据库连接。JdbcTemplate将完成JDBC核心处理流程,比如SQL语句的创建、执行,而把SQL语句的生成以及查询结果的提取工作留给我们的应用代码。
NamedParameterJdbcTemplate类
NamedParameterJdbcTemplate类增加了在SQL语句中使用命名参数的支持。在此之前,在传统的SQL语句中,参数都是用'?'占位符来表示的。 NamedParameterJdbcTemplate类内部封装了一个普通的JdbcTemplate,并作为其代理来完成大部分工作。下面的内容主要针对NamedParameterJdbcTemplate与JdbcTemplate的不同之处来加以说明,即如何在SQL语句中使用命名参数。
JdbcDaoSupport类
继承JdbcDaoSupport类也可以操作数据源,但是需要手动注入值(即便交给容器管理,也需要手动配置),并且JdbcDaoSupport类中提供的配置数据源的方法setDataSource是final修饰的,不能重写.这里提供一种方式如下.在下述的案例中可以看到效果
@Repository public class DepartmentDao extends JdbcDaoSupport{
@Autowired public void setDataSource2(DataSource dataSource){ setDataSource(dataSource); }
public Department get(Integer id){ String sql = "SELECT id, dept_name name FROM departments WHERE id = ?"; RowMapper<Department> rowMapper = new BeanPropertyRowMapper<>(Department.class); return getJdbcTemplate().queryForObject(sql, rowMapper, id); } } |
案例
创建表(id自增长):
Departments(id,dept_name)
Employees(id,last_name,email,dept_id)
继续导入jar包
spring-jdbc-4.0.0.RELEASE.jar spring-orm-4.0.0.RELEASE.jar spring-tx-4.0.0.RELEASE.jar hamcrest-all-1.3.jar junit-4.12.jar |
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" xmlns:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">
<context:component-scan base-package="com.yr.spt" > </context:component-scan> <!-- 数据源 --> <!-- 引入外部配置文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!-- 配置 Spirng 的 JdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置 NamedParameterJdbcTemplate, 该对象可以使用具名参数, 其没有无参数的构造器, 所以必须为其构造器指定参数 --> <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg ref="dataSource"></constructor-arg> </bean> </beans> |
JDBCTest
package com.yr.spt.jdbcTemp.jdbc;
import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map;
import javax.sql.DataSource;
import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource;
public class JDBCTest {
private ApplicationContext ctx = null; private JdbcTemplate jdbcTemplate; private EmployeeDao employeeDao; private DepartmentDao departmentDao; private NamedParameterJdbcTemplate namedParameterJdbcTemplate; { ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate"); employeeDao = ctx.getBean(EmployeeDao.class); departmentDao = ctx.getBean(DepartmentDao.class); namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class); }
/** * 使用具名参数时, 可以使用 update(String sql, SqlParameterSource paramSource) 方法进行更新操作 * 1. SQL 语句中的参数名和类的属性一致! 2. 使用 SqlParameterSource 的 * BeanPropertySqlParameterSource 实现类作为参数. */ @Test public void testNamedParameterJdbcTemplate2() { String sql = "INSERT INTO employees(last_name, email, dept_id) " + "VALUES(:lastName,:email,:dpetId)";
Employee employee = new Employee(); employee.setLastName("XYZ"); employee.setEmail("xyz@sina.com"); employee.setDpetId(3);
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(employee); namedParameterJdbcTemplate.update(sql, paramSource); }
/** * 使用具名参数 * 可以为参数起名字. 1. 好处: 若有多个参数, 则不用再去对应位置, 直接对应参数名, 便于维护 2. 缺点: 较为麻烦. */ @Test public void testNamedParameterJdbcTemplate() { String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(:ln,:email,:deptid)";
Map<String, Object> paramMap = new HashMap<>(); paramMap.put("ln", "FF"); paramMap.put("email", "ff@atguigu.com"); paramMap.put("deptid", 2);
namedParameterJdbcTemplate.update(sql, paramMap); }
@Test public void testDepartmentDao() { System.out.println(departmentDao.get(1)); }
@Test public void testEmployeeDao() { System.out.println(employeeDao.get(1)); }
/** * 获取单个列的值, 或做统计查询 使用 queryForObject(String sql, Class<Long> requiredType) */ @Test public void testQueryForObject2() { String sql = "SELECT count(id) FROM employees"; long count = jdbcTemplate.queryForObject(sql, Long.class);
System.out.println(count); }
/** * 查到实体类的集合 注意调用的不是 queryForList 方法 */ @Test public void testQueryForList() { String sql = "SELECT id, last_name lastName, email FROM employees WHERE id > ?"; RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class); List<Employee> employees = jdbcTemplate.query(sql, rowMapper, 5); System.out.println(employees); }
/** * 从数据库中获取一条记录, 实际得到对应的一个对象 注意不是调用 queryForObject(String sql, Class * <Employee> requiredType, Object... args) 方法! 而需要调用 queryForObject(String * sql, RowMapper<Employee> rowMapper, Object... args) 1. 其中的 RowMapper * 指定如何去映射结果集的行, 常用的实现类为 BeanPropertyRowMapper 2. 使用 SQL 中列的别名完成列名和类的属性名的映射. * 例如 last_name lastName * 3. 不支持级联属性. JdbcTemplate 到底是一个 JDBC 的小工具, 而不是 ORM * 框架,所以sql 中的dept_id as \"department.id\" 无法将employees表中的dept_id映射到department对象中,在这里没有效果 */ @Test public void testQueryForObject() { String sql = "SELECT id, last_name lastName, email, dept_id as \"department.id\" FROM employees WHERE id = ?"; RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class); Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, 1);
System.out.println(employee); }
/** * 执行批量更新: 批量的 INSERT, UPDATE, DELETE 最后一个参数是 Object[] 的 List 类型: * 因为修改一条记录需要一个 Object 的数组, 那么多条不就需要多个 Object 的数组吗 */ @Test public void testBatchUpdate() { String sql = "INSERT INTO employees(last_name, email, dept_id) VALUES(?,?,?)";
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[] { "AA", "aa@atguigu.com", 1 }); batchArgs.add(new Object[] { "BB", "bb@atguigu.com", 2 }); batchArgs.add(new Object[] { "CC", "cc@atguigu.com", 3 }); batchArgs.add(new Object[] { "DD", "dd@atguigu.com", 3 }); batchArgs.add(new Object[] { "EE", "ee@atguigu.com", 2 });
jdbcTemplate.batchUpdate(sql, batchArgs); }
/** * 执行 INSERT, UPDATE, DELETE */ @Test public void testUpdate() { String sql = "UPDATE employees SET last_name = ? WHERE id = ?"; jdbcTemplate.update(sql, "Jack", 5); }
@Test public void testDataSource() throws SQLException { DataSource dataSource = ctx.getBean(DataSource.class); System.out.println(dataSource.getConnection()); }
}
|
Department
package com.yr.spt.jdbcTemp.jdbc;
public class Department {
private Integer id; private String name;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "Department [id=" + id + ", name=" + name + "]"; }
}
|
Employee
package com.yr.spt.jdbcTemp.jdbc;
public class Employee { private Integer id; private String lastName; private String email; private Integer dpetId;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getLastName() { return lastName; }
public void setLastName(String lastName) { this.lastName = lastName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public Integer getDpetId() { return dpetId; }
public void setDpetId(Integer dpetId) { this.dpetId = dpetId; }
@Override public String toString() { return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", dpetId=" + dpetId + "]"; }
}
|
EmployeeDao
package com.yr.spt.jdbcTemp.jdbc;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowMapper; import org.springframework.stereotype.Repository;
@Repository public class EmployeeDao { @Autowired private JdbcTemplate jdbcTemplate; public Employee get(Integer id){ String sql = "SELECT id, last_name lastName, email FROM employees WHERE id = ?"; RowMapper<Employee> rowMapper = new BeanPropertyRowMapper<>(Employee.class); Employee employee = jdbcTemplate.queryForObject(sql, rowMapper, id); return employee; } }
|
DepartmentDao
package com.yr.spt.jdbcTemp.jdbc;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.stereotype.Repository;
/** * 不推荐使用 JdbcDaoSupport, 而推荐直接使用 JdbcTempate 作为 Dao 类的成员变量 */ @Repository public class DepartmentDao extends JdbcDaoSupport{
@Autowired public void setDataSource2(DataSource dataSource){ setDataSource(dataSource); }
public Department get(Integer id){ String sql = "SELECT id, dept_name name FROM departments WHERE id = ?"; RowMapper<Department> rowMapper = new BeanPropertyRowMapper<>(Department.class); return getJdbcTemplate().queryForObject(sql, rowMapper, id); } } |