Spring诞生:
侵入式:对于EJB、Struts2等一些传统的框架,通常是要实现特定的接口,继承特定的类才能增强功能
非侵入式:对于Hibernate、Spring等框架,对现有的类结构没有影响,就能够增强JavaBean的功能
前面我们在写程序的时候,都是面向接口编程,通过DaoFactroy等方法来实现松耦合
private CategoryDao categoryDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.CategoryDAOImpl", CategoryDao.class);
private BookDao bookDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.BookDaoImpl", BookDao.class);
private UserDao userDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.UserDaoImpl", UserDao.class);
private OrderDao orderDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.OrderDaoImpl", OrderDao.class);
代码如下:
DAO层和Service层通过DaoFactory来实现松耦合,如果Serivce层直接new DaoBook()
,那么DAO和Service就紧耦合了【Service层依赖紧紧依赖于Dao】。
而Spring给我们更加合适的方法来实现松耦合,并且更加灵活、功能更加强大!---->IOC控制反转(这个后面会说)
切面编程也就是AOP编程,其实我们在之前也接触过…动态代理就是一种切面编程了…
当时我们使用动态代理+注解的方式给Service层的方法添加权限.
@Override
@permission("添加分类")
/*添加分类*/
public void addCategory(Category category) {
categoryDao.addCategory(category);
}
/*查找分类*/
@Override
public void findCategory(String id) {
categoryDao.findCategory(id);
}
@Override
@permission("查找分类")
/*查看分类*/
public List<Category> getAllCategory() {
return categoryDao.getAllCategory();
}
/*添加图书*/
@Override
public void addBook(Book book) {
bookDao.addBook(book);
}
Controller调用Service的时候,Service返回的是一个代理对象,代理对象得到Controller想要调用的方法,通过反射来看看该方法上有没有注解
如果有注解的话,那么就判断该用户是否有权限来调用 此方法,如果没有权限,就抛出异常给Controller,Controller接收到异常,就可以提示用户没有权限了。
AOP编程可以简单理解成:在执行某些代码前,执行另外的代码(Struts2的拦截器也是面向切面编程【在执行Action业务方法之前执行拦截器】)
Spring也为我们提供更好地方式来实现面向切面编程!
我们试着回顾一下没学Spring的时候,是怎么开发Web项目的
用户访问:Tomcat->servlet->service->dao
我们来思考几个问题:
action 多个 【维护成员变量】
service 一个 【不需要维护公共变量】
dao 一个 【不需要维护公共变量】
action 访问时候创建
service 启动时候创建
dao 启动时候创建
对于第一个问题和第三个问题,我们可以通过DaoFactory解决掉(虽然不是比较好的解决方法)
对于第二个问题,我们要控制对象的数量和创建时间就有点麻烦了…
而Spring框架通过IOC就很好地可以解决上面的问题…
Spring的核心思想之一:Inversion of Control , 控制反转 IOC
那么控制反转是什么意思呢???对象的创建交给外部容器完成,这个就做控制反转。
那么对象的对象之间的依赖关系Spring是怎么做的呢??依赖注入:dependency injection.
上面已经说了,控制反转是通过外部容器完成的,而Spring又为我们提供了这么一个容器,我们一般将这个容器叫做:IOC容器.
无论是创建对象、处理对象之间的依赖关系、对象创建的时间还是对象的数量,我们都是在Spring为我们提供的IOC容器上配置对象的信息就好了。
那么使用IOC控制反转这一思想有什么作用呢???我们来看看一些优秀的回答…
来自知乎:https://www.zhihu.com/question/23277575/answer/24259844
我摘取一下核心的部分:
ioc的思想最核心的地方在于,资源不由使用资源的双方管理,而由不使用资源的第三方管理,这可以带来很多好处。第一,资源集中管理,实现资源的可配置和易管理。第二,降低了使用资源双方的依赖程度,也就是我们说的耦合度。
也就是说,甲方要达成某种目的不需要直接依赖乙方,它只需要达到的目的告诉第三方机构就可以了,比如甲方需要一双袜子,而乙方它卖一双袜子,它要把袜子卖出去,并不需要自己去直接找到一个卖家来完成袜子的卖出。它也只需要找第三方,告诉别人我要卖一双袜子。这下好了,甲乙双方进行交易活动,都不需要自己直接去找卖家,相当于程序内部开放接口,卖家由第三方作为参数传入。甲乙互相不依赖,而且只有在进行交易活动的时候,甲才和乙产生联系。反之亦然。这样做什么好处么呢,甲乙可以在对方不真实存在的情况下独立存在,而且保证不交易时候无联系,想交易的时候可以很容易的产生联系。甲乙交易活动不需要双方见面,避免了双方的互不信任造成交易失败的问题。因为交易由第三方来负责联系,而且甲乙都认为第三方可靠。那么交易就能很可靠很灵活的产生和进行了。这就是ioc的核心思想。生活中这种例子比比皆是,支付宝在整个淘宝体系里就是庞大的ioc容器,交易双方之外的第三方,提供可靠性可依赖可灵活变更交易方的资源管理中心。另外人事代理也是,雇佣机构和个人之外的第三方。
update=在以上的描述中,诞生了两个专业词汇,依赖注入和控制反转所谓的依赖注入,则是,甲方开放接口,在它需要的时候,能够讲乙方传递进来(注入)所谓的控制反转,甲乙双方不相互依赖,交易活动的进行不依赖于甲乙任何一方,整个活动的进行由第三方负责管理。
参考优秀的博文②:这里写链接内容
知乎@Intopass的回答:
Spring可以分为6大模块:
可以与struts整合,让struts的action创建交给spring
spring mvc模式
既可以与hibernate整合,【session】
也可以使用spring的对hibernate操作的封装
上面文主要引出了为啥我们需要使用Spring框架,以及大致了解了Spring是分为六大模块的…下面主要讲解Spring的core模块!
更新:如果使用maven的同学,引入pom文件就好了
本博文主要是core模块的内容,涉及到Spring core的开发jar包有五个:
这次使用的是Spring3.2版本
**编写配置文件:**Spring核心的配置文件applicationContext.xml
或者叫bean.xml
那这个配置文件怎么写呢??一般地,我们都知道框架的配置文件都是有约束的…我们可以在spring-framework-3.2.5.RELEASE\docs\spring-framework-reference\htmlsingle\index.html
找到XML配置文件的约束
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd">
beans>
我是使用Intellij Idea集成开发工具的,可以选择自带的Spring配置文件,它长的是这样:
<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">
beans>
前面在介绍Spring模块的时候已经说了,Core模块是:IOC容器,解决对象创建和之间的依赖关系。
因此Core模块主要是学习如何得到IOC容器,通过IOC容器来创建对象、解决对象之间的依赖关系、IOC细节。
Spring容器不单单只有一个,可以归为两种类型
步骤
//加载Spring的资源文件
Resource resource = new ClassPathResource("applicationContext.xml");
//创建IOC容器对象【IOC容器=工厂类+applicationContext.xml】
BeanFactory beanFactory = new XmlBeanFactory(resource);
直接通过ClassPathXmlApplicationContext对象来获取
// 得到IOC容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println(ac);
在Spring中总体来看可以通过四种方式来配置对象:
在上面我们已经可以得到IOC容器对象了。接下来就是在applicationContext.xml文件中配置信息【让IOC容器根据applicationContext.xml文件来创建对象】
首先我们先有个JavaBean的类
/**
* Created by ozc on 2017/5/10.
*/
public class User {
private String id;
private String username;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
以前我们是通过new User的方法创建对象的…
User user = new User();
现在我们有了IOC容器,可以让IOC容器帮我们创建对象了。在applicationContext.xml文件中配置对应的信息就行了
<bean id="user" class="User"/>
通过IOC容器对象获取对象:在外界通过IOC容器对象得到User对象
// 得到IOC容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) ac.getBean("user");
System.out.println(user);
上面我们使用的是IOC通过无参构造函数来创建对象,我们来回顾一下一般有几种创建对象的方式:
使用无参的构造函数创建对象我们已经会了,接下来我们看看使用剩下的IOC容器是怎么创建对象的。
首先,JavaBean就要提供带参数的构造函数:
public User(String id, String username) {
this.id = id;
this.username = username;
}
接下来,关键是怎么配置applicationContext.xml文件了。
<bean id="user" class="User">
<constructor-arg index="0" name="id" type="java.lang.String" value="1">constructor-arg>
<constructor-arg index="1" name="username" type="java.lang.String" value="zhongfucheng">constructor-arg>
bean>
在constructor上如果构造函数的值是一个对象,而不是一个普通类型的值,我们就需要用到ref属性了,而不是value属性
比如说:我在User对象上维护了Person对象的值,想要在构造函数中初始化它。因此,就需要用到ref属性了
<bean id="person" class="Person">bean>
<bean id="user" class="User" >
<constructor-arg index="0" name="id" type="java.lang.String" value="1">constructor-arg>
<constructor-arg index="1" name="username" type="java.lang.String" ref="person">constructor-arg>
bean>
首先,使用一个工厂的静态方法返回一个对象
public class Factory {
public static User getBean() {
return new User();
}
}
配置文件中使用工厂的静态方法返回对象
<bean id="user" class="Factory" factory-method="getBean" >
bean>
首先,也是通过工厂的非非静态方法来得到一个对象
public class Factory {
public User getBean() {
return new User();
}
}
配置文件中使用工厂的非静态方法返回对象
<bean id="factory" class="Factory"/>
<bean id="user" class="User" factory-bean="factory" factory-method="getBean"/>
我们在使用XML配置创建Bean的时候,如果该Bean有构造器,那么我们使用
这个节点来对构造器的参数进行赋值…
未免有点太长了,为了简化配置,Spring来提供了c名称空间…
要想c名称空间是需要导入xmlns:c="http://www.springframework.org/schema/c"
的
<bean id="userService" class="bb.UserService" c:userDao-ref="">
bean>
c名称空间有个**缺点:不能装配集合,**当我们要装配集合的时候还是需要
这个节点
如果对象上的属性或者构造函数拥有集合的时候,而我们又需要为集合赋值,那么怎么办?
在构造函数上,普通类型
<bean id="userService" class="bb.UserService" >
<constructor-arg >
<list>
//普通类型
<value>value>
list>
constructor-arg>
bean>
在属性上,引用类型
<property name="userDao">
<list>
<ref>ref>
list>
property>
自从jdk5有了注解这个新特性,我们可以看到Struts2框架、Hibernate框架都支持使用注解来配置信息…
通过注解来配置信息就是为了简化IOC容器的配置,注解可以把对象添加到IOC容器中、处理对象依赖关系,我们来看看怎么用吧:
使用注解步骤:
xmlns:context="http://www.springframework.org/schema/context"
` `
第二种方法:也可以通过自定义扫描类以@CompoentScan修饰来扫描IOC容器的bean对象。如下代码:
//表明该类是配置类
@Configuration
//启动扫描器,扫描bb包下的
//也可以指定多个基础包
//也可以指定类型
@ComponentScan("bb")
public class AnnotationScan {
}
在使用@ComponentScan()这个注解的时候,在测试类上需要@ContextConfiguration这个注解来加载配置类…
创建对象以及处理对象依赖关系,相关的注解:
测试代码:UserDao
package aa;
import org.springframework.stereotype.Repository;
/**
* Created by ozc on 2017/5/10.
*/
//把对象添加到容器中,首字母会小写
@Repository
public class UserDao {
public void save() {
System.out.println("DB:保存用户");
}
}
userService
package aa;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
//把UserService对象添加到IOC容器中,首字母会小写
@Service
public class UserService {
//如果@Resource不指定值,那么就根据类型来找--->UserDao....当然了,IOC容器不能有两个UserDao类型的对象
//@Resource
//如果指定了值,那么Spring就在IOC容器找有没有id为userDao的对象。
@Resource(name = "userDao")
private UserDao userDao;
public void save() {
userDao.save();
}
}
userAction
package aa;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;
/**
* Created by ozc on 2017/5/10.
*/
//把对象添加到IOC容器中,首字母会小写
@Controller
public class UserAction {
@Resource(name = "userService")
private UserService userService;
public String execute() {
userService.save();
return null;
}
}
测试
package aa;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by ozc on 2017/5/10.
*/
public class App {
public static void main(String[] args) {
// 创建容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("aa/applicationContext.xml");
UserAction userAction = (UserAction) ac.getBean("userAction");
userAction.execute();
}
}
怎么通过java代码来配置Bean呢??
编写配置类:
@org.springframework.context.annotation.Configuration
public class Configuration {
}
使用配置类创建bean:
@org.springframework.context.annotation.Configuration
public class Configuration {
@Bean
public UserDao userDao() {
UserDao userDao = new UserDao();
System.out.println("我是在configuration中的"+userDao);
return userDao;
}
}
package bb;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
/**
* Created by ozc on 2017/5/11.
*/
//加载配置类的信息
@ContextConfiguration(classes = Configuration.class)
public class Test2 {
@Test
public void test33() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("bb/bean.xml");
UserDao userDao = (UserDao) ac.getBean("userDao");
System.out.println(userDao);
}
}
结果如下:
注解和XML配置是可以混合使用的,JavaConfig和XML也是可以混合使用的…
如果JavaConfig的配置类是分散的,我们一般再创建一个更高级的配置类(root),然后使用**@Import来将配置类进行组合**
如果XML的配置文件是分散的,我们也是创建一个更高级的配置文件(root),然后使用
来将配置文件组合
在JavaConfig引用XML
在XML引用JavaConfig
节点就行了在公司的项目中,一般我们是XML+注解
既然我们现在已经初步了解IOC容器了,那么这些问题我们都是可以解决的。并且是十分简单【对象写死问题已经解决了,IOC容器就是控制反转创建对象】
指定scope属性,IOC容器就知道创建对象的时候是单例还是多例的了。
属性的值就只有两个:单例/多例
当我们使用singleton【单例】的时候,从IOC容器获取的对象都是同一个:
当我们使用prototype【多例】的时候,从IOC容器获取的对象都是不同的:
scope属性除了控制对象是单例还是多例的,还控制着对象创建的时间!
我们在User的构造函数中打印出一句话,就知道User对象是什么时候创建了。
public User() {
System.out.println("我是User,我被创建了");
}
当使用singleton的时候,对象在IOC容器之前就已经创建了
当使用prototype的时候,对象在使用的时候才创建
lazy-init属性只对singleton【单例】的对象有效…lazy-init默认为false…
有的时候,可能我们想要对象在使用的时候才创建,那么将lazy-init设置为ture就行了
如果我们想要对象在创建后,执行某个方法,我们指定为init-method属性就行了。。
如果我们想要IOC容器销毁后,执行某个方法,我们指定destroy-method属性就行了。
<bean id="user" class="User" scope="singleton" lazy-init="true" init-method="" destroy-method=""/>
/**
* 1) 对象创建: 单例/多例
* scope="singleton", 默认值, 即 默认是单例 【service/dao/工具类】
* scope="prototype", 多例; 【Action对象】
*
* 2) 什么时候创建?
* scope="prototype" 在用到对象的时候,才创建对象。
* scope="singleton" 在启动(容器初始化之前), 就已经创建了bean,且整个应用只有一个。
* 3)是否延迟创建
* lazy-init="false" 默认为false, 不延迟创建,即在启动时候就创建对象
* lazy-init="true" 延迟初始化, 在用到对象的时候才创建对象
* (只对单例有效)
* 4) 创建对象之后,初始化/销毁
* init-method="init_user" 【对应对象的init_user方法,在对象创建之后执行 】
* destroy-method="destroy_user" 【在调用容器对象的destroy方法时候执行,(容器用实现类)】
*/
我们来看一下我们以前关于对象依赖,是怎么的历程
在最开始,我们是直接new对象给serice的userDao属性赋值…
class UserService{
UserDao userDao = new UserDao();
}
后来,我们发现service层紧紧耦合了dao层。我们就写了DaoFactory,在service层只要通过字符串就能够创建对应的dao层的对象了。
DaoFactory
public class DaoFactory {
private static final DaoFactory factory = new DaoFactory();
private DaoFactory(){}
public static DaoFactory getInstance(){
return factory;
}
public <T> T createDao(String className,Class<T> clazz){
try{
T t = (T) Class.forName(className).newInstance();
return t;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
serivce
private CategoryDao categoryDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.CategoryDAOImpl", CategoryDao.class);
private BookDao bookDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.BookDaoImpl", BookDao.class);
private UserDao userDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.UserDaoImpl", UserDao.class);
private OrderDao orderDao = DaoFactory.getInstance().createDao("zhongfucheng.dao.impl.OrderDaoImpl", OrderDao.class);
再后来,我们发现要修改Dao的实现类,还是得修改service层的源代码呀…于是我们就在DaoFactory中读取关于daoImpl的配置文件,根据配置文件来创建对象,这样一来,创建的是哪个daoImpl对service层就是透明的
DaoFactory
public class DaoFactory {
private UserDao userdao = null;
private DaoFactory(){
try{
InputStream in = DaoFactory.class.getClassLoader().getResourceAsStream("dao.properties");
Properties prop = new Properties();
prop.load(in);
String daoClassName = prop.getProperty("userdao");
userdao = (UserDao)Class.forName(daoClassName).newInstance();
}catch (Exception e) {
throw new RuntimeException(e);
}
}
private static final DaoFactory instance = new DaoFactory();
public static DaoFactory getInstance(){
return instance;
}
public UserDao createUserDao(){
return userdao;
}
}
service
UserDao dao = DaoFactory.getInstance().createUserDao();
通过上面的历程,我们可以清晰地发现:对象之间的依赖关系,其实就是给对象上的属性赋值!因为对象上有其他对象的变量,因此存在了依赖…
Spring提供了好几种的方式来给属性赋值
UserService中使用userDao变量来维护与Dao层之间的依赖关系,UserAction中使用userService变量来维护与Service层之间的依赖关系。
userDao
public class UserDao {
public void save() {
System.out.println("DB:保存用户");
}
}
userService
public class UserService {
private UserDao userDao;
public void save() {
userDao.save();
}
}
userAnction
public class UserAction {
private UserService userService;
public String execute() {
userService.save();
return null;
}
}
其实我们在讲解创建带参数的构造函数的时候已经讲过了…我们还是来回顾一下呗…
我们测试service和dao的依赖关系就好了…在serice中加入一个构造函数,参数就是userDao
public UserService(UserDao userDao) {
this.userDao = userDao;
//看看有没有拿到userDao
System.out.println(userDao);
}
applicationContext.xml配置文件
<bean id="userDao" class="UserDao"/>
<bean id="userService" class="UserService">
<constructor-arg index="0" name="userDao" type="UserDao" ref="userDao">constructor-arg>
bean>
测试:可以成功获取到userDao对象
// 创建容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//得到service对象
UserService userService = (UserService) ac.getBean("userService");
我们这里也是测试service和dao层的依赖关系就好了…在service层通过set方法来把userDao注入到UserService中
为UserService添加set方法
public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
//看看有没有拿到userDao
System.out.println(userDao);
}
public void save() {
userDao.save();
}
}
applicationContext.xml配置文件:通过property节点来给属性赋值
<bean id="userDao" class="UserDao"/>
<bean id="userService" class="UserService">
<property name="userDao" ref="userDao"/>
bean>
测试:
我们刚才是先创建userDao对象,再由userService对userDao对象进行引用…我们还有另一种思维:先创建userService,发现userService需要userDao的属性,再创建userDao…我们来看看这种思维方式是怎么配置的:
applicationContext.xml配置文件:property节点内置bean节点
<bean id="userService" class="UserService">
<property name="userDao">
<bean id="userDao" class="UserDao"/>
property>
bean>
测试
我们发现这种思维方式和服务器访问的执行顺序是一样的,但是如果userDao要多次被其他service使用的话,就要多次配置了…
p名称控件这种方式其实就是set方法的一种优化,优化了配置而已…p名称空间这个内容需要在Spring3版本以上才能使用…我们来看看:
applicationContext.xml配置文件:使用p名称空间
<bean id="userDao" class="UserDao"/>
<bean id="userService" class="UserService" p:userDao-ref="userDao"/>
测试
Spring还提供了自动装配的功能,能够非常简化我们的配置
自动装载默认是不打开的,自动装配常用的可分为两种:
applicationContext.xml配置文件:使用自动装配,根据名字
<bean id="userDao" class="UserDao"/>
<bean id="userService" class="UserService" autowire="byName"/>
测试
applicationContext.xml配置文件:使用自动装配,根据类型
值得注意的是:如果使用了根据类型来自动装配,那么在IOC容器中只能有一个这样的类型,否则就会报错!
<bean id="userDao" class="UserDao"/>
<bean id="userService" class="UserService" autowire="byType"/>
测试:
我们这只是测试两个对象之间的依赖关系,如果我们有很多对象,我们也可以使用默认自动分配
@Autowired注解来实现自动装配:
如果没有匹配到bean,又为了避免异常的出现,我们可以使用required属性上设置为false。【谨慎对待】
测试代码
@Component
public class UserService {
private UserDao userDao ;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
顺利拿到userDao的引用
在讲解AOP模块之前,首先我们来讲解一下cglib代理、以及怎么手动实现AOP编程
在讲解cglib之前,首先我们来回顾一下静态代理和动态代理
由于静态代理需要实现目标对象的相同接口,那么可能会导致代理类会非常非常多…不好维护---->因此出现了动态代理
动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理…----->因此出现了cglib代理
cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能!
接下来我们就讲讲怎么写cglib代理:
//需要实现MethodInterceptor接口
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);
proxy.invokeSuper(object, args);
System.out.println("提交事务.....");
return returnValue;
}
}
测试:
public class App {
public static void main(String[] args) {
UserDao userDao = new UserDao();
UserDao factory = (UserDao) new ProxyFactory(userDao).getProxyInstance();
factory.save();
}
}
结果如下:
使用cglib就是为了弥补动态代理的不足【动态代理的目标对象一定要实现接口】
AOP 面向切面的编程:AOP可以实现“业务代码”与“关注点代码”分离
下面我们来看一段代码:
// 保存一个用户
public void add(User user) {
Session session = null;
Transaction trans = null;
try {
session = HibernateSessionFactoryUtils.getSession(); // 【关注点代码】
trans = session.beginTransaction(); // 【关注点代码】
session.save(user); // 核心业务代码
trans.commit(); //…【关注点代码】
} catch (Exception e) {
e.printStackTrace();
if(trans != null){
trans.rollback(); //..【关注点代码】
}
} finally{
HibernateSessionFactoryUtils.closeSession(session); ////..【关注点代码】
}
}
关注点代码,就是指重复执行的代码。
业务代码与关注点代码分离,好处?
IUser接口
public interface IUser {
void save();
}
我们一步一步来分析,首先我们的UserDao有一个save()方法,每次都要开启事务和关闭事务
//@Component -->任何地方都能用这个
@Repository //-->这个在Dao层中使用
public class UserDao {
public void save() {
System.out.println("开始事务");
System.out.println("DB:保存用户");
System.out.println("关闭事务");
}
}
在刚学习java基础的时候,我们知道:如果某些功能经常需要用到就封装成方法:
//@Component -->任何地方都能用这个
@Repository //-->这个在Dao层中使用
public class UserDao {
public void save() {
begin();
System.out.println("DB:保存用户");
close();
}
public void begin() {
System.out.println("开始事务");
}
public void close() {
System.out.println("关闭事务");
}
}
现在呢,我们可能有多个Dao,都需要有开启事务和关闭事务的功能,现在只有UserDao中有这两个方法,重用性还是不够高。因此我们抽取出一个类出来
public class AOP {
public void begin() {
System.out.println("开始事务");
}
public void close() {
System.out.println("关闭事务");
}
}
在UserDao维护这个变量,要用的时候,调用方法就行了。
@Repository //-->这个在Dao层中使用
public class UserDao {
AOP aop;
public void save() {
aop.begin();
System.out.println("DB:保存用户");
aop.close();
}
}
现在的开启事务、关闭事务还是需要我在userDao中手动调用。还是不够优雅。。我想要的效果:当我在调用userDao的save()方法时,动态地开启事务、关闭事务。因此,我们就用到了代理。当然了,真正执行方法的都是userDao、要干事的是AOP,因此在代理中需要维护他们的引用。
public class ProxyFactory {
//维护目标对象
private static Object target;
//维护关键点代码的类
private static AOP aop;
public static Object getProxyInstance(Object target_, AOP aop_) {
//目标对象和关键点代码的类都是通过外界传递进来
target = target_;
aop = aop_;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aop.begin();
Object returnValue = method.invoke(target, args);
aop.close();
return returnValue;
}
}
);
}
}
把AOP加入IOC容器中
//把该对象加入到容器中
@Component
public class AOP {
public void begin() {
System.out.println("开始事务");
}
public void close() {
System.out.println("关闭事务");
}
}
把UserDao放入容器中
@Component
public class UserDao {
public void save() {
System.out.println("DB:保存用户");
}
}
在配置文件中开启注解扫描,使用工厂静态方法创建代理对象
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd">
<bean id="proxy" class="aa.ProxyFactory" factory-method="getProxyInstance">
<constructor-arg index="0" ref="userDao"/>
<constructor-arg index="1" ref="AOP"/>
bean>
<context:component-scan base-package="aa"/>
beans>
测试,得到UserDao对象,调用方法
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
IUser iUser = (IUser) ac.getBean("proxy");
iUser.save();
}
}
上面使用的是工厂静态方法来创建代理类对象。我们也使用一下非静态的工厂方法创建对象。
package aa;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by ozc on 2017/5/11.
*/
public class ProxyFactory {
public Object getProxyInstance(final Object target_, final AOP aop_) {
//目标对象和关键点代码的类都是通过外界传递进来
return Proxy.newProxyInstance(
target_.getClass().getClassLoader(),
target_.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aop_.begin();
Object returnValue = method.invoke(target_, args);
aop_.close();
return returnValue;
}
}
);
}
}
配置文件:先创建工厂,再创建代理类对象
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd">
<bean id="factory" class="aa.ProxyFactory"/>
<bean id="IUser" class="aa.IUser" factory-bean="factory" factory-method="getProxyInstance">
<constructor-arg index="0" ref="userDao"/>
<constructor-arg index="1" ref="AOP"/>
bean>
<context:component-scan base-package="aa"/>
beans>
效果如下:
Aop: aspect object programming 面向切面编程
关注点:重复代码就叫做关注点。
// 保存一个用户
public void add(User user) {
Session session = null;
Transaction trans = null;
try {
session = HibernateSessionFactoryUtils.getSession(); // 【关注点代码】
trans = session.beginTransaction(); // 【关注点代码】
session.save(user); // 核心业务代码
trans.commit(); //…【关注点代码】
} catch (Exception e) {
e.printStackTrace();
if(trans != null){
trans.rollback(); //..【关注点代码】
}
} finally{
HibernateSessionFactoryUtils.closeSession(session); ////..【关注点代码】
}
}
切面:关注点形成的类,就叫切面(类)!
public class AOP {
public void begin() {
System.out.println("开始事务");
}
public void close() {
System.out.println("关闭事务");
}
}
切入点:
切入点表达式:
更新:如果用maven的同学,引入pom依赖就好了
1) 先引入aop相关jar文件 (aspectj aop优秀组件)
注意: 用到spring2.5版本的jar文件,如果用jdk1.7可能会有问题。
2) bean.xml中引入aop名称空间
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
引入4个jar包:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd">
beans>
我们之前手动的实现AOP编程是需要自己来编写代理工厂的**,现在有了Spring,就不需要我们自己写代理工厂了。Spring内部会帮我们创建代理工厂**。也就是说,不用我们自己写代理对象了。
因此,我们只要关心切面类、切入点、编写切入表达式指定拦截什么方法就可以了!
还是以上一个例子为案例,使用Spring的注解方式来实现AOP编程
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="aa"/>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
切面类
@Component
@Aspect//指定为切面类
public class AOP {
//里面的值为切入点表达式
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("开始事务");
}
@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("关闭事务");
}
}
UserDao实现了IUser接口
@Component
public class UserDao implements IUser {
@Override
public void save() {
System.out.println("DB:保存用户");
}
}
IUser接口
public interface IUser {
void save();
}
测试代码:
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
//这里得到的是代理对象....
IUser iUser = (IUser) ac.getBean("userDao");
System.out.println(iUser.getClass());
iUser.save();
}
}
效果:
上面我们测试的是UserDao有IUser接口,内部使用的是动态代理…那么我们这次测试的是目标对象没有接口
OrderDao没有实现接口
@Component
public class OrderDao {
public void save() {
System.out.println("我已经进货了!!!");
}
}
测试代码:
public class App {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
System.out.println(orderDao.getClass());
orderDao.save();
}
}
效果:
api:
// 前置通知 : 在执行目标方法之前执行
@Before("pointCut_()")
public void begin(){
System.out.println("开始事务/异常");
}
// 后置/最终通知:在执行目标方法之后执行 【无论是否出现异常最终都会执行】
@After("pointCut_()")
public void after(){
System.out.println("提交事务/关闭");
}
// 返回后通知: 在调用目标方法结束后执行 【出现异常不执行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}
// 异常通知: 当目标方法执行异常时候执行此关注点代码
@AfterThrowing("pointCut_()")
public void afterThrowing(){
System.out.println("afterThrowing()");
}
// 环绕通知:环绕目标方式执行
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前....");
pjp.proceed(); // 执行目标方法
System.out.println("环绕后....");
}
我们的代码是这样的:每次写Before、After等,都要重写一次切入点表达式,这样就不优雅了。
@Before("execution(* aa.*.*(..))")
public void begin() {
System.out.println("开始事务");
}
@After("execution(* aa.*.*(..))")
public void close() {
System.out.println("关闭事务");
}
于是乎,我们要使用@Pointcut这个注解,来指定切入点表达式,在用到的地方中,直接引用就行了!
那么我们的代码就可以改造成这样了:
@Component
@Aspect//指定为切面类
public class AOP {
// 指定切入点表达式,拦截哪个类的哪些方法
@Pointcut("execution(* aa.*.*(..))")
public void pt() {
}
@Before("pt()")
public void begin() {
System.out.println("开始事务");
}
@After("pt()")
public void close() {
System.out.println("关闭事务");
}
}
XML文件配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userDao" class="aa.UserDao"/>
<bean id="orderDao" class="aa.OrderDao"/>
<bean id="aop" class="aa.AOP"/>
<aop:config >
<aop:pointcut id="pointCut" expression="execution(* aa.*.*(..))"/>
<aop:aspect ref="aop">
<aop:before method="begin" pointcut-ref="pointCut"/>
<aop:after method="close" pointcut-ref="pointCut"/>
aop:aspect>
aop:config>
beans>
测试:
public class App {
@Test
public void test1() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
System.out.println(orderDao.getClass());
orderDao.save();
}
@Test
public void test2() {
ApplicationContext ac =
new ClassPathXmlApplicationContext("aa/applicationContext.xml");
IUser userDao = (IUser) ac.getBean("userDao");
System.out.println(userDao.getClass());
userDao.save();
}
}
测试OrderDao
测试UserDao
切入点表达式主要就是来配置拦截哪些类的哪些方法
我们去文档中找找它的语法…
在文档中搜索:execution(
那么它的语法是这样子的:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
符号讲解:
参数讲解:
官方也有给出一些例子给我们理解:
上一篇Spring博文主要讲解了如何使用Spring来实现AOP编程,本博文主要讲解Spring的DAO模块对JDBC的支持,以及Spring对事务的控制…
对于JDBC而言,我们肯定不会陌生,我们在初学的时候肯定写过非常非常多的JDBC模板代码!
我们来回忆一下我们怎么对模板代码进行优化的!
首先来看一下我们原生的JDBC:需要手动去数据库的驱动从而拿到对应的连接…
try {
String sql = "insert into t_dept(deptName) values('test');";
Connection con = null;
Statement stmt = null;
Class.forName("com.mysql.jdbc.Driver");
// 连接对象
con = DriverManager.getConnection("jdbc:mysql:///hib_demo", "root", "root");
// 执行命令对象
stmt = con.createStatement();
// 执行
stmt.execute(sql);
// 关闭
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
因为JDBC是面向接口编程的,因此数据库的驱动都是由数据库的厂商给做到好了,我们只要加载对应的数据库驱动,便可以获取对应的数据库连接…因此,我们写了一个工具类,专门来获取与数据库的连接(Connection),当然啦,为了更加灵活,我们的工具类是读取配置文件的方式来做的。
/*
* 连接数据库的driver,url,username,password通过配置文件来配置,可以增加灵活性
* 当我们需要切换数据库的时候,只需要在配置文件中改以上的信息即可
*
* */
private static String driver = null;
private static String url = null;
private static String username = null;
private static String password = null;
static {
try {
//获取配置文件的读入流
InputStream inputStream = UtilsDemo.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(inputStream);
//获取配置文件的信息
driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//加载驱动类
Class.forName(driver);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);
}
public static void release(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
经过上面一层的封装,我们可以**在使用的地方直接使用工具类来得到与数据库的连接…那么比原来就方便很多了!**但是呢,每次还是需要使用Connection去创建一个Statement对象。并且无论是什么方法,其实就是SQL语句和传递进来的参数不同!
于是我们可以使用DBUtils这样的组件来解决上面的问题
上面已经回顾了一下以前我们的JDBC开发了,那么看看Spring对JDBC又是怎么优化的
首先,想要使用Spring的JDBC模块,就必须引入两个jar文件:
首先还是看一下我们原生的JDBC代码:获取Connection是可以抽取出来的,直接使用dataSource来得到Connection就行了。
public void save() {
try {
String sql = "insert into t_dept(deptName) values('test');";
Connection con = null;
Statement stmt = null;
Class.forName("com.mysql.jdbc.Driver");
// 连接对象
con = DriverManager.getConnection("jdbc:mysql:///hib_demo", "root", "root");
// 执行命令对象
stmt = con.createStatement();
// 执行
stmt.execute(sql);
// 关闭
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
值得注意的是,JDBC对C3P0数据库连接池是有很好的支持的。因此我们直接可以使用Spring的依赖注入,在配置文件中配置dataSource就行了!
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql:///hib_demo">property>
<property name="user" value="root">property>
<property name="password" value="root">property>
<property name="initialPoolSize" value="3">property>
<property name="maxPoolSize" value="10">property>
<property name="maxStatements" value="100">property>
<property name="acquireIncrement" value="2">property>
bean>
// IOC容器注入
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void save() {
try {
String sql = "insert into t_dept(deptName) values('test');";
Connection con = null;
Statement stmt = null;
// 连接对象
con = dataSource.getConnection();
// 执行命令对象
stmt = con.createStatement();
// 执行
stmt.execute(sql);
// 关闭
stmt.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Spring来提供了JdbcTemplate这么一个类给我们使用!它封装了DataSource,也就是说我们可以在Dao中使用JdbcTemplate就行了。
创建dataSource,创建jdbcTemplate对象
<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:c="http://www.springframework.org/schema/c"
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">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql:///zhongfucheng">property>
<property name="user" value="root">property>
<property name="password" value="root">property>
<property name="initialPoolSize" value="3">property>
<property name="maxPoolSize" value="10">property>
<property name="maxStatements" value="100">property>
<property name="acquireIncrement" value="2">property>
bean>
<context:component-scan base-package="bb"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
beans>
userDao
package bb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
* Created by ozc on 2017/5/10.
*/
@Component
public class UserDao implements IUser {
//使用Spring的自动装配
@Autowired
private JdbcTemplate template;
@Override
public void save() {
String sql = "insert into user(name,password) values('zhoggucheng','123')";
template.update(sql);
}
}
测试:
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bb/bean.xml");
UserDao userDao = (UserDao) ac.getBean("userDao");
userDao.save();
}
我们要是使用JdbcTemplate查询会发现有很多重载了query()方法
一般地,如果我们使用queryForMap(),那么只能封装一行的数据,如果封装多行的数据、那么就会报错!并且,Spring是不知道我们想把一行数据封装成是什么样的,因此返回值是Map集合…我们得到Map集合的话还需要我们自己去转换成自己需要的类型。
我们一般使用下面这个方法:
我们可以实现RowMapper,告诉Spriing我们将每行记录封装成怎么样的。
public void query(String id) {
String sql = "select * from USER where password=?";
List<User> query = template.query(sql, new RowMapper<User>() {
//将每行记录封装成User对象
@Override
public User mapRow(ResultSet resultSet, int i) throws SQLException {
User user = new User();
user.setName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
return user;
}
},id);
System.out.println(query);
}
当然了,一般我们都是将每行记录封装成一个JavaBean对象的,因此直接实现RowMapper,在使用的时候创建就好了。
class MyResult implements RowMapper<Dept>{
// 如何封装一行记录
@Override
public Dept mapRow(ResultSet rs, int index) throws SQLException {
Dept dept = new Dept();
dept.setDeptId(rs.getInt("deptId"));
dept.setDeptName(rs.getString("deptName"));
return dept;
}
}
下面主要讲解Spring的事务控制,如何使用Spring来对程序进行事务控制…
一般地,我们事务控制都是在service层做的。。为什么是在service层而不是在dao层呢??有没有这样的疑问…
service层是业务逻辑层,service的方法一旦执行成功,那么说明该功能没有出错。
一个service方法可能要调用dao层的多个方法…如果在dao层做事务控制的话,一个dao方法出错了,仅仅把事务回滚到当前dao的功能,这样是不合适的[因为我们的业务由多个dao方法组成]。如果没有出错,调用完dao方法就commit了事务,这也是不合适的[导致太多的commit操作]。
事务控制分为两种:
自己手动控制事务,就叫做编程式事务控制。
Spring提供对事务的控制管理就叫做声明式事务控制
Spring提供了对事务控制的实现。
Spring给我们提供了事务的管理器类,事务管理器类又分为两种,因为JDBC的事务和Hibernate的事务是不一样的。
Jdbc技术:DataSourceTransactionManager
Hibernate技术:HibernateTransactionManager
我们基于Spring的JDBC来做例子吧
引入相关jar包(如果用maven,那引入pom依赖就好了)
编写一个接口
public interface IUser {
void save();
}
UserDao实现类,使用JdbcTemplate对数据库进行操作!
@Repository
public class UserDao implements IUser {
//使用Spring的自动装配
@Autowired
private JdbcTemplate template;
@Override
public void save() {
String sql = "insert into user(name,password) values('zhong','222')";
template.update(sql);
}
}
userService
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void save() {
userDao.save();
}
}
bean.xml配置:配置数据库连接池、jdbcTemplate对象、扫描注解
<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:c="http://www.springframework.org/schema/c"
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">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql:///zhongfucheng">property>
<property name="user" value="root">property>
<property name="password" value="root">property>
<property name="initialPoolSize" value="3">property>
<property name="maxPoolSize" value="10">property>
<property name="maxStatements" value="100">property>
<property name="acquireIncrement" value="2">property>
bean>
<context:component-scan base-package="bb"/>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">property>
bean>
beans>
前面搭建环境的的时候,是没有任何的事务控制的。也就是说,当我在service中调用两次userDao.save(),即时在中途中有异常抛出,还是可以在数据库插入一条记录的。
Service代码:
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void save() {
userDao.save();
int i = 1 / 0;
userDao.save();
}
}
测试代码:
public class Test2 {
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bb/bean.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.save();
}
}
首先,我们要配置事务的管理器类:因为JDBC和Hibernate的事务控制是不同的。
<bean id="txManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
再而,配置事务管理器类如何管理事务
<tx:advice id="txAdvice" transaction-manager="txManage">
<tx:attributes>
<tx:method name="*" read-only="false"/>
tx:attributes>
tx:advice>
最后,配置拦截哪些方法,
<aop:config>
<aop:pointcut id="pt" expression="execution(* bb.UserService.*(..) )"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt">aop:advisor>
aop:config>
配置完成之后,service中的方法都应该被Spring的声明式事务控制了。因此我们再次测试一下:
@Test
public void test33() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bb/bean.xml");
UserService userService = (UserService) ac.getBean("userService");
userService.save();
}
当然了,有的人可能觉得到XML文件上配置太多东西了。Spring也提供了使用注解的方式来实现对事务控制
第一步和XML的是一样的,必须配置事务管理器类:
<bean id="txManage" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
第二步:开启以注解的方式来实现事务控制
<tx:annotation-driven transaction-manager="txManage"/>
最后,**想要控制哪个方法事务,在其前面添加@Transactional这个注解就行了!**如果想要控制整个类的事务,那么在类上面添加就行了。
@Transactional
public void save() {
userDao.save();
int i = 1 / 0;
userDao.save();
}
其实我们**在XML配置管理器类如何管理事务,就是在指定事务的属性!**我们来看一下事务的属性有什么:
看了上面的事务属性,没有接触过的属性其实就这么一个:propagation = Propagation.REQUIRED
事务的传播行为。
事务传播行为的属性有以下这么多个,常用的就只有两个:
现在有一个日志类,它的事务传播行为是Propagation.REQUIRED
Class Log{
Propagation.REQUIRED
insertLog();
}
现在,我要在保存之前记录日志
Propagation.REQUIRED
Void saveDept(){
insertLog();
saveDept();
}
saveDept()本身就存在着一个事务,当调用insertLog()的时候,insertLog()的事务会加入到saveDept()事务中
也就是说,saveDept()方法内始终是一个事务,如果在途中出现了异常,那么insertLog()的数据是会被回滚的【因为在同一事务内】
Void saveDept(){
insertLog(); // 加入当前事务
.. 异常, 会回滚
saveDept();
}
现在有一个日志类,它的事务传播行为是Propagation.REQUIRED_NEW
Class Log{
Propagation.REQUIRED
insertLog();
}
现在,我要在保存之前记录日志
Propagation.REQUIRED
Void saveDept(){
insertLog();
saveDept();
}
当执行到saveDept()中的insertLog()方法时,insertLog()方法发现 saveDept()已经存在事务了,insertLog()会独自新开一个事务,直到事务关闭之后,再执行下面的方法
如果在中途中抛出了异常,insertLog()是不会回滚的,因为它的事务是自己的,已经提交了
Void saveDept(){
insertLog(); // 始终开启事务
.. 异常, 日志不会回滚
saveDept();
}
Spring事务管理我相信大家都用得很多,但可能仅仅局限于一个@Transactional
注解或者在XML
中配置事务相关的东西。不管怎么说,日常可能足够我们去用了。但作为程序员,无论是为了面试还是说更好把控自己写的代码,还是应该得多多了解一下Spring事务的一些细节。
这里我抛出几个问题,看大家能不能瞬间答得上:
Hibernate/JPA
或者是Mybatis
,都知道的底层是需要一个session/connection
对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection
对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?阅读这篇文章的同学我默认大家都对Spring事务相关知识有一定的了解了。(ps:如果不了解点解具体的文章去阅读再回到这里来哦)
我们都知道,Spring事务是Spring AOP的最佳实践之一,所以说AOP入门基础知识(简单配置,使用)是需要先知道的。如果想更加全面了解AOP可以看这篇文章:AOP重要知识点(术语介绍、全面使用)。说到AOP就不能不说AOP底层原理:动态代理设计模式。到这里,对AOP已经有一个基础的认识了。于是我们就可以使用XML/注解方式来配置Spring事务管理。
在IOC学习中,可以知道的是Spring中Bean的生命周期(引出BPP对象)并且IOC所管理的对象默认都是单例的:单例设计模式,单例对象如果有"状态"(有成员变量),那么多线程访问这个单例对象,可能就造成线程不安全。那么何为线程安全?,解决线程安全有很多方式,但其中有一种:让每一个线程都拥有自己的一个变量:ThreadLocal
如果对我以上说的知识点不太了解的话,建议点击蓝字进去学习一番。
之前朋友问了我一个例子:
在Service层抛出Exception,在Controller层捕获,那如果在Service中有异常,那会事务回滚吗?
// Service方法
@Transactional
public Employee addEmployee() throws Exception {
Employee employee = new Employee("3y", 23);
employeeRepository.save(employee);
// 假设这里出了Exception
int i = 1 / 0;
return employee;
}
// Controller调用
@RequestMapping("/add")
public Employee addEmployee() {
Employee employee = null;
try {
employee = employeeService.addEmployee();
} catch (Exception e) {
e.printStackTrace();
}
return employee;
}
我第一反应:不会回滚吧。
但朋友经过测试说,可以回滚阿。(pappapa打脸)
看了一下文档,原来文档有说明:
By default checked exceptions do not result in the transactional interceptor marking the transaction for rollback and instances of RuntimeException and its subclasses do
结论:如果是编译时异常不会自动回滚,如果是运行时异常,那会自动回滚!
第二个例子来源于知乎@柳树文章,文末会给出相应的URL
我们都知道,带有@Transactional
注解所包围的方法就能被Spring事务管理起来,那如果我在当前类下使用一个没有事务的方法去调用一个有事务的方法,那我们这次调用会怎么样?是否会有事务呢?
用代码来描述一下:
// 没有事务的方法去调用有事务的方法
public Employee addEmployee2Controller() throws Exception {
return this.addEmployee();
}
@Transactional
public Employee addEmployee() throws Exception {
employeeRepository.deleteAll();
Employee employee = new Employee("3y", 23);
// 模拟异常
int i = 1 / 0;
return employee;
}
我第一直觉是:这跟Spring事务的传播机制有关吧。
其实这跟Spring事务的传播机制没有关系,下面我讲述一下:
@Transactional
,那么会生成一个代理对象。接下来我用图来说明一下:
显然地,我们拿到的是代理(Proxy)对象,调用addEmployee2Controller()
方法,而addEmployee2Controller()
方法的逻辑是target.addEmployee()
,调用回原始对象(target)的addEmployee()
。所以这次的调用压根就没有事务存在,更谈不上说Spring事务传播机制了。
原有的数据:
测试结果:压根就没有事务的存在
从上面的测试我们可以发现:如果是在本类中没有事务的方法来调用标注注解@Transactional
方法,最后的结论是没有事务的。那如果我将这个标注注解的方法移到别的Service对象上,有没有事务?
@Service
public class TestService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public Employee addEmployee() throws Exception {
employeeRepository.deleteAll();
Employee employee = new Employee("3y", 23);
// 模拟异常
int i = 1 / 0;
return employee;
}
}
@Service
public class EmployeeService {
@Autowired
private TestService testService;
// 没有事务的方法去调用别的类有事务的方法
public Employee addEmployee2Controller() throws Exception {
return testService.addEmployee();
}
}
测试结果:
因为我们用的是代理对象(Proxy)去调用addEmployee()
方法,那就当然有事务了。
看完这两个例子,有没有觉得3y的直觉是真的水!
如果嵌套调用含有事务的方法,在Spring事务管理中,这属于哪个知识点?
在当前含有事务方法内部调用其他的方法(无论该方法是否含有事务),这就属于Spring事务传播机制的知识点范畴了。
Spring事务基于Spring AOP,Spring AOP底层用的动态代理,动态代理有两种方式:
至于为啥以上的情况不能增强,用你们的脑瓜子想一下就知道了。
值得说明的是:那些不能被Spring AOP增强的方法并不是不能在事务环境下工作了。只要它们被外层的事务方法调用了,由于Spring事务管理的传播级别,内部方法也可以工作在外部方法所启动的事务上下文中。
至于Spring事务传播机制的几个级别,我在这里就不贴出来了。这里只是再次解释“啥情况才是属于Spring事务传播机制的范畴”。
我们使用的框架可能是
Hibernate/JPA
或者是Mybatis
,都知道的底层是需要一个session/connection
对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection
对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?
回想一下当年我们学Mybaits的时候,是怎么编写Session工具类?
没错,用的就是ThreadLocal,同样地,Spring也是用的ThreadLocal。
以下内容来源《精通 Spring4.x》
我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的“状态性对象”采用ThreadLocal封装,让它们也成为线程安全的“状态性对象”,因此,有状态的Bean就能够以singleton的方式在多线程中工作。
我们可以试着点一下进去TransactionSynchronizationManager中看一下:
BBP的全称叫做:BeanPostProcessor,一般我们俗称对象后处理器
Spring管理Bean(或者说Bean的生命周期)也是一个常考的知识点,我在秋招也重新整理了一下步骤,因为比较重要,所以还是在这里贴一下吧:
配置/实现
了InstantiationAwareBean,则调用对应的方法配置/实现了
Aware接口,则调用对应的方法init-method
或者实现InstantiationBean,则调用对应的方法其中也有关于BPP图片:
Spring AOP编程底层通过的是动态代理技术,在调用的时候肯定用的是代理对象。那么Spring是怎么做的呢?
我只需要写一个BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,对对象进行判断,看他需不需要织入切面逻辑,如果需要,那我就根据这个对象,生成一个代理对象,然后返回这个代理对象,那么最终注入容器的,自然就是代理对象了。
Spring提供了BeanPostProcessor,就是让我们可以对有需要的对象进行“加工处理”啊!
Spring事务可以分为两种:
编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。
在编程式事务中有以下几个重要的了接口:
在声明式事务中,除了TransactionStatus和PlatformTransactionManager接口,还有几个重要的接口:
只有光头才能变强。
文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y
大年初二,朋友问了我一个技术的问题(朋友实在是好学,佩服!)
该问题来源知乎(synchronized锁问题):
开启10000个线程,每个线程给员工表的money字段【初始值是0】加1,没有使用悲观锁和乐观锁,但是在业务层方法上加了synchronized关键字,问题是代码执行完毕后数据库中的money 字段不是10000,而是小于10000 问题出在哪里?
Service层代码:
SQL代码(没有加悲观/乐观锁):
用1000个线程跑代码:
简单来说:多线程跑一个使用synchronized关键字修饰的方法,方法内操作的是数据库,按正常逻辑应该最终的值是1000,但经过多次测试,结果是低于1000。这是为什么呢?
既然测试出来的结果是低于1000,那说明这段代码不是线程安全的。不是线程安全的,那问题出现在哪呢?众所周知,synchronized方法能够保证所修饰的代码块、方法
保证有序性、原子性、可见性
。
讲道理,以上的代码跑起来,问题中Service
层的increaseMoney()
是有序的、原子的、可见的
,所以断定跟synchronized应该没关系。
(参考我之前写过的synchronize锁笔记:Java锁机制了解一下)
既然Java层面上找不到原因,那分析一下数据库层面的吧(因为方法内操作的是数据库)。在increaseMoney()
方法前加了@Transcational
注解,说明这个方法是带有事务的。事务能保证同组的SQL要么同时成功,要么同时失败。讲道理,如果没有报错的话,应该每个线程都对money值进行+1
。从理论上来说,结果应该是1000的才对。
(参考我之前写过的Spring事务:一文带你看懂Spring事务!)
根据上面的分析,我怀疑是提问者没测试好(hhhh,逃),于是我也跑去测试了一下,发现是以提问者的方式来使用是真的有问题。
首先贴一下我的测试代码:
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping("/add")
public void addEmployee() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> employeeService.addEmployee()).start();
}
}
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public synchronized void addEmployee() {
// 查出ID为8的记录,然后每次将年龄增加一
Employee employee = employeeRepository.getOne(8);
System.out.println(employee);
Integer age = employee.getAge();
employee.setAge(age + 1);
employeeRepository.save(employee);
}
}
简单地打印了每次拿到的employee值,并且拿到了SQL执行的顺序,如下(贴出小部分):
从打印的情况我们可以得出:多线程情况下并没有串行执行addEmployee()
方法。这就导致对同一个值做重复的修改,所以最终的数值比1000要少。
发现并不是同步执行的,于是我就怀疑synchronized
关键字和Spring肯定有点冲突。于是根据这两个关键字搜了一下,找到了问题所在。
我们知道Spring事务的底层是Spring AOP,而Spring AOP的底层是动态代理技术。跟大家一起回顾一下动态代理:
public static void main(String[] args) {
// 目标对象
Object target ;
Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 但凡带有@Transcational注解的方法都会被拦截
// 1... 开启事务
method.invoke(target);
// 2... 提交事务
return null;
}
});
}
(详细请参考我之前写过的动态代理:给女朋友讲解什么是代理模式)
实际上Spring做的处理跟以上的思路是一样的,我们可以看一下TransactionAspectSupport类中invokeWithinTransaction()
:
调用方法前开启事务,调用方法后提交事务
在多线程环境下,就可能会出现:方法执行完了(synchronized代码块执行完了),事务还没提交,别的线程可以进入被synchronized修饰的方法,再读取的时候,读到的是还没提交事务的数据,这个数据不是最新的,所以就出现了这个问题。
从上面我们可以发现,问题所在是因为@Transcational
注解和synchronized
一起使用了,加锁的范围没有包括到整个事务。所以我们可以这样做:
新建一个名叫SynchronizedService类,让其去调用addEmployee()
方法,整个代码如下:
@RestController
public class EmployeeController {
@Autowired
private SynchronizedService synchronizedService ;
@RequestMapping("/add")
public void addEmployee() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();
}
}
}
// 新建的Service类
@Service
public class SynchronizedService {
@Autowired
private EmployeeService employeeService ;
// 同步
public synchronized void synchronizedAddEmployee() {
employeeService.addEmployee();
}
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public void addEmployee() {
// 查出ID为8的记录,然后每次将年龄增加一
Employee employee = employeeRepository.getOne(8);
System.out.println(Thread.currentThread().getName() + employee);
Integer age = employee.getAge();
employee.setAge(age + 1);
employeeRepository.save(employee);
}
}
我们将synchronized锁的范围包含到整个Spring事务上,这就不会出现线程安全的问题了。在测试的时候,我们可以发现1000个线程跑起来比之前要慢得多,当然我们的数据是正确的:
可以发现的是,虽然说Spring事务用起来我们是非常方便的,但如果不了解一些Spring事务的细节,很多时候出现Bug了就百思不得其解。还是得继续加油努力呀~~~
本来想的是刷完《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》的IOC章节后来重新编写一篇IOC的文章的,看了一下之前已经写过的入门系列Spring入门这一篇就够了和Spring【依赖注入】就是这么简单。最主要的知识点都已经讲过了,所以感觉就没必要重新来编写这些知识点了…
这篇文章主要是补充和强化一些比较重要的知识点,并会把上面的两本书关于IOC的知识点整理出来。
那么接下来就开始吧,如果有错的地方希望能多多包涵,并不吝在评论区指正!
结合《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》两本书的IOC章节将其知识点整理起来~
在《精通Spring4.x 企业应用开发实战》中对IOC的定义是这样的:
IoC(Inversion of Control)控制反转,包含了两个方面:一、控制。二、反转
我们可以简单认为:
IOC不够开门见山,于是Martin Fowler提出了DI(dependency injection)来替代IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
在《Spring 实战 (第4版)》中并没有提及到IOC,而是直接来说DI的:
通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件在创建对象的时候进行设定,对象无需自行创建或管理它们的依赖关系,依赖关系将被自动注入到需要它们的对象当中去
从书上我们也可以发现:IoC和DI的定义(区别)并不是如此容易就可以说得清楚的了。这里我就简单摘抄一下:
对我们而言,其实也没必要分得那么清,混合一谈也不影响我们的理解…
再通过昨天写过的工厂模式理解了没有?,我们现在就可以很清楚的发现,其实所谓的IOC容器就是一个大工厂【第三方容器】(Spring实现的功能很强大!比我们自己手写的工厂要好很多)。
使用IOC的好处(知乎@Intopass的回答):
参考资料:
从上面就已经说了:IOC容器其实就是一个大工厂,它用来管理我们所有的对象以及依赖关系。
上面描述的技术只要学过点Java的都能说出来,这一下子可能就会被面试官问倒了,我们简单来看看实际Spring IOC容器是怎么实现对象的创建和依赖的:
Spring容器(Bean工厂)可简单分成两种:
几乎所有的应用场合都是使用ApplicationContext!
BeanFactory的继承体系:
ApplicationContext的继承体系:
其中在ApplicationContext子类中又有一个比较重要的:WebApplicationContext
专门为Web应用准备的
Web应用与Spring融合:
我们看看BeanFactory的生命周期:
接下来我们再看看ApplicationContext的生命周期:
初始化的过程都是比较长,我们可以分类来对其进行解析:
ApplicationContext和BeanFactory不同之处在于:
addBeanPostProcessor()
方法进行注册有了上面的知识点了,我们再来详细地看看Bean的初始化过程:
简要总结:
解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中;Spring4.x开始IOC容器装配Bean有4种方式:
总的来说:我们以XML配置+注解来装配Bean得多,其中注解这种方式占大部分!
依赖注入的方式有3种方式:
setter()
方法注入总的来说使用属性注入是比较灵活和方便的,这是大多数人的选择!
对象之间有三种关系:
Bean的作用域:
使用到了Web应用环境相关的Bean作用域的话,是需要我们手动配置代理的~
原因也很简单:因为我们默认的Bean是单例的,为了适配Web应用环境相关的Bean作用域—>每个request都需要一个对象,此时我们返回一个代理对象出去就可以完成我们的需求了!
将Bean配置单例的时候还有一个问题:
此时我们需要用到了lookup
方法注入,使用也很简单,看看例子就明白了:
昨天在刷书的时候刚好看到了有人在知乎邀请我回答这个问题:
结合两本书的知识点,可以归纳成两种解决方案:
@Primary
注解设置为首选的注入Bean@Qualifier
注解设置特定名称的Bean来限定注入!
之前在写配置文件的时候都是直接将我们的数据库配置信息在里面写死的了:
其实我们有更优雅的做法:将这些配置信息写到配置文件上(因为这些配置信息很可能是会变的,而且有可能被多个配置文件引用).
引用配置文件的数据使用的是${}
除了引用配置文件上的数据,我们还可以引用Bean的属性:
引用Bean的属性使用的是#{}
在这种技术在《Spring 实战 第四版》称之为Spring EL,跟我们之前学过的EL表达式是类似的。主要的功能就是上面的那种,想要更深入了解可参考下面的链接:
xml文件之间组合:
xml和javaconfig互相组合的方式:
public static void main(String[] args) {
//1.通过构造函数加载配置类
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class);
//2.通过编码方式注册配置类
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(DaoConfig.class);
ctx.register(ServiceConfig.class);
ctx.refresh();
//3.通过XML组装@Configuration配置类所提供的配置信息
ApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/conf/beans2.xml");
//4.通过@Configuration组装XML配置所提供的配置信息
ApplicationContext ctx = new AnnotationConfigApplicationContext(LogonAppConfig.class);
//5.@Configuration的配置类相互引用
ApplicationContext ctx = new AnnotationConfigApplicationContext(DaoConfig.class,ServiceConfig.class);
LogonService logonService = ctx.getBean(LogonService.class);
System.out.println((logonService.getLogDao() !=null));
logonService.printHelllo();
}
第一种的例子:
第二种的例子:
第三种的例子:
第四种的例子:
总的来说,Spring IOC容器就是在创建Bean的时候有很多的方式给了我们实现,其中也包括了很多关于Bean的配置~
对于Bean相关的注入教程代码和简化配置(p和c名称空间)我就不一一说明啦,你们去看Spring入门这一篇就够了和Spring【依赖注入】就是这么简单就行了。
总的对比图:
分别的应用场景:
至于一些小的知识点:
上面这些小知识点比较少情况会用到,这也不去讲解啦。知道有这么一回事,到时候查查就会用啦~~~
将SpringIOC相关知识点整理了一遍,要想知道哪些知识点是比较重要的。很简单,我们去找找相关的面试题就知道了,如果该面试题是常见的,那么说明这个知识点还是相对比较重要的啦!
以下的面试题从各种博客上摘抄下来,摘抄量较大的会注明出处的~
什么是spring?
Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用。Spring框架目标是简化Java企业级应用开发,并通过POJO为基础的编程模型促进良好的编程习惯。
使用Spring框架的好处是什么?
Spring由哪些模块组成?
简单可以分成6大模块:
BeanFactory 实现举例
Bean工厂是工厂模式的一个实现,提供了控制反转功能,用来把应用的配置和依赖从正真的应用代码中分离。
在spring3.2之前最常用的是XmlBeanFactory的,但现在被废弃了,取而代之的是:XmlBeanDefinitionReader和DefaultListableBeanFactory
什么是Spring的依赖注入?
依赖注入,是IOC的一个方面,是个通常的概念,它有多种解释。这概念是说你不用创建对象,而只需要描述它如何被创建。你不在代码里直接组装你的组件和服务,但是要在配置文件里描述哪些组件需要哪些服务,之后一个容器(IOC容器)负责把他们组装起来。
有哪些不同类型的IOC(依赖注入)方式?
哪种依赖注入方式你建议使用,构造器注入,还是 Setter方法注入?
你两种依赖方式都可以使用,构造器注入和Setter方法注入。最好的解决方案是用构造器参数实现强制依赖,setter方法实现可选依赖。
什么是Spring beans?
Spring beans 是那些形成Spring应用的主干的java对象。它们被Spring IOC容器初始化,装配,和管理。这些beans通过容器中配置的元数据创建。比如,以XML文件中
的形式定义。
这里有四种重要的方法给Spring容器提供配置元数据。
解释Spring框架中bean的生命周期
解释不同方式的自动装配
只用注解的方式时,注解默认是使用byType的!
IOC的优点是什么?
IOC 或 依赖注入把应用的代码量降到最低。它使应用容易测试,单元测试不再需要单例和JNDI查找机制。最小的代价和最小的侵入性使松散耦合得以实现。IOC容器支持加载服务时的饿汉式初始化和懒加载。
哪些是重要的bean生命周期方法? 你能重载它们吗?
有两个重要的bean 生命周期方法,第一个是setup
, 它是在容器加载bean的时候被调用。第二个方法是 teardown
它是在容器卸载类的时候被调用。
The bean 标签有两个重要的属性(init-method
和destroy-method
)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct
和@PreDestroy
)。
怎么回答面试官:你对Spring的理解?
来源:
下面我就截几个答案:
一、
二、
Spring框架中的单例Beans是线程安全的么?
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。
最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”
FileSystemResource和ClassPathResource有何区别?
在FileSystemResource 中需要给出spring-config.xml文件在你项目中的相对路径或者绝对路径。在ClassPathResource中spring会在ClassPath中自动搜寻配置文件,所以要把ClassPathResource文件放在ClassPath下。
如果将spring-config.xml保存在了src文件夹下的话,只需给出配置文件的名称即可,因为src文件夹是默认。
简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。
这篇文章主要是补充和强化一些比较重要的知识点
结合《Spring 实战 (第4版)》和《精通Spring4.x 企业应用开发实战》两本书的AOP章节将其知识点整理起来~
AOP称为面向切面编程,那我们怎么理解面向切面编程??
我们可以先看看下面这段代码:
我们学Java面向对象的时候,如果代码重复了怎么办啊??可以分成下面几个步骤:
抽取成类的方式我们称之为:纵向抽取
但是,我们现在的办法不行:即使抽取成类还是会出现重复的代码,因为这些逻辑(开始、结束、提交事务)依附在我们业务类的方法逻辑中!
现在纵向抽取的方式不行了,AOP的理念:就是将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中!
上面的图也很清晰了,将重复性的逻辑代码横切出来其实很容易(我们简单可认为就是封装成一个类就好了),但我们要将这些被我们横切出来的逻辑代码融合到业务逻辑中,来完成和之前(没抽取前)一样的功能!这就是AOP首要解决的问题了!
被我们横切出来的逻辑代码融合到业务逻辑中,来完成和之前(没抽取前)一样的功能
没有学Spring AOP之前,我们就可以使用代理来完成。
其实Spring AOP的底层原理就是动态代理!
来源《精通Spring4.x 企业应用开发实战》一段话:
Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。
来源《Spring 实战 (第4版)》一句话:
Spring AOP构建在动态代理基础之上,因此,Spring对AOP的支持局限于方法拦截。
在Java中动态代理有两种方式:
JDK动态代理是需要实现某个接口了,而我们类未必全部会有接口,于是CGLib代理就有了~~
那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:
原因:
看到这里我们就应该知道什么是Spring AOP(面向切面编程)了:将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。
AOP除了有Spring AOP实现外,还有著名的AOP实现者:AspectJ,也有可能大家没听说过的实现者:JBoss AOP~~
我们下面来说说AspectJ扩展一下知识面:
AspectJ是语言级别的AOP实现,扩展了Java语言,定义了AOP语法,能够在编译期提供横切代码的织入,所以它有专门的编译器用来生成遵守Java字节码规范的Class文件。
而Spring借鉴了AspectJ很多非常有用的做法,融合了AspectJ实现AOP的功能。但Spring AOP本质上底层还是动态代理,所以Spring AOP是不需要有专门的编辑器的~
嗯,AOP搞了好几个术语出来~~两本书都有讲解这些术语,我会尽量让大家看得明白的:
连接点(Join point):
切点(Poincut):
增强/通知(Advice):
织入(Weaving):
增强/通知
添加到目标类的具体连接点上的过程。引入/引介(Introduction):
引入/引介
允许我们向现有的类添加新方法或属性。是一种特殊的增强!切面(Aspect):
增强/通知
组成,它既包括了横切逻辑的定义、也包括了连接点的定义。在《Spring 实战 (第4版)》给出的总结是这样子的:
通知/增强包含了需要用于多个应用对象的横切行为;连接点是程序执行过程中能够应用通知的所有点;切点定义了通知/增强被应用的具体位置。其中关键的是切点定义了哪些连接点会得到通知/增强。
总的来说:
Spring提供了3种类型的AOP支持:
@AspectJ
注解驱动的切面
这部分配置比较麻烦,用起来也很麻烦,这里我就主要整理一下书上的内容,大家看看了解一下吧,我们实际上使用Spring AOP基本不用这种方式了!
首先,我们来看一下增强接口的继承关系图:
可以分成五类增强的方式:
Spring提供了六种的切点类型:
切面类型主要分成了三种:
一般切面,切点切面,引介/引入切面介绍:
对于切点切面我们一般都是直接用就好了,我们来看看引介/引入切面是怎么一回事:
继承关系图:
引介/引入切面有两个实现类:
实际上,我们使用AOP往往是Spring内部使用BeanPostProcessor帮我们创建代理。
这些代理的创建器可以分成三类:
对应的类继承图:
嗯,基于代理的经典SpringAOP就讲到这里吧,其实我是不太愿意去写这个的,因为已经几乎不用了,在《Spring 实战 第4版》也没有这部分的知识点了。
Spring在新版本中对AOP功能进行了增强,体现在这么几个方面:
那我们使用@AspectJ
来玩AOP的话,学什么??其实也就是上面的内容,学如何设置切点、创建切面、增强的内容是什么…
具体的切点表达式使用还是前往:Spring【AOP模块】就这么简单看吧~~
对应的增强注解:
其实前置啊、后置啊这些很容易就理解了,整篇文章看下来就只有这个引介/引入切面有点搞头。于是我们就来玩玩吧~
我们来看一下具体的用法吧,现在我有个服务员的接口:
public interface Waiter {
// 向客人打招呼
void greetTo(String clientName);
// 服务
void serveTo(String clientName);
}
一位年轻服务员实现类:
public class NaiveWaiter implements Waiter {
public void greetTo(String clientName) {
System.out.println("NaiveWaiter:greet to " + clientName + "...");
}
@NeedTest
public void serveTo(String clientName) {
System.out.println("NaiveWaiter:serving " + clientName + "...");
}
}
现在我想做的就是:想这个服务员可以充当售货员的角色,可以卖东西!当然了,我肯定不会加一个卖东西的方法到Waiter接口上啦,因为这个是暂时的~
所以,我搞了一个售货员接口:
public interface Seller {
// 卖东西
int sell(String goods, String clientName);
}
一个售货员实现类:
public class SmartSeller implements Seller {
// 卖东西
public int sell(String goods,String clientName) {
System.out.println("SmartSeller: sell "+goods +" to "+clientName+"...");
return 100;
}
}
此时,我们的类图是这样子的:
现在我想干的就是:借助AOP的引入/引介切面,来让我们的服务员也可以卖东西!
我们的引入/引介切面具体是这样干的:
@Aspect
public class EnableSellerAspect {
@DeclareParents(value = "com.smart.NaiveWaiter", // 指定服务员具体的实现
defaultImpl = SmartSeller.class) // 售货员具体的实现
public Seller seller; // 要实现的目标接口
}
写了这个切面类会发生什么??
是不是很神奇??我也觉得很神奇啊,我们来测试一下:
我们的bean.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<aop:aspectj-autoproxy/>
<bean id="waiter" class="com.smart.NaiveWaiter"/>
<bean class="com.smart.aspectj.basic.EnableSellerAspect"/>
</beans>
测试一下:
public class Test {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("com/smart/aspectj/basic/beans.xml");
Waiter waiter = (Waiter) ctx.getBean("waiter");
// 调用服务员原有的方法
waiter.greetTo("Java3y");
waiter.serveTo("Java3y");
// 通过引介/引入切面已经将waiter服务员实现了Seller接口,所以可以强制转换
Seller seller = (Seller) waiter;
seller.sell("水军", "Java3y");
}
}
具体的调用过程是这样子的:
当引入接口方法被调用时,代理对象会把此调用委托给实现了新接口的某个其他对象。实际上,一个Bean的实现被拆分到多个类中
我们知道注解很方便,但是,要想使用注解的方式使用Spring AOP就必须要有源码(因为我们要在切面类上添加注解)。如果没有源码的话,我们就得使用XML来声明切面了~
其实就跟注解差不多的功能:
我们就直接来个例子终结掉它吧:
首先我们来测试一下与传统的SpringAOP结合的advisor是怎么用的:
实现类:
xml配置文件:
…
一个一个来讲解还是太花时间了,我就一次性用图的方式来讲啦:
最后还有一个切面类型总结图,看完就几乎懂啦:
看起来AOP有很多很多的知识点,其实我们只要记住AOP的核心概念就行啦。
下面是我的简要总结AOP:
下面的文章都有对应的原创精美PDF,在持续更新中,可以来找我催更~
如果大家想要实时关注我更新的文章以及分享的干货的话,微信搜索Java3y。
PDF文档的内容均为手打,有任何的不懂都可以直接来问我(公众号有我的联系方式)。
我是三歪,一个想要变强的男人,感谢大家的点赞收藏和转发,下期见。