结构总览
结构图:
基础
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
IOC理论推导
使用:
- 导入jar包,用配置
下面有个Enanble auto import。
思想
之前程序是主动的,执行什么由程序决定;
之后程序是被动的,执行什么看接收了什么;
从本质上解决了问题,程序员不用再去管对象的创建了,系统的耦合性大大降低,可以更专注在业务上的实现!
Dao接口:
public interface UserDao {
void getUser();
}
Dao的两个实现类:
public class UserDaoImpl implements UserDao {
public void getUser() {
System.out.println("默认获取数据");
}
}
public class UserDaoMysqlImpl implements UserDao{
public void getUser() {
System.out.println("Mysql");
}
}
Service接口:
public interface UserService {
void getUser();
}
Service实现类原版:
public class UserServiceImpl implements UserService {
// 写死了
private UserDao userDao = new UserDaoImpl();
public void getUser() {
userDao.getUser();
}
}
原版测试代码:
public class MyTest {
public static void main(String[] args) {
// 每次变化都要改动上面的源码,这里不变
UserServiceImpl userService = new UserServiceImpl();
userService.getUser();
}
}
Service实现类新版:
public class UserServiceImpl implements UserService {
// 新思想,利用set动态注入
private UserDao userDao;
// 如果参数是接口的话,可以传递实现类进去
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
新版测试:
public class UserServiceImpl implements UserService {
// 新思想,利用set动态注入
private UserDao userDao;
// 如果参数是接口的话,可以传递实现类进去
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
IOC本质
使用和不使用的区别:
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入DI
好像就是把对象的创建、赋值都交给了xml文件
bean是利用set进行注入的,类的左边会有叶子标志(点了右上角的config才会有);
如果没有set的话是不能用bean的
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
使用xml配置文件
先把刚才的练习重写一遍,先添加个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="Daoimpl" class="com.kuang.dao.UserDaoImpl" ></bean>
<bean id="Mysqlimpl" class="com.kuang.dao.UserDaoMysqlImpl"></bean>
<bean id="ServiceImpl" class="com.kuang.service.UserServiceImpl">
<!--ref是自己定义的类型, 使用上面bean定义的
value是基本数据类型,是个值-->
<property name="userDao" ref="Mysqlimpl"></property>
</bean>
</beans>
修改测试代码:
public class MyTestBean {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
serviceImpl.getUser();
}
}
HelloSpring:
<?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">
<!--使用Spring创建对象,在Spring中这些都成为Bean
Bean = 对象,相当于new Hello();
class就是要new的对象
property就是个对象中的属性设置一个值
-->
<bean id="hello" class="com.it.Hello">
<!--在这里设置参数-->
<property name="str" value="Spring"/>
</bean>
</beans>
public class MyTest {
public static void main(String[] args) {
// 获取spring的上下文对象
// 使用xml文件专用初始化,可加载多个xml
// 这个加载语句是固定的
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
// 对象现在都在spring中管理,要使用直接从里面取出来就可以了
// 参数为bean的id
// 然后强转一下
Hello hello = (Hello) context.getBean("hello");
System.out.println(hello.getStr());
}
}
public class Hello {
private String str;
public void setStr(String str) {
this.str = str;
}
@Override
public String toString() {
return "Hello{" +
"str='" + str + '\'' +
'}';
}
public String getStr() {
return str;
}
}
IOC创建对象方式
- 使用类的无参构造方法,默认就是无参,此时如果没有无参构造函数,就会出错
- 有参
- 下标
- 参数类型
- 参数名
容器就像婚介所,配置文件加载的时候,容器中管理的对象就就已经初始化了。
而且容器的对象都是一个,get后是同一个
配置文件:
<?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="User" class="com.it.User">-->
<!--<constructor-arg index="0" value="第一种,index"></constructor-arg>-->
<!--</bean>-->
<!--第二种-->
<!--<bean id="User" class="com.it.User">-->
<!--<constructor-arg type="java.lang.String" value="第二种,参数类型"></constructor-arg>-->
<!--</bean>-->
<!--第三种-->
<bean id="User" class="com.it.User">
<constructor-arg name="name" value="第三种,参数名"></constructor-arg>
</bean>
<bean id="UserT" class="com.it.UserT"></bean>
</beans>
测试代码:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("User");
User user2 = (User) context.getBean("User");
System.out.println("if equal ? : "+ (user==user2));
user.show();
}
}
Spring配置
alisa
别名
<alias name="User" alias="hhh"></alias>
使用原来的名字和别名都能调用
bean
简单的那几个不说了,另一种取别名方式,这个更高级,可以同时取多个别名。还可以用多种分割符,如空格,逗号,分号。
<bean id="UserT" class="com.it.UserT" name="u2,u3 u4;u5"></bean>
import
一般用于团队开发使用,可以将多个配置文件,导入合并为一个。
假设有三个人复制不同的类开发,不同的类的注册再不同的bean中,使用import将所有人的bean.xml合并为一个总的
<import resource="beans2.xml"></import>
<import resource="beans3.xml"></import>
DI依赖注入
三种方式
构造器注入
用Set方式注入
依赖: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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.it.Address">
<property name="add" value="北京"></property>
</bean>
<bean id="student" class="com.it.Student">
<!--第一种,普通指注入,直接value-->
<property name="name" value="张三"></property>
<!--第二种,bean注入,ref是bean中注册的-->
<property name="address" ref="address"></property>
<!--数组注入-->
<property name="books">
<array>
<!--这里不用加引号-->
<value>红楼梦</value>
<value>西游记</value>
<value>水浒传</value>
<value>三国演义</value>
</array>
</property>
<!--List注入-->
<property name="hobbys">
<list>
<value>音乐</value>
<value>运动</value>
</list>
</property>
<!--Map注入-->
<property name="card">
<map>
<!--这里用的entry,键值对-->
<entry key="身份证" value="123"/>
<entry key="银行卡" value="345"/>
</map>
</property>
<!--Set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>DNF</value>
</set>
</property>
<!--NULL和空-->
<property name="wife">
<!--自闭和-->
<null/>
</property>
<!--Properties-->
<property name="info">
<props>
<prop key="学号">2075</prop>
<prop key="url">男</prop>
</props>
</property>
</bean>
</beans>
拓展方式
p命名
xmlns:p="http://www.springframework.org/schema/p"
<bean id="User" class="com.it.User" p:name="张三" p:age="12"></bean>
可无参构造
p命名空间注入,可以直接注入属性的值
c命名
需要有参构造器
xmlns:c="http://www.springframework.org/schema/c"
<bean id="User2" class="com.it.User" c:age="12" c:name="李四"></bean>
测试:
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("userbean.xml");
User user = context.getBean("User", User.class);
System.out.println(user.toString());
}
注意:
- 需要导入约束才可以使用p、c命名空间
bean的作用域
singleton单例模式
设置为单例(默认):
<bean id="User2" class="com.it.User" c:age="12" c:name="李四" scope="singleton"></bean>
单线程适合使用,多线程会出错
prototype原型模式
<bean id="User2" class="com.it.User" c:age="12" c:name="李四" scope="prototype"></bean>
从容器中get的两个实例不想等,每次都产生新对象。
比较浪费资源,但是适合多线程
request、seesion、application只能在web开发中使用
Bean的自动装配
自动装配是Spring满足bean依赖的一种方式
在Spring中有三种装配方式
- 在xml中显示的配置
- 在java中显示配置
- 隐式的自动装配【重要】
byName:
省了一些ref
<?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="cat" class="com.it.Cat"></bean>
<bean id="dog" class="com.it.Dog"></bean>
<!--会自动在容器中寻找和set后面的值对应的beanid-->
<bean id="person" class="com.it.People" autowire="byName">
<!--使用了byName就不需要下面这两个了-->
<!--<property name="cat" ref="cat"/>-->
<!--<property name="dog" ref="dog"/>-->
<property name="name" value="张三"></property>
</bean>
</beans>
byType:
通过类型来省ref
<?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">
<!--此时这边可以不加id了-->
<bean class="com.it.Cat"></bean>
<bean class="com.it.Dog"></bean>
<!--byType自动找类型,但是要保证类型全局为一-->
<bean id="person" class="com.it.People" autowire="byType">
<property name="name" value="张三"></property>
</bean>
</beans>
小结:
- byName要保证bean的id唯一,并且bean需要和自动注入的属性的set方法的值一致(有点搞不懂,小写可以,大写就不可以了)
- byType要保证所有bean的class唯一,并且这个bean需要和自动注入的属性类型一致
使用注解实现自动装配
Autowired和Qualifirer
注意:
- 导入约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/>
<bean id="cat" class="com.it.Cat"/>
<bean id="dog" class="com.it.Dog"/>
<bean id="person" class="com.it.People"/>
</beans>
使用Autowired:
package com.it;
import org.springframework.beans.factory.annotation.Autowired;
public class People {
@Autowired
private Dog dog;
@Autowired
private Cat cat;
private String name;
public Dog getDog() {
return dog;
}
public Cat getCat() {
return cat;
}
public String getName() {
return name;
}
}
注意:
- 可以在属性上用,也可以在set上用
- 可以不用set,前提是子弟哦那个装配的属性在IOC容器中存在,且符合名字
这个名字还是挺奇怪的,使用注解的时候ID不符合也可以:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
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/>
<bean id="cat1" class="com.it.Cat"/>
<bean id="Dog" class="com.it.Dog"/>
<bean id="person" class="com.it.People"/>
</beans>
使用Qualifier:
科普:
- 参数加了
@Nullable
,就允许该参数为null @Autowired(required = false)
说明可以为null,否则不可以为空
如果@Autowired自动装配的环境比较复杂, 自动装配无法通过一个注解【@Autowired】完成的时候,我们可以使用@Qualifier(value= "xxx")
来配置@Autowired的使用,指定唯一的bean对象注入。
public class People {
@Autowired
@Qualifier(value="dog222") // 自动装配指定id
private Dog dog;
@Autowired(required=false)
// @Qualifier(value = "dog222") 类型不同也不能指定
private Cat cat;
private String name;
public Dog getDog() {
return dog;
}
public Cat getCat() {
return cat;
}
public String getName() {
return name;
}
}
不需要spring的自动注入
@Resource
先通过名字找,找不到就通过类型找,都找不到才会报错
但是有问题,先不管,主要还是用@Aurowire
二者区别:
- 都是用来自动装配的,都可以放在属性字段上
- @Autowire通过byName方式实现的,而且必选要求这个对象存在,不然就空指针异常了
- @Resource默认通过byName实现,如果找不到就用byType,否则就报错
Spring注解开发
在Spring4之后,使用注解必须导入
类直接
指定要扫描的包,在这个包下的注解会生效
<context:comonent-scan base-package="com.kuang.pojo">
类加@Component ,此时不使用bean就可调用
// 等价于<bean id="user" class="com.kuang.poojo.User"/>
// 名字默认变小写
@Component
public class User{
}
属性注入
此时属性还没注入,属性注入方法:
@Component
public class User {
// 相当于使用<property name="name" value="李四">
@Value("李四")
public String name;
}
也可在set上写
@Value("王五")
public void setName(String name) {
this.name = name;
}
衍生的注解
这四个都一样,都是将类放到容器中,只是位置不同表示不同
// controller包用的
@Controller
public class UserController {
}
// Service层常用的注解
@Service
public class UserService {
}
// 仓库的意思
// 一般Dao层用这个标注
@Repository
public class UserDao {
}
@Component
public class User {
// 相当于使用<property name="name" value="李四">
@Value("李四")
public String name;
}
自动配置注解
就Autowired那几个
作用域
@Scope控制,直接用类上的注解实现
@Scope("prototype")
@Scope("singleton")
public class UserDao {
}
小结
xml与注解:
- xml万能,适用于任何场合,维护简单方便
- 注解不是自己的类使用不了,要scan;维护相对复杂,每个类里面都要改
最佳实践:
- xml用来管理bean
- 注解只负责完成属性的注入
- 我们在使用的过程中只需要注意一个问题:必须让注解生效,就需要开启直接的支持:
<context:component-scan base-package="com.it.pojo"/> 控制扫描的包
<context:annotation-config/> 开启注解使用
基于JAVA类的配置
用java类配置替代xml配置。
@componentScan()
@Import
代理模式
为什么要学习代理模式?
代理模式的分类:
- 静态代理
- 动态代理
静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类
- 真实角色:被代理的角色
- 代理角色:代理真是角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
代码步骤:
- 接口
- 真实角色
- 代理角色
- 客户端访问代理角色
代理模式好处:
- 可以使真实角色的操作更加纯村,不用去关注一些公共的业务
- 公共也就交给代理角色,实现了业务的分工
- 公共业务发生拓展的时候,方便集中管理
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
租房案例:
// 出租接口
public interface Rent {
void rent();
}
// 房东类
public class Host implements Rent{
public void rent() {
System.out.println("房东有房要出租");
}
}
// 中介类
public class Proxy implements Rent{
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
public void rent() {
host.rent();
hetong();
feiyong();
}
private void hetong(){
System.out.println("跟中介签合同");
}
private void feiyong(){
System.out.println("中介收取中介费");
}
}
// 客户
public class Client {
public static void main(String[] args) {
// 一个房东出现了!
Host host = new Host();
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
通过中介来完成租房事件,全程要租的人和有房子的人都没有接触。
不改变对象的情况下,使用代理模式很方便,可以不改变业务的源码,只改变代理类中的代码即可
代理模式就是用set接收一个对象,然后对这个对象操作,
// 改动原有的代码,在公司中是大忌
拓展时方便集中管理的例子:
// 方法接口
public interface UserService {
void add();
void del();
void update();
void query();
}
// 方法实现类
public class USImpl implements UserService {
public void add() {
System.out.println("add");
}
public void del() {
System.out.println("adeldd");
}
public void update() {
System.out.println("update");
}
public void query() {
System.out.println("query");
}
}
// 代理,也实现了方法接口
public class USProxy implements UserService{
private USImpl us;
public void setUs(USImpl us) {
this.us = us;
}
public USProxy() {
}
// 包装一下
public void add() {
show();
us.add();
}
public void del() {
show();
us.del();
}
public void update() {
show();
us.update();
}
public void query() {
show();
us.query();
}
// 添加新功能
private void show(){
System.out.println("新功能!");
}
}
// 客户
public class Client {
public static void main(String[] args) {
USImpl us = new USImpl();
USProxy usProxy = new USProxy();
usProxy.setUs(us);
usProxy.add();
}
}
动态代理
底层都是反射
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口—JDK动态代理
- 基于类:cglib
+java字节码实现:javasist
如何知道要用代理类的那个方法?如歌修改方法?
invoke是跟所有方法相关?一次改好多?
那如何一次改一个?
反射是万能的,可以直接用method.getName( )来获得名字
Proxy用来生活曾带实例,InvocationHandler用来调用方法返回结果
动态代理的好处:
- 包含静态的全部好处
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口
不用每个类都写,直接由模板(invoke)
动态代理工具类:
public class ProxyInv implements InvocationHandler {
// 被代理的接口
private Object target;
// 输入实现类
public void setTarget(Object target) {
this.target = target;
}
// 生成得到代理类实例
// 参数是:实现类自身属性,代理类获取接口,代理实现类本身
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
// 这个方法可以代理实例中的所有方法,进行修改
// 这个方法是接口的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 插入其他功能,方法也会被调用
log(method.getName());
// 万能的反射
Object result = method.invoke(target, args);
return result;
}
public void log(String mst){
System.out.println("执行了" + mst + "方法");
}
}
测试代码:
public class Client {
public static void main(String[] args) {
// 1. 创建真实角色,是个实现类
USImpl us = new USImpl();
// 2. 创建代理角色,现在并不存在实例
ProxyInv proxyInv = new ProxyInv();
// 3. 设置要代理的对象,代理真实角色
proxyInv.setTarget(us);
// 4. 动态生成代理类实例
// 返回的类型是接口类型,调用接口类型的方法是实现类的方法
// 之前手动写代理,代理也是实现的接口,所以代理的方法还是接口中的方法
// 直接返回个接口类型也没毛病,方法还是那些方法,不过方法的实现被改造了
// 每个方法是调用了invoke吧,然后invoke中有修改和原实现类方法
UserService proxy = (UserService) proxyInv.getProxy();
// 5. 利用代理类实例调用方法
proxy.query();
}
}
使用代理类步骤:
- 创建真实角色,是个实现类
- 创建代理角色,现在并不存在实例
- 设置要代理的对象,代理真实角色
- 动态生成代理类实例
- 利用代理类实例调用方法
通过预定义的类,输入真实角色实现类就可进行代理,代理类会自动知道该类实现的接口,然后创建实例,实例类型就是接口的类型,然后调用方法的时候是调用的invoke,里面利用反射来调用实现类的方法,同时还可在invoke中附加操作。
动态的意思,就是类是动态设置的,每次改动传入的参数就能改变
AOP
实现方式:
- 导包
实现方式一:使用Spring的API接口
主要是Spring API的接口实现
两个.表示任意参数,一会的代码会在切入点执行
把log类切入到上面,
步骤:
繁琐的紧呀
接口:
public interface UserService {
public void add();
public void del();
public void update();
public void select();
}
实现类:
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("add");
}
public void del() {
System.out.println("del");
}
public void update() {
System.out.println("update");
}
public void select() {
System.out.println("select");
}
}
Log:
public class Log implements MethodBeforeAdvice {
// method: 要执行的目标对象方法
// objects:参数
// target: 目标对象
// 执行方法前自动调用
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println(target.getClass().getName() + "的" + method.getName() + "方法被执行");
}
}
AfterLog:
// 像让方法在函数调用后被调用 要继承接口
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object returnvalue, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("已经执行方法: " + method.getName() + " ,返回结果为:" + returnvalue);
}
}
AOP配置文件:
<?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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userService" class="com.it.service.UserServiceImpl"/>
<bean id="log" class="com.it.log.Log"/>
<bean id="afterLog" class="com.it.log.AfterLog"/>
<!--配置aop, 导入aop的约束-->
<aop:config>
<!--导入切入点:-->
<!--exectution(要执行的位置 类 方法 参数)-->
<!--*表示所有,类后面的.*表示类中所有方法,..表示所有参数-->
<aop:pointcut id="pointcut" expression="execution(* com.it.service.UserServiceImpl.*(..))"/>
<!--执行环绕增强-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试代码:
public class MyTest9 {
public static void main(String[] args) {
ApplicationContext context= new ClassPathXmlApplicationContext("bean.xml");
// 动态代理代理的是接口
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
}
理解:
- 首先是把三各类都注册了,并没有和接口相关的操作,在使用getBean的时候,对注册的实现类获取接口类型,然后调用接口类型的方法,这个和上面的动态代理是一样的,都是直接调用接口的方法。
- 两个log分别实现了两个接口,都插入到了pointcut中,而这个pointcut执行的位置是实现类,所以该实现类的方法都通过
aop:advisor
和两个log相关了。 - 实现类能被动态代理是因为在
aop:config
中将该实现类导入了切点。
实现方法二
主要是切面定义
自定义优劣:
- 好处是更加方便了,不用实现接口
- 确定是功能不强,无法和方法互动,只是独立的方法(API中的参数有方法和结果相关内容)
步骤:
- 自定义一个普通类,里面有需要的方法
- 配置文件中注册该类
- 创建自定义切片,指向该类的id
- 将需要被使用的地方做成切点
- 使用aop中对应的方法把对应的方法插入切点
自定义类:
public class diy {
public void before(){
System.out.println("=========执行之前========");
}
public void after(){
System.out.println("=========执行之后========");
}
}
配置文件:
<!--方法二,不用API-->
<bean id="diy" class="com.it.diy.diy"/>
<aop:config>
<!--自定义切片, 使用ref指定类-->
<aop:aspect ref="diy">
<!--切点-->
<aop:pointcut id="point" expression="execution(* com.it.service.UserServiceImpl.*(..))"/>
<!--方法-->
<aop:after method="after" pointcut-ref="point"/>
<aop:before method="before" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
测试代码不用改,因为是读取的配置文件
实现方式三:使用注解
步骤:
- 建立一个类,在适当的地方加注解
- 配置文件中注册这个类
- 配置文件中开启注解支持
切面类:
// 标注这是一个切面
@Aspect
public class Annotation {
@Before("execution(* com.it.service.UserServiceImpl.*(..))")
public void befor(){
System.out.println("=====before=====");
}
@After("execution(* com.it.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("=====after=====");
}
@Around("execution(* com.it.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();
// 打印签名,即当前在调用哪个函数, void com.it.service.UserService.add()
System.out.println("签名: " + signature);
Object proceed = jp.proceed(); // 执行方法
System.out.println("环绕后");
}
}
配置文件:
<!--方法三, 注解-->
<!--注册注解类-->
<bean id="annoPointCut" class="com.it.diy.Annotation"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>
整合Mybatis
步骤:
- 导入相关jar包
- junit
- mybatis
- mysql数据库
- spring相关
- aop注入
- mybatis-spring
- 编写配置文件
- 测试
依赖:
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--spring操作数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>
</dependencies>
构建:
最重要的两个东西:一个是SqlSessionFactory
和至少一个数据映射器类。
创建SqlSessionFactory:
在XML配置文件中配置工厂bean,工厂bean需要数据源,数据源是用来连接数据库的
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
上下是一样的,只是一个用xml,一个用java
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
return factoryBean.getObject();
}
将Mapper接口接入Spring:
接口,可以用注解,也可以用xml配置文件:
那要是使用xml会自动读取吗?
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
通过MapperFactoryBean
来加入:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
使用:
获取Mapper:
@Bean
public UserMapper userMapper() throws Exception {
SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sqlSessionTemplate.getMapper(UserMapper.class);
}
调用Mybatis方法:
public class FooServiceImpl implements FooService {
private final UserMapper userMapper;
public FooServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User doSomeBusinessStuff(String userId) {
return this.userMapper.getUser(userId);
}
}
SqlSessionFactoryBean:
SqlSessionFactoryBean
实现了FactoryBean
的结果,所以Spring最终创建的bean并不是SqlSessionFactroy
本身,而是工厂类的getObject()
方法返回的结果。此时,Spring会在应用启动时创建SqlSessionFactory
,并使用sqlSessionFactory
这个名字存储起来
SqlSessionFactory:
必要属性:dataSource
常用属性:configLocation,指定Mybatis的xml配置文件路径,Mybatis的配置不需要完整,Spring能代替一部分。新版本可以在没有对应XML配置文件的时候,自己设置Configuration实例。
常用属性:mapperLocations,指定Mybatis的映射器XML配置文件的位置,可以用*
使用SqlSession:
使用bean植入到线程安全的SqlSession
,可以基于Spring的配置来自动提交、回滚、关闭Session。
SqlSessionTemplate:
是SqlSession
的一个实现。
管理session
的生命周期
SqlSessionTemplate的构建
使用构造方法,第一个参数为sqlSessionFactory
:
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
java写法:
@Bean
public SqlSessionTemplate sqlSession() throws Exception {
return new SqlSessionTemplate(sqlSessionFactory());
}
实现类:
这个sqlSession是怎么传递进来的呢?
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(String userId) {
return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
}
}
答:通过bean注入的,还真是啥都不用写定了,直接往里怼
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSession" ref="sqlSession" />
</bean>
批量操作:
但是,没写insert的操作啊
public void insertUsers(List<User> users) {
for (User user : users) {
sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user);
}
}
SqlSessionDaoSupport:
是一个抽象的支持类,用来提供SqlSession
,可以调用getSqlSession()
方法来获取一个SqlSessionTemplate
可以通过属性来设置sqlSessionFactroy
或者SqlSessionTemplate
,如果两个属性都被设置了,那么SqlSessionFactory
会被忽略。
设置方法:
前提是UserMapperImpl
是SqlSessionDaoSupport
的子类,可以编写下面的内容来执行配置:
<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
注册映射器
使用MapperFactoryBean
来将映射器注册到Spring中,如果在接口UserMapper的相同类路径下有对应的XML映射器配置文件,那么会被MapperFactoryBean
自动解析,所以,就不用在Mybatis配置文件中显示配置映射器,除非路径不同。(所以说放在一块还是很方便的)
java方法:
@Bean
public MapperFactoryBean<UserMapper> userMapper() throws Exception {
MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
factoryBean.setSqlSessionFactory(sqlSessionFactory());
return factoryBean;
}
发现映射器
三种方法:
- 使用
<mybatis:scan/>
元素 - 使用
@MapperScan
注解 - 在经典Spring XML配置文件中注册一个
MapperScannerConfigurer
<mybatis:scan/>
:
对整个包进行扫描,可以通过逗号或者分号分隔来设置多个包。
不需要指定SqlSessionFactory
或SqlSessionTemplate
,因为使用的是能够被自动注入的MapperFactoryBean
<context:component-scan/>
无法发现并注册映射器
<mybatis:scan base-package="org.mybatis.spring.sample.mapper" />
@MapperScan
:
可以配置sqlSessionFactory
和sqlSessionTemplate
属性。
@Configuration
@MapperScan("org.mybatis.spring.sample.mapper")
public class AppConfig {
// ...
}
MapperScannerConfigurer:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.mybatis.spring.sample.mapper" />
</bean>
如果想指定sqlSessionFactory
或sqlSessionTemplate
,需要指定bean名而不是bean引用,所以要使用value
属性:
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
问题:
SqlSessionFactory
和SqlSessionTemplate
的区别是什么?
SqlSessionFactory是通过SqlSessionFactoryBean的getObject获取的
SqlSessionTemplate是以SqlSessionFactory为构造参数构造的
使用SqlSessionFactory
来创建SqlSession
,有了SqlSession
就不需要再使用SqlSessionFactory
了,SqlSessionTemplate
是SqlSession
的一个实现。
SqlSessionDaoSupport也可以获得SqlSession
报错:
-
Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.mapper.UserMapper.getUser. please check com/mapper/UserMapper.xml and file [D:\同步文件夹\Java_code\Spring\spring-10-mybatis\target\classes\com\mapper\UserMapper.xml]
好像是说已经包含了mapper的值 -
java.lang.NoSuchMethodError: org.springframework.beans.factory.config.BeanDefinition.getResolvableType()Lorg/springframework/core/ResolvableType;
说是没方法,有没报错,可能就是继承或实现出错了
版本问题,检查maven依赖中的spring-jdbc和spring-webmvc是否版本一致
方法一流程
流程图:
UserMapper
和UserMapper.xml
就不谈了,跟Mybatis一样,这里多了个UserMapperImpl
UserMapperImpl:
public class UserMapperImpl implements UserMapper {
// 设置私有成员变量接受sqlSession
// 注意啦,这里是Template
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(int id) {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.getUser(id);
}
}
绑定Mybatis:
固定不变的
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--连接数据库的数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&userUnicode=true&characterEncoding=gbk"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
<!--创建SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--还可以绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:config.xml"/>
<property name="mapperLocations" value="classpath:com/mapper/*.xml"/>
</bean>
<!--创建sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--通过构造函数设置的-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
绑定实现类:
可以达到分离的效果
<?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">
<import resource="spring-dao.xml"/>
<!--注册实现类的bean-->
<bean id="userMapper" class="com.mapper.UserMapperImpl">
<!--参数赋值,利用set-->
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
测试:
@Test
public void testSpring1(){
ApplicationContext context = new ClassPathXmlApplicationContext("Application.xml");
// 可以绑定上接口的class
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
User user = userMapper.getUser(1);
System.out.println(user);
}
思路总结:
Spring利用dataSource创建了SqlSessionFactoryBean,然后利用SqlSessionFactoryBean来构造SqlSessionTemplate,将这个SqlSessionTemplate传到实现类中,在测试中用getMapper就能调用方法。
方法二流程
相当于方法一的简化版,跳过了对实现类SqlSessionTemplate赋值的部分,通过继承的父类中的方法直接过去session,该父类需要一个SqlSessionFactoryBean的输入
流程图:
实现类:
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
public User getUser(int id) {
// 一步到位,点开父类看代码就知道只是做了简化
return getSqlSession().getMapper(UserMapper.class).getUser(id);
}
}
Spring配置:
<!--创建SqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--还可以绑定Mybatis配置文件-->
<property name="configLocation" value="classpath:config.xml"/>
<property name="mapperLocations" value="classpath:com/mapper/*.xml"/>
</bean>
<bean id="userMapper2" class="com.mapper.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
事务
在事务处理期间,一个单独的SqlSession
对象会被创建或使用。当事务完成时,这个session会以合适的方式提交或回滚。
配置:
注意这里的dataSource
要和用来创建SqlSessionFactoryBean
的是同一个数据源。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
交由容器管理事务:
使用Spring的事务命名空间:
<tx:jta-transaction-manager />
Spring会自动使用任何一个存在的容器事务管理器,并注入一个SqlSession
。如果没有正在进行的事务,而基于事务配置需要一个新的事物的时候,Spring会开启一个新的由容器管理的事务。
所以容器是什么意思?
如果不想使用Spring的事务管理,就必须配置SqlSessionFactoryBean
来使用基本的Mybatis的ManagedTreansactionFactory
:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
</property>
</bean>
编程式事务管理:
使用Spring的时候,不能再Spring管理的SqlSession中调用SqlSession.commit()、SqlSession.rollback()、SqlSession.close()方法。
注意:无论 JDBC 连接是否设置为自动提交,调用 SqlSession 数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。
导入:
xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
好像有版本问题
**固定步骤
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--结合Aop实现事务织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给方法配置事务-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<!--expression控制执行范围, mapper里的全部类的全部方法-->
<aop:pointcut id="txPointCut" expression="execution(* com.mapper.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>