0. 简介
本文用于记录本人的Spring学习内容以及部分自己对于工厂模式和代理模式的理解,参考资料源自以下:
JAVA设计模式之工厂模式(三种工厂模式)-阿里云开发者社区 (aliyun.com)
设计模式(四)——搞懂什么是代理模式 - 知乎 (zhihu.com)
静态代理和动态代理有什么区别?–乐字节java - 知乎 (zhihu.com)
B站狂神说Java——Spring相关视频
可能有部分理解不到位的地方,不喜勿喷。
1. 工厂模式:用于对象的创建,使得客户从具体的产品对象中被解耦;简单理解就是对象的创建不通过程序本身来决定,而是由客户来决定。
-
简单工厂模式:实际上不能算作一种设计模式,它引入了创建者的概念,将实例化的代码从应用代码中抽离,在创建者类的静态方法只处理创建对象的细节,参考如下代码:
public class SimpleFactory{ public static Object getInstance(String type){ if(type.equals("ClassA")) return new ClassA(); else if(type.equals("ClassB")) return new ClassB(); else if(type.equals("ClassC")) return new ClassC(); return null; } }
这里的ClassA,ClassB,ClassC都是同一类产品,可以是实现了同一接口的类,也可以是继承了同一父类的类,返回类型为Object只是因为我没有写这个接口和父类。简单工厂强调的是,将对象的业务与创建分离开来,即,ClassA,ClassB,ClassC的业务与创建。
在简单工厂中,如果有新增了产品,则需要对工厂类的静态方法进行改动,不符合开闭原则。
-
工厂方法模式:定义了一个创建对象的接口,但是由子类决定要实例化的类是哪一个,工厂方法把类的实例化推迟到了子类。
应用场景:同样是生产泡面的工厂,合味道生产合味道的泡面,康师傅生产康师傅的泡面,在这里面,合味道和康师傅这两个品牌,属于抽象工厂的两个子类,他们生产的都是泡面。
public abstract class PaoMianFactory{ public abstract PaoMian createPaoMian(); } public class HeWeiDao extends PaoMianFactory{ @Override public PaoMian createPaoMian(){ return new HeWeiDaoPaoMian(); } } public class KangShiFu extends PaoMianFactory{ @Override public PaoMian createPaoMian(){ return new KangShiFuPaoMian(); } } //在这里面,PaoMian也是一个抽象类 //KangshifuPaoMian和HeWeiDaoPaoMian都是PaoMian的子类
相比于简单工厂,工厂方法模式在增加了产品的情况下,不需要修改原有的代码,符合开闭原则,但是相关类的代码文件量直线上升,新增一个产品需要添加定义两个新的类,很不方便。
-
抽象工厂模式:在工厂方法的基础上进行扩展,假设,合味道工厂除了生产泡面,还生产饮料,康师傅工厂也是一样。那么此时,根据共产方法模式,我们一共需要新增6个类(1个饮料抽象类,2个饮料子类,1个工厂抽象类,2个工厂子类),此时工厂方法的弊端就体现出来了。因此,我们在工厂方法的基础上,套用简单工厂,形成了抽象工厂模式。
即,工厂抽象类不仅要生产泡面还要生产饮料(新增一个生产饮料的方法),此时我们就不需要再去写新的工厂类,节省了一半的代码。从康师傅的角度来看,康师傅工厂既生产了泡面,又生产了饮料,和简单工厂很相似,只是从传入不同的参数变成了调用不同的方法;从多个工厂的角度来看,和工厂方法很相似。因此,抽象工厂模式对于新增产品品牌,是开放的,对于新增产品本身,则是关闭的。
2. 代理模式:为其他对象提供一种代理以控制对这个对象的访问
在上图中,Subject是一个抽象类或者接口,RealSubject是实现方法类,实现了具体的业务执行,Proxy则是RealSubject的代理,直接和Client接触,即RealSubject不和客户接触。代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强,想要达到这个功能,代理类与被代理类应该共同实现同一个接口或者共同继承某个类,在代理类中选择调用被代理类的实现方法从而实现拓展。
-
静态代理:以租房为例,我们一般使用租房软件、找中介或者找房东,这里的中介就是代理者,需要定义一个提供了租房方法的接口,租房的实现类,以及中介的类:
//租房接口 public interface IRentHouse{ void rentHouse(); } //租房的实现类 public class RentHouse implements IRentHouse{ @Override public void rentHouse(){ System.out.println("租了一间房子……"); } } //中介的类 public class Proxy implements IRentHouse{ private IRentHouse rentHouse; public IRentHouse(RentHouse rentHouse){ this.rentHouse=rentHouse; } @Override public void rentHouse(){ System.out.println("交中介费"); rentHouse.rentHouse(); System.out.println("中介负责维修管理"); } }
定义好之后,通过客户端调用中介类Proxy即可实现租房的功能。在静态代理中,代理与被代理的角色是固定的,如果说存在20个业务,如果要对这20个业务都实现代理,那么就需要创建20个代理角色,与之前的工厂方法模式一样存在着类似的弊端,于是就催生了动态代理。
-
动态代理:相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。
动态代理的两种实现方式:
- JDK动态代理
- CGLIB动态代理
特点:
- 代理目标对象不固定
- 在应用程序执行时动态地创建目标对象
- 代理对象会增强目标对象的行为
本文介绍一下JDK动态代理的实现,CGLIB不谈。JDK动态代理需要实现InvocationHandler接口,此接口是JDK提供的动态代理接口,对被代理的方法提供代理。其中Invoke方法是接口InvocationHandler定义必须实现的,它完成了对真实方法的调用。
//中介的类需要做出相应的修改 public class InterMediaryProxy implements InvocationHandler{ private Object obj; public InterMediaryProxy(Object object){ this.object=object; } @Override public Object invoke(Object proxy, Method method, Object[] args)throws Throwable{ System.out.println("代理前"); Object result = method.invoke(this.obj,args); System.out.println("代理后"); return result; } }
//相关测试代码: public static void main(String[] args){ IRentHouse rentHouse = new RentHouse(); //定义一个Handler InvocationHandler handler = new InterMediaryProxy(rentHouse); //获得类的class loader ClassLoader classLoader = rentHouse.getClass().getClassLoader(); //动态生成一个代理者 IRentHouse proxy = (IRentHouse) Proxy.newProxyInstance (classLoader,new class[]{IRentHouse.class},handler); proxy.rentHouse(); }
在这里我们使用了Proxy的类方法,代码如下:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) //loader:类加载器,interfaces:代码用来代理的接口,即需要被代理的接口 //handler:一个InvocationHandler对象,即我们自定义的动态代理类
JDK动态代理是针对于接口进行代理的,所以需要编写的接口类仍旧需要编写,其实现子类的相关代码也要编写,只是相比起静态代理,不需要去编写代理类的相关代码,只要拓展的操作是相同的,那么只需要使用同一个动态代理类实现代理即可。实现业务的真正方法是,动态代理类的invoke方法。
动态代理是否要求,接口只能有一种业务?
3. Spring是什么?
Spring是一个轻量级控制反转(IOC)和面向切面(AOP)的容器框架。
4. Spring导包:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
5. 什么是IOC容器?
-
IOC容器是指在Spring中负责创建对象,管理对象(依赖注入、装配对象、配置对象并且管理这些对象的整个生命周期)的容器;
-
其中,IOC是指控制反转,是一种设计思想。在Java开发中,传统设计思路,是由程序主动去创建依赖对象;而在控制反转的设计思想中,有一个专门的容器来创建这些对象,在这里我们简称其为IOC容器,同时,IOC容器也负责管理依赖对象的生命周期;
-
控制反转是指创建对象的这一个过程,交给了容器来做,而程序本身只是被动地接受已经创建好的对象,相比较于传统的开发,这个过程被反转了,因此称为控制反转;
-
有点类似于工厂方法模式,将对象创建交托给工厂来做,而程序本身只负责接收以及使用,但是这两者之间还是存在区别的,只是这样类比可能方便理解;
-
使用IOC的好处:
- 实现了组件之间的解耦,提高了程序的灵活性和可维护性;
使用IOC的缺点:
- 创建对象的步骤变复杂了,不直观;
- 使用了反射来创建对象,所以在效率上有所损耗,但对于程序的灵活性和可维护性而言,消耗所带来的损失比较小;
- 如果修改了相关的类名,需要到XML配置文件中修改相应的设置。
6. Spring IOC怎么使用
-
编写一个Hello实体类
public class Hello{ private String name; public String getName(){ return name; } public void setName(String name){ this.name=name; } public void show(){ System.out.println("Hello"+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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="hello" class="xxx.xxx.xxx.Hello"> <property name="name" value="Spring"></property> </bean> </beans>
-
实例化容器并取得目标对象:
//实例化容器 ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml"/*,"services.xml", "daos.xml"*/); //这里可以一次性读取多个配置文件 Hello hello = (Hello)context.getBean("hello"); hello.show(); //此时控制台就会输出HelloSpring
-
在Spring的xml配置文件中:
- bean标签代表的是对象,其中id属性代表的是变量名,class属性则是要创建对象的类名;
- property标签相当于对象中的属性,name属性是变量名,value属性是要注入的值;
- 如果自定义类中的某个属性也是自定义类,此时value属性是没有用的,应该使用ref属性引用某个已经被创建的bean;
- 如果要将自定义的类交给SpringIOC管理,需要给相应的属性都设置set方法,因为在SpringIOC中默认的注入方法是通过set注入的,如果没有设置相应的方法,则会报错。
7. IOC创建对象的方式
-
默认情况下,走无参构造方法,通过设置property和相应的set方法将属性注入;
-
如果设置了有参构造的话,可以通过有参构造来实现对象的创建:
-
配合索引使用:
<bean id="xxx" class="xxx.xxx.xxx.Object"> <constructor-arg index="0" value="xxx" /> <constructor-arg index="1" value="xxx" /> </bean>
-
配合类型使用:如果要使用这种方式,不允许构造方法中存在相同的类型参数
<bean id="xxx" class="xxx.xxx.xxx.Object"> <constructor-arg type="int" value="xxx" /> <constructor-arg type="String" value="xxx" /> </bean>
-
配合参数名使用:
<bean id="xxx" class="xxx.xxx.xxx.Object"> <constructor-arg name="argFir" value="xxx" /> <constructor-arg name="argSec" ref="xxx" /> <!--这里和之前说的一样,遇到了自定义类填充的属性,使用ref属性--> </bean>
-
-
Spring创建的对象,默认情况下,都是唯一的,或者说,在Spring中注册的类只能创建一个实例(单例模式,可以通过设置更改);
8. Spring配置文件的标签说明:
-
bean标签:负责实例化类;
-
import标签:负责导入其他的子配置文件,通过统一管理,在客户端只需要读取总配置文件即可;
<import resource="beans.xml" />
-
alias标签:为bean设置别名;
9. DI(Dependency Injection——依赖注入):bean对象的创建依赖于容器,bean对象中的所有属性由容器来注入。
Set注入的复杂对象分析:
-
List——<list />
<property name="someList"> <list> <value>xxx</value> <ref bean="xxx" /> </list> </property>
-
Set——<set />
<property name="someSet"> <set> <value>xxx</value> <ref bean="xxx" /> </set> </property>
-
Map——<map />
<property name="someMap"> <map> <entry key="an entry" value="just some string"></entry> <entry key="a ref" value-ref="xxx"></entry> </map> </property>
-
Properties——<props />
<property name="adminEmails"> <prop key="adminstrastor">xxx</prop> <prop key="support">[emailprotected]</prop> <prop key="development">[emailprotected]</prop> </property>
-
Arrays——<array />
<property name="arrays"> <array> <value>xxx</value> <ref bean="xxx" /> </array> </property>
-
如果想实现null的注入,有一个标签是<null />,使用这个标签表示空指针注入
-
p命名空间注入:如果想要使用p命名空间需要导入约束
xmlns:p="http://www.springframework.org/schema/p"
<!--使用p命名空间的情况下,会自动识别当前注入类有哪些属性,从而简化配置文件--> <!--假设类User有属性name,age,gender,可以通过以下方式实现注入--> <bean id="user" class="xxx.xxx.xxx.User" p:name="张三" p:age="18" p:gender="男"></bean> <!--对于简单的属性可以使用p命名空间注入,p--》property,p命名空间也可以实现引用-->
-
c命名空间注入:如果想要使用c命名空间需要导入约束(constructor)
xmlns:c="http://www.springframework.org/schema/c"
<bean id="user" class="xxx.xxx.xxx.User" c:name-ref="beanName" c:age-ref="beanAge" c:gender-ref="beanGender"></bean> <!--这里面都是使用引用的,如果是简单类型的可以直接注入String,即不用加上-ref--> <!--c命名空间和p命名空间的区别在于,c命名空间是通过构造器注入的,p命名空间是通过Set注入的-->
Bean的作用域:scope属性——singleton,prototype,request,session,application,websocket
- singleton(默认作用域):同一个类型创建的bean对象是一样的(即同一块地址)
- prototype(原型模式):即使是同一个bean对象,每一个getBean都会分配一个新的,即产生新的独立的对象
- 其余的在Web开发中使用
10. Bean的自动装配:我们先假设一个环境,分别有三个类:People,Cat,Dog
public class People{
private String name;
private Dog dog;
private Cat cat;
//省略GetterAndSetter
}
public class Cat{
public void shout(){
System.out.println("miao~");
}
}
public class Dog{
public void shout(){
System.out.println("wang!");
}
}
相应的bean配置文件如下:
<bean id="dog" class="xxx.xxx.xxx.Dog"></bean>
<bean id="cat" class="xxx.xxx.xxx.Cat"></bean>
<bean id="people" class="xxx.xxx.xxx.People">
<property name="name" value="张三" />
<property name="cat" ref="cat" />
<property name="dog" ref="dog" />
</bean>
如上所示,虽然cat和dog我们在配置文件的前面已经显式地声明过了,但是在装配People类的bean的时候,我们还是需要再配置一遍。然而通过使用autowire属性我们可以省略掉重复的配置:
<bean id="dog" class="xxx.xxx.xxx.Dog"></bean>
<bean id="cat" class="xxx.xxx.xxx.Cat"></bean>
<bean id="people" class="xxx.xxx.xxx.People" autowire="byName">
<property name="name" value="张三" />
</bean>
<!--
使用autowire="byName",Spring会自动查找并进行配置,但是有一个限制条件,
就是已经配置好的bean的id属性必须和自定义类中的相应的set方法后面的名字保持一致,否则会报错
-->
<!--
autowire="byType"则会自动检查类型然后进行适配,但是如果在装配类中有多个相同类型的属性字段,
即People中有多个Dog属性,或者已经定义的bean中有多个属于Dog类的bean,
那么装配会失败,Spring会报错
-->
11. 在Java中使用注解实现自动装配:
-
导入支持:在相应的配置文件下导入对注解的支持
<beans xmlns="……………………………………" xmlns:xsi="……………………………………" 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"> <context:annotation-config /> </beans>
-
在需要实现自动装配的字段上添加注解:
public class People{ @Autowired private Cat cat; //使用了注解自动配置的情况下,甚至可以不用设置set方法 //因为注解的底层是通过反射实现的,也可以在set方法上用注解,效果一样 //但是使用注解的情况需要保证bean的id与属性名保持一致 //或者类型唯一,将byName,byType的索引方式融合了 @Autowired @Qualifier(value="beanid") private Dog dog; //在byName匹配不上且byType类型不唯一时,可以通过添加@Qualifier注解的方式 //指定目标bean的id,以此实现自动装配 private String name; //java自带的注解@Resource也可以实现和@Autowired一样的效果 //在注解@Resource中,也有指定beanid的属性,@Resource(name="beanid") //@Autowired先byType在byName,@Resource则相反,且@Resource没有提示 }
12. Spring注解开发:
-
导入AOP的包(因为注解的后台实现用到了AOP编程),这里在webmvc中的包已经包含了AOP的包;
-
增加约束:
<beans xmlns="……………………………………" xmlns:xsi="……………………………………" 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"> <context:annotation-config /> </beans>
-
指定要自动扫描的包,这个包下的注解就会生效:
<beans ……………………………………> …………………………………… <context:component-scan base-package"xxx.xxx.xxx" /> </beans>
-
注解类型:
-
@Component:在Java代码层面将这个类注册到Spring容器中,默认beanid是类名小写
@Component("user") //beanid也可以通过这种方式显式声明 public class User{ …………………… }
@Component衍生注解:(效果一样只是为了区分SpringMVC中的三层架构)
- Mapper:@Repository
- Service:@Service
- Controller:@Controller
-
@Value:在属性字段为属性赋值,也可以设置在set方法上,效果是一样的
@Component public class User{ @Value("张三") public String name; @Value("张三") public void setName(String name){ this.name=name; } }
-
@Autowired:上文已经描述
-
@Scope:设置bean的作用域
@Component @Scope("singleton") public class User{ …………………………………… }
-
13. 使用JavaConfig实现配置:前面讲的都是如何使用注解完成对于bean在Spring中的注册,以及相关的属性注入。而在Spring中,也可以实现自定义类搭配注解替代配置文件,具体代码如下:
@Component("user")
public class User{
@Value("张三")
private String name;
}
@Configuration
@ComponentScan("xxx.xxx.pojo")
//自动扫描pojo包下的注解
@Import(xxx.class)
//导入另外一个配置文件
public class JavaConfig{
@Bean
public User user(){
return new User();
}
//注册一个bean,相当于我们的bean标签
//方法名字相当于beanid
//方法返回值相当bean中的class属性
}
//这里一开始参考资料是没有加上自动扫描的注解的,然后方法名用的是getUser
//测试的时候也是用的getUser,试过getBean传参数"user"但是无法获取
//个人认为这里面因为没有加上自动扫描的注解,所以一开始原先那个类没有注册到Spring中,或者说自定义的JavaConfig中
//后面虽然加上了自动扫描的注解但是方法名也改成了user,所以无法检测到Sping中是有一个bean还是两个bean
//建议自己敲一遍代码确认一下
public void static main(String[] args){
ApplicationContext context = new AnnotationConfigApplicationContext(JavaConfig.class);
//这里因为是使用了注解,所以上下文获取要用AnnotationConfigApplicationContext来获取
User user = (User)context.getBean("user");
System.out.println(user.getName());
}
14. AOP:面向切面编程,核心是动态代理。使用AOP织入,需要导入一个依赖包:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
同时在相应的配置文件中写好约束:
<beans xmlns="……………………………………"
xmlns:xsi="……………………………………"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="……………………………………
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
……………………………………
</beans>
实现AOP的方式:
-
使用Spring的API接口:自定义多个类分别实现自己想要的前置功能和后置功能,然后在Spring中注册,最后使用aop标签进行包装;
public interface UserService{ public void add(); public void delete(); }
public class UserServiceImpl implements UserService{ public void add(){ …………………………………… } public void delete(){ …………………………………… } }
//自定义的类实现SpringAOP的相关接口 public class Log implements MethodBeforeAdvice{ //实现before方法 public void before(Method method, Object[] args, Object target)throws Throwable{ //method——被切入的方法 //args——动态执行时传入的参数 //target——执行方法的对象 System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了"); } } //这里的MethodBeforeAdvice应该是指方法被执行前 //而BeforeAdvice应该是已经进入了方法但是方法体执行前
public class AfterLog implements AfterReturningAdvice{ public void afterReturning(Object returnValue, Method method, Object[] args, Object target)throws Throwable{ System.out.println(………………………………); //这里比前面的方法多了一个参数,returnValue,是方法调用后的返回参数 } }
<bean id="userServiceImpl" class="xxx.xxx.xxx.UserServiceImpl" /> <bean id="log" class="xxx.xxx.xxx.Log" /> <bean id="afterLog" class="xxx.xxx.xxx.AafterLog" /> <aop:config> <!--设置切入点--> <aop:pointcut id="pointcut" expression="execution(* xxx.xxx.service.UserServiceImpl.*(..))"></aop:pointcut> <!--绑定切入点与切入内容--> <aop:advisor advice-ref="log" pointcut-ref="pointcut" /> <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut" /> <!--这里的切入内容放置顺序,由实现类的实现接口决定--> </aop:config>
这里面的execution表达式有以下语法格式:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)
-
除了返回类型模式,方法名模式和参数模式,其他两个可要可不要。而方法名模式中,需要写清楚该方法属于哪个包下的哪个类。
-
"…"在方法名模式下表示当前包及其子包,在参数模式下表示任意数量任意参数
-
"*"在方法名模式下表示包下的类名,或者任意前缀后缀,或者方法名,及其前缀后缀
在参数名模式下表示一个任意参数
-
上文的execution表达式表示匹配xxx.xxx.service包下的UserServiceImpl类的所有方法
测试代码:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationConfig.xml"); UserService userService = (UserService)context.getBean("userServiceImpl"); //这里使用接口来接收对象,因为动态代理的对象是接口 userService.add();
-
-
自定义类实现:约束没有上一种那么强
public class DiyPointCut{ public void before(){ …………………………………… } public void after(){ …………………………………… } }
<bean id="diy" class="xxx.xxx.xxx.DiyPointCut"></bean> <aop:config> <!--切面--> <aop:aspct ref="diy"> <aop:pointcut id="pointcut" expression="execution(* xxx.xxx.service.UserServiceImpl.*(..))" /> <aop:before method="before" pointcut-ref="pointcut" /> <aop:after method="after" pointcut-ref="pointcut" /> </aop:aspct> </aop:config>
-
自定义的类,功能没有SpringAPI强大,从SpringAPI中的方法重写会传递一堆与方法有关的参数就可以看出来。
-
注解实现AOP:
-
在配置文件中导入AOP注解支持
<aop:aspectj-autoproxy />
-
实现自定义类:这里的自定义类,资料中作者也把它一起注册到Spring中,但我觉得应该不用注册,没有实际敲代码测试
@Aspect //表示这个类是一个切面 public class AnnotationPointCut{ @Before("execution(* xxx.xxx.service.UserServiceImpl.*(..))") public void before(){ …………………………………… } @After("execution(* xxx.xxx.service.UserServiceImpl.*(..))") public void after(){ …………………………………… } @Around("execution(* xxx.xxx.service.UserServiceImpl.*(..))") public void around(ProceedingJoinPoint joinPoint){ …………………………………… //这里的joinPoint是连接点的意思 //这一句代码的作用是让原先的方法正常执行 joinPoint.proceed(); …………………………………… } }
-
15. 整合MyBatis
-
导入依赖:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>
-
配置DataSource和SqlSessionFactory(将数据源和SqlSession创建工厂交给Spring)
<bean id="datasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="…………………………" /> <property name="url" value="………………………………" /> <property name="username" value="………………………………" /> <property name="password" value="………………………………" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="datasource" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> <!--也可以直接在这里绑定Mappers映射--> <property name="mapperLocations" value="classpath:xxx/xxx/mapper/*.xml" /> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <!--SqlSessionFactory注册后则可以完成SqlSession的注册--> <!--SqlSessionTemplate与SqlSession是一样的,某种程度上比SqlSession还强大(线程安全)--> <!--在源代码中,没有设置相应的set方法,这里只能通过构造器注入--> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
-
为什么整合MyBatis了,SqlSession注册到了Spring中,反而要实现Dao层的接口了?直接从Spring中获取SqlSession,然后使用getMapper方法获得目标对象调用其方法不可以吗?
官方文档的建议使用方法:
public class UserMapperImpl implements UserMapper{ private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession){ this.sqlSession = sqlSession; } public List<User> getUser(){ UserMapper mapper=sqlSession.getMapper(UserMapper.class); return mapper.selectUser(); } }
<bean id="userMapperImpl" class="xxx.xxx.xxx.UserMapperImpl"> <property name="sqlSession" ref="sqlSession" /> </bean>
-
整合MyBatis的第二种方式:使用自定义类继承SqlSessionDaoSupport同时实现:
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{ public List<User> getUser(){ UserMapper mapper = getSession().getMapper(UserMapper.class); return mapper.selectUser(); } } //这个SqlSessionDaoSupport内嵌了SqlSession,所以不需要再在自定义类定义SqlSession //因为SqlSessionDaoSupport的SqlSession是通过SqlSessionFactory来初始化的 //所以这里在注册bean的时候要给自定义类注入SqlSessionFactory
<bean id="userMapperImpl" class="xxx.xxx.xxx.UserMapperImpl"> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
16. 声明式事务:MyBatis—Spring允许MyBatis参与到Spring的事务管理中,直接借助Spring的DataSourceTranscationManager实现事务管理,而不用给MyBatis再去创建一个新的专用事务管理器,核心是SpringAOP。(优点在于,只需要关注逻辑代码的生成,而不用关注事务的自动提交关闭再打开这种重复性代码)
```xml
<beans xmlns="……………………………………"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="……………………………………
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="transactionManager" class="org.springframeword.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!--结合AOP实现自定义事务管理-->
<!--这里的tx标签是需要引入约束的-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置相关参数,选择给哪些方法做事务管理-->
<tx:attributes>
<tx:method name="add" propagation="REQUIRED" />
<!--propagation默认情况下就是REQUIRED-->
<tx:method name="*" />
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* xxx.xxx.xxx.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
</aop:config>
<!--通过上面的配置就可以完成声明式事务管理,主要是为了预防在一次Dao连接中的两次操作不全生效的问题-->
```