spring是分层的,一站式的轻量级开源架构
JavaEE分层
一站式
Spring提供了JavaEE各层的解决方案:
表现层:struts1、struts2、Spring MVC
业务层:Ioc、AOP、事务控制
持久层:JdbcTemplate、HibernateTemplate、ORM框架(对象关系映射)整合
轻量级
Spring的出现取代了EJB的臃肿、低效、繁琐复杂、脱离现实的情况. 而且使用spring编程是非侵入式的。
1Spring项目的核心容器的最基本Jar包(4个):
2.Spring框架所需的日志包(2个,依赖jar库中找):
默认采用apache commons-logging(JCL)日志框架+log4j的日志实现,还需要添加log4j的配置文件。
3.添加log4j.properties文件放置到src下。
### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=c\:mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug' ###
log4j.rootLogger=info, stdout
public class UserServiceImpl implements IUserService{
public void login() {
System.out.println("UserServiceImpl-service层被调用了。。。");
//实例化dao层
//传统方式
IUserDao userDao = new UserDaoImpl();
userDao.findByUsernameAndPassword();
}
}
public class UserDaoImpl implements IUserDao {
@Override
public void findByUsernameAndPassword() {
System.out.println("UserDaoImpl-dao层被调用了");
}
}
存在问题:代码过于耦合,上层代码过度依赖于下一层代码的实现:
UserDao userDao = new UserDaoImpl();
如果要更换实现类,或者实现类换一个名字,此时代码会报错,必须要修改原来的业务代码!
怎么解决类与类之间如此密切的耦合问题呢?
采用IoC(Inverse of Control,控制反转)的思想解决代码耦合问题。
简单的说就是引入工厂(第三者),将原来在程序中手动创建管理的依赖的UserDaoImpl对象,交给工厂来创建管理。
public class UserServiceImpl implements IUserService{
public void login() {
System.out.println("UserServiceImpl-service层方法调用了");
//ioc方式:
//创建工厂,利用工厂提供依赖的对象
UserDAOFactory userDAOFactory = new UserDAOFactory();
UserDAOImpl userDAO = userDAOFactory.getUserDAO();
userDAO.findUserByUsernameAndPassword();
}
}
public class UserDAOFactory {
//提供获取对象的方法
public UserDAOImpl getUserDAO(){
//返回实例对象
return new UserDAOImpl ();
}
}
发现问题:工厂方法仍然需要返回具体类型的实例对象,存在代码耦合
解决方案:使用反射技术传入具体类型的类字符串生产对象的实例:
public Object getBean(){
Object bean = null;
try {
//传入类字符串,生产对象实例
bean = Class.forName("cn.itcast.spring.a_quickstart.UserDAOImpl").newInstance();
} catch (Exception e) {
e.printStackTrace();
}
//返回具体类型的对象类型实例
return bean;
}
//使用反射方法获取对象
IUserDAO userDAO = (IUserDAO) userDAOFactory.getBean();
userDAO.findUserByUsernameAndPassword();
发现问题:类字符串是固定的,怎么动态的传入不同的类字符串呢?
解决方案: 使用xml配置文件动态传入类字符串
IoC底层实现:工厂(设计模式)+反射(机制) + 配置文件(xml)。
IoC控制反转的理解和实现
1.在src下建立applicationContext.xml (位置:applicationContext.xml文件放置到任何目录都可以,习惯上放在src目录或者 WEB-INF目录)
2.参考规范文档配置xml的头信息:bean schema
3.配置本地提示:(联网的情况下会Myeclipse会自动下载关联相关约束文件)
4.配置applicationContext.xml:
在程序中创建spring工厂对象, 通过工厂对象加载spring的xml配置文件,生产配置文件中配置 的bean对应的对象
//加载Spring配置文件,创建Spring工厂对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//从spring工厂中,通过bean的id/name获取对象
IUserDao userDao = (IUserDao)ac.getBean("userDao");
userDao.findByUsernameAndPassword();
因为applicationContext.xml在src目录下所以可以直接用过文件夹获取
发现问题:该方式虽然解决了类与类之间的耦合关系,但却需要在获取对象的时候创建spring工厂,有没有更方便获取对象的依赖的方法呢?
DI:Dependency Injection 依赖注入,在Spring框架负责创建Bean对象时,动态的将依赖对象注入到Bean组件(简单的说,可以将另外一个bean对象动态的注入到另外一个bean中。)
Di的做法是:由Spring容器创建了Service、Dao对象,并且在配置中将Dao传入Servcie,那么Service对象就包含了Dao对象的引用。
1.将service对象也交给spring容器管理
2.在程序中定义属性提供setter方法:
public class UserServiceImpl implements IUserService {
private IUserDao userDao;
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
public void login() {
System.out.println("UserServiceImpl-service层方法调用了");
//ioc:依赖注入
userDao.findByUsernameAndPassword();
}
}
3.测试运行,此时获取对象必须从spring工厂获取(在spring容器配置中才有依赖注入,自己创建的对象没有注入依赖关系)
public class SpringTest {
//测试
@Test
public void test(){
//创建spring工厂,获取spring管理的对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserService userService = (IUserService) ac.getBean("userService");
userService.login();
}
}
注意:userService 对象必须从Spring工厂中获取,才有依赖注入
ApplicationContext直译为应用上下文,是用来加载Spring框架配置文件,来构建Spring的工厂对象,它也称之为Spring容器的上下文对象,也称之为Spring的容器。
ApplicationContext 只是BeanFactory(Bean工厂,Bean就是一个java对象) 一个子接口:
为什么不直接使用顶层接口对象来操作呢?
ApplicationContext 更加强大, 所以现在开发基本没人使用BeanFactory。
【扩展】Bean获取的两种方式:
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//1.通过spring容器的bean的id/name来获取
IUserService userService = (IUserService) ac.getBean("userService");
//2.根据bean的类型或者bean接口的类型获取,一般使用接口类型
IUserService userService2 = (IUserService) ac.getBean(IUserService.class);
userService.login();
userService2.login();
}
常用根据名称获取(id/name),即第一种方式,使用spring容器中的标识获取对象
如果根据类型获取,配置了多个类型的话,则抛出异常,如果是通过id/name获取,则不会抛异常
1.无参数构造器(最常用)
//1.默认构造器(spring在创建bean的时候自动调用无参构造器来实例化,相当于new Bean1())
public class Bean1 {
}
//2.在spring容器applicationContext.xml中配置
//3.创建测试类获取bean对象
public void test(){
//创建spring工厂
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//1.默认构造器获取bean对象
Bean1 bean1 = (Bean1) ac.getBean("bean1");
System.out.println(bean1);
}
如果Bean1类,没有无参构造,就会报错:
No default constructor found; nested exception is java.lang.NoSuchMethodException:
2.静态工厂方法
//1.静态工厂方法构造,用来初始化Bean1的时候,可以初始化其他东西
public class Bean1 {
}
//2.静态工厂
public class Bean1Factory {
//静态方法,用来返回对象的实例
public static Bean1 getBean1(){
//在做实例化的时候,可以做其他的事情,即可以在这里写初始化其他对象的代码
//Connection conn....
return new Bean1();
}
}
在执行new ClassPathXmlApplicationContext(“applicationContext.xml”)时,就将所有的对象都生成了,无参构造就调用无参构造函数生成,如果是静态工厂方式就直接通过反射执行工厂类的静态方法,这个过程中是不需要创建工厂类的,所以Spring是单例的,之后获取的bean1都是同一个
3.实例工厂方法
//1.实例工厂方式创建
public class Bean1 {
}
//2.实例工厂,必须new工厂,再获取bean
public class Bean1Factory {
//普通方法,非静态方法
public Bean1 getBean1(){
//初始化实例对象返回
return new Bean1();
}
}
4.FactoryBean方式
之前讲过BeanFactory(工厂类的顶层接口),而这里是FactoryBean,是两个完全不同的概念
public class Bean1 {
}
//实现FactoryBean的方式
//泛型:你要返回什么类型的对象,泛型就是什么
public class Bean1Factory implements FactoryBean {
public Bean1Factory() {
System.out.println("sss");
}
//用来获取bean的实例对象
public Bean1 getObject() throws Exception {
//写一写初始化数据库连接等代码
System.out.println("ggg");
return new Bean1();
}
public Class> getObjectType() {
return null;
}
public boolean isSingleton() {
return false;
}
}
由spring创建的bean对象在什么情况下有效。
项目中常用:singleton 单例 prototype 多例
Singleton: 在一个spring容器中,对象只有一个实例。(默认值)
Prototype: 在一个spring容器中,存在多个实例,每次getBean 返回一个新的实例。
Spring默认是Singleton(单例),在读取applicationContext.xml,就会通过工厂创建所有的bean对象,之后之后直接通过id/name从工厂中获取
测试:
//单例bean
public class SingletonBean {
public SingletonBean() {
System.out.println("SingletonBean:初始化了单例");
}
}
//多实例bean
public class PrototypeBean {
public PrototypeBean() {
System.out.println("--PrototypeBean初始化了多例的");
}
}
//定义spring容器
创建测试类
public void test(){
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("---读取配置文件结束---");
SingletonBean singletonBean1=(SingletonBean)ac.getBean("singletonBean");
SingletonBean singletonBean2=(SingletonBean)ac.getBean("singletonBean");
System.out.println("s1="+singletonBean1);
System.out.println("s2="+singletonBean2);
PrototypeBean prototypeBean1=(PrototypeBean)ac.getBean("prototypeBean");
PrototypeBean prototypeBean2=(PrototypeBean)ac.getBean("prototypeBean");
System.out.println("p1="+prototypeBean1);
System.out.println("p2="+prototypeBean2);
}
结果如下
1.当bean的scope是默认时,表示单例,此时当我们在读取配置文件,生成Spring工厂时就会创建这个bean,之后通过getBean获取到的都是同一个对象
2.prototype时,在我们调用工厂类对象.getBean方法时,才去构建对象
通过spring工厂,可以控制bean的生命周期。
1.在xml配置Bean的初始化和销毁方法
通过 init-method属性 指定实例化后 的调用方法
通过 destroy-method属性 指定销毁对象前的方法
public class SingletonBean {
public SingletonBean() {
System.out.println("SingletonBean的无参构造被调用了");
}
//初始化后自动调用
public void init(){
System.out.println("SingletonBean-init初始化时调用");
}
//销毁时调用
public void destroy(){
System.out.println("SingletonBean-destroy销毁时调用");
}
}
我们发现并没有执行销毁方法,这是因为当方法运行结束,jvm直接关了,Spring容器还来不及销毁对象
解决方式:手动销毁spring容器,自动销毁单例的对象
提示:销毁方法的执行必须满足两个条件:
1)单例(singleton)的bean才会可以手动销毁。
2)必须手动关闭容器(调用close的方法)时,才会执行手动销毁的方法。
2.后处理Bean(BeanPostProcessor接口)了解
后处理Bean也称之为Bean的后处理器,作用是:在Bean初始化的前后,对Bean对象进行增强。它既可以增强一个指定的Bean,也可以增强所有的Bean,底层很多功能(如AOP等)的实现都是基于它的,Spring可以在容器中直接识别调用。
示例:
1.要对“所有”的bean的初始化的时候进行增强(打印一句话)
//后处理bean:用来对bean进行功能增强,可以实现,对所有或者某个bean的初始化进行增强
public class MyBeanPostProcessor implements BeanPostProcessor{
//初始化之后调用
//参数1:Spring管理的对象 参数2:对象的名称
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(beanName + "初始化前被增强了");
return bean;
}
//初始化时调用的
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println(beanName + "初始化后被增强了");
return bean;
}
}
此时我们去加载Spring配置文件(会初始化单例对象)
而多例对象在通过getBean时,初始化,此时被加强
1.属性依赖注入的三种方式
什么是bean属性注入?就是对一个bean的属性赋值,有三种方式:
Spring 框架规范中通过配置文件配置的方式,只支持构造器参数注入和setter方法属性注入,不支持接口注入 !所以我们这里只讲前面两种
2.构造器参数注入 constructor-arg
不再使用无参构造器实例化对象,而是调用有参构造器,通过注入属性来实例化对象
//目标,构造器参数注入,new car直接将参数的值直接赋值
public class Car {
private Integer id;
private String name;
private Double price;
//有参构造
public Car(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Car [id=" + id + ", name=" + name + ", price=" + price + "]";
}
}
配置applicationContext.xml
2.自标签的属性赋值问题,可以使用子标签的value,效果和value属性一样
3.setter方法属性注入 property
使用的默认的构造器(new Bean()),但必须提供属性的setter方法,使用setter方法也是企业经常使用的属性注入方式。
两步:在类中加入setter方法,在配置文件中使用property
//setter方法属性注入
public class Person {
private Integer id;
private String name;
private Car car;
//必须提供setter属性方法
public void setId(Integer id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", car=" + car + "]";
}
}
4.p名称空间的使用-了解
什么是名称空间?
作用:Schema区分同名元素。(有点类似于java的包)
为简化XML文件的配置,Spring2.5版本开始引入了一个新的p名称空间。简单的说,它的作用是为了简化setter方法属性依赖注入配置的,它不是真正的名称空间。
它的使用方法:
p:<属性名>="xxx" 引入常量值
p:<属性名>_ref="xxx" 引用其它Bean对象
步骤:
1.引入p名称空间
2.将
配置时不需要 子元素,简化了配置 .
5.spEL表达式的使用 –会用即可
spEL(Spring Expression Language)是一种表达式语言,它是spring3.x版本的新特性。
它的作用是:支持在运行时操作和查询对象,其语法类似统一的EL语言,但是SpEL提供了额外的功能,功能更强大。
什么是EL、OGNL、spEL?
EL:操作servlet相关的一些对象和相关的值
OGNL:主要操作struts2值栈
spEL:操作bean相关的
语法: #{…} , 引用另一个Bean 、属性、 方法 , 运算
SpEL表达式的使用功能比较多,Bean操作相关的通常有: