IOC:是一种把对象的创建和对象之间的调用过程,交给spring管理,从而减低耦合度的一种面向对象的设计方式
DI:是ioc的另一种表表达形式;即组件以一些预先定义好的方式(例如:setter方法)接收来自于容器的资源注入。相对于ioc而言,这种表述更直接
简单地说IOC就是一种反转控制的思想,而DI是对IOC的一种具体实现
spring的ioc容器就是ioc思想的一个落地产品的实现。ioc容器中管理的组件叫做bean。在创建bean之前,首先需要创建ioc容器。spring提供了ioc容器的两种实现方式
BeanFactory:这是IOC容器的基本实现,是spring内部使用的接口。面向spring本身,不提供给开发人员使用
ApplicationContext:BeanFactory的子接口,提供了更多高级的特性。面向spring的使用者,几乎所有的场合都使用ApplicationContext,而不是底层的BeanFactory
依赖
<packaging>jarpackaging>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
helloworld对象
public class HelloWorld {
public void sayHello(){
System.out.println("Hello,Spring");
}
}
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">
<bean id="helloworld" class="com.sentiment.pojo.HelloWorld">bean>
beans>
测试类
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringTest {
@Test
public void HelloWorldTest(){
//通过配置文件获取ioc容器
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
//从ioc中获取bean对象
HelloWorld bean = (HelloWorld) ioc.getBean("helloworld");
bean.sayHello();
}
}
Student studentOne = (Student) ioc.getBean("studentOne");
Student studentOne = ioc.getBean(Student.class);
注意:根据类型获取bean时,要求Ioc容器中有且只有一个类型匹配的bean
若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException
若有多个类型匹配的bean,此时抛出异常:NouniqueBeanDefinitionException
Student studentOne = ioc.getBean("studentOne", Student.class);
根据类型来获取bean时,在满足bean唯一性的前提下其实只是看:『对象instanceof指定的类型』的返回结果只要返回的是true就可以认定为和类型匹配,能够获取到。即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
扩展
若组件类实现了接口,可以根据接口获取bean,但若这个接口有多个实现类则无法获取即:若Student实现或继承了Persion类,则可以通过person获取student的bean
Person person = ioc.getBean(Person.class);
三种方式及扩展
public void testIoc(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
//Student studentOne = (Student) ioc.getBean("studentOne");
//Student studentOne = ioc.getBean(Student.class);
//Student studentOne = ioc.getBean("studentOne", Student.class);
Person person = ioc.getBean(Person.class);
System.out.println(person);
}
配置
<bean id="studentTwo" class="com.sentiment.pojo.Student">
<property name="sid" value="1842">property>
<property name="sname" value="Sentiment">property>
<property name="age" value="20">property>
<property name="gender" value="男">property>
bean>
测试
public void testSetter(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentTwo = ioc.getBean("studentTwo", Student.class);
System.out.println(studentTwo);
}
结果
此时通过property标签,对各个属性进行了赋值
Student{sid=1842, sname='Sentiment', age='20', gender='男'}
配置
若只有一个有参控制器,直接赋值即可
<bean id="studentThree" class="com.sentiment.pojo.Student">
<constructor-arg value="1843"></constructor-arg>
<constructor-arg value="Tana"></constructor-arg>
<constructor-arg value="21"></constructor-arg>
<constructor-arg value="男"></constructor-arg>
</bean>
测试
public void testByConstructor(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentThree = ioc.getBean("studentThree", Student.class);
System.out.println(studentThree);
}
结果
Student{sid=1843, sname='Tana', age='21', gender='男'}
此时若再加一个属性并定义他的有参控制器
public Student(Integer sid, String sname, Double score, String gender) {
this.sid = sid;
this.sname = sname;
this.score = score;
this.gender = gender;
}
在执行后结果:
Student{sid=1843, sname='Tana', age='null', gender='男', score=21.0}
可以看到21想赋给age但,赋值给了score,所以在配置文件后边可以再加上一个name属性,来定义赋值对象
<constructor-arg value="21" name="age">constructor-arg>
此时结果就赋给了age
Student{sid=1843, sname='Tana', age='21', gender='男', score=null}
赋值null
常规的通过value赋值,只会将null字符串赋给属性,并不是真正意义的null值,所以若想要为null的话可以通过标签完成
<bean id="studentFour" class="com.sentiment.pojo.Student">
<property name="sid" value="1844">property>
<property name="sname" value="Shelter">property>
<property name="gender">
<null>null>
property>
<property name="age" value="22">property>
bean>
xml实体
在xml文档中,<>会被当做标签处理,因此不能随便使用,而当我们赋值中需要带上<>时,则会报错,此时就可以用xml实体来表示
<property name="sname" value="<Shelter>">property>
CDATA字节
除上边方法外,还可以通过CDATA字节解决即:
CDATA中的数据会被正常解析
<property name="sname" >
<value>]]>value>
property>
加一个Clazz类
package com.sentiment.pojo;
public class Clazz {
private Integer cid;
private String cname;
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Clazz{" +
"cid=" + cid +
", cname='" + cname + '\'' +
'}';
}
}
并在student类中,添加一个Clazz类型的属性
private Clazz clazz;
ref:引用IOC容器中的某个bean的id
Clazz是类对象,因此不能直接使用value赋值,要使用ref
<bean id="studentFive" class="com.sentiment.pojo.Student">
<property name="sid" value="1845">property>
<property name="sname" value="Demo">property>
<property name="age" value="23">property>
<property name="gender" value="男">property>
<property name="clazz" ref="clazzOne">property>
bean>
<bean id="clazzOne" class="com.sentiment.pojo.Clazz">
<property name="cid" value="1">property>
<property name="cname" value="QLNU">property>
bean>
测试
@Test
public void testByRef(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentFive = ioc.getBean("studentFive", Student.class);
System.out.println(studentFive);
}
这种方式,仍需用ref,因为不是用的话 clazz值为空,匹配不到对应的cid和cname,所以这种方式也就相当于一种另外的赋值方式
<bean>
<property name="clazz" ref="clazzOne"></property>
<property name="clazz.cid" value="2"></property>
<property name="clazz.cname" value="QlNU2"></property>
</bean>
<bean id="clazzOne" class="com.sentiment.pojo.Clazz">
<property name="cid" value="1"></property>
<property name="cname" value="QLNU"></property>
</bean>
内部bean,只能在当前bean的内部使用,不能直接通过ioc容器得到
<bean id="studentFive" class="com.sentiment.pojo.Student">
<property name="sid" value="1845">property>
<property name="sname" value="Demo">property>
<property name="age" value="23">property>
<property name="gender" value="男">property>
<property name="clazz">
<bean id="clazzInner" class="com.sentiment.pojo.Clazz">
<property name="cid" value="2">property>
<property name="cname" value="QLNU2">property>
bean>
property>
bean>
添加一个字符串数组型变量 hobby,并设置对应的setter、getter和toString方法
private String[] hobby;
数组类型赋值有对应的标签,如果数组存储的是类变量,则将value标签改为ref标签
<property name="hobby">
<array>
<value>唱value>
<value>跳value>
<value>rapvalue>
<value>篮球value>
array>
property>
在clazz类中添加一个students集合属性,并设置对应的setter、getter和toString方法
private List<Student> students;
同数组形式,直接用list标签即可,由于存储的事Student类型数据,所以里边用ref标签
<property name="students">
<list>
<ref bean="studentOne">ref>
<ref bean="studentTwo">ref>
<ref bean="studentThree">ref>
list>
property>
list标签相当于是内部调用,而util就相当于外部调用
<property name="students" ref="studentList">property>
<util:list id="studentList">
<ref bean="studentOne">ref>
<ref bean="studentTwo">ref>
<ref bean="studentThree">ref>
util:list>
新建一个Teacher类
package com.sentiment.pojo;
public class Teacher {
private Integer tid;
private String tname;
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
'}';
}
public Integer getTid() {
return tid;
}
public void setTid(Integer tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
}
在Student类中,定义一个 teacher类型的map属性,并设置对应的setter、getter、toString方法
private Map<String,Teacher> teacherMap;
map中的entry标签 ,自动存储键和值,设置value-ref存储 bean中的类型数据
<property name="teacherMap">
<map>
<entry key="1" value-ref="teacherOne">entry>
<entry key="2" value-ref="teacherTwo">entry>
map>
property>
<bean id="teacherOne" class="com.sentiment.pojo.Teacher">
<property name="tid" value="1">property>
<property name="tname" value="AA">property>
bean>
<bean id="teacherTwo" class="com.sentiment.pojo.Teacher">
<property name="tid" value="2">property>
<property name="tname" value="BB">property>
bean>
<property name="teacherMap" ref="studentMap">property>
<util:map id="studentMap">
<entry key="1" value-ref="teacherOne">entry>
<entry key="2" value-ref="teacherTwo">entry>
util:map>
配置
<bean id="studentSix" class="com.sentiment.pojo.Student"
p:sid="111" p:sname="Sentiment" p:teacherMap-ref="studentMap">bean>
测试
public void testByPnamesapce(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student studentSix = ioc.getBean("studentSix", Student.class);
System.out.println(studentSix);
}
依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.6version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.0.31version>
dependency>
配置文件
jdbc.properties用的是mybatis里的
<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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}">property>
<property name="url" value="${jdbc.url}">property>
<property name="username" value="${jdbc.username}">property>
<property name="password" value="${jdbc.password}">property>
bean>
beans>
测试
@Test
public void dataSourceTest() throws SQLException {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-datasource.xml");
DruidDataSource bean = ioc.getBean(DruidDataSource.class);
System.out.println(bean.getConnection());
}

在spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,属性如下
常规情况下同一个ioc容器获取的bean值是相等的,在比较时会返回true
public void ScopeTest(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-scope.xml");
Student bean1 = ioc.getBean(Student.class);
Student bean2 = ioc.getBean(Student.class);
System.out.println(bean1 == bean2);
}
当设置了scope标签并且值为prototype时(默认为singleton),两个bean值则会不同,返回false
<bean id="student" class="com.sentiment.pojo.Student" scope="prototype">
<property name="sid" value="1842">property>
<property name="sname" value="Sentiment">property>
bean>
如果是在WebApplicationContext环境下会有另外两个作用域
| 取值 | 含义 |
|---|---|
| request | 在一个请求范围内有效 |
| session | 在一个会话范围内有效 |
作用域注解:@Scope(value=“singleton”)
注意:该注解可以用在@Bean标识的方法中,也可以标识在@Component标识的类中;从而表明获取到的对象为单例或多例模式
生命周期过程
第一步是实例化,是由于ioc容器管理对象时,是通过工厂和反射获取的,所以会默认使用无参构造。
创建一个User类
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
public User() {
System.out.println("生命周期1:实例化");
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setAge(Integer age) {
this.age = age;
}
public void setId(Integer id) {
System.out.println("生命周期2:依赖注入");
this.id = id;
}
void init(){
System.out.println("生命周期3:初始化");
}
void destroy(){
System.out.println("生命周期4:销毁");
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
'}';
}
}
初始化和销毁需要在配置文件中定义
<bean id="student" class="com.sentiment.pojo.Student" scope="prototype">
<property name="sid" value="1842">property>
<property name="sname" value="Sentiment">property>
bean>
测试
public void test(){
ConfigurableApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
User bean = ioc.getBean(User.class);
System.out.println(bean);
ioc.close();
}
最后的销毁部分是通过ioc.close()来完成的,而这里的用的是ConfigurableApplicationContext类型,因为ApplicationContext中没有close方法,而ConfigurableApplicationContext是他的子接口其中定义了刷新和关闭的方法。这里用原来的ClassPathXmlApplicationContext也是可以的

其实当执行第一步的时候就已经,初始化了,而这里的初始化只指单例模式的

如果换成多例模式即:配置中加上scope="prototype"后
此时运行便没有任何结果,而它的初始化则是在获取bean的时候生成
在bean的声明周期过程中,初始化前后还有两个操作但是在上边并没有体现到
bean对象初始化之前的操作(由bean的后置处理器postProcessBeforeInitialization负责)
bean对象初始化后的操作(由bean的后置处理器postProcessAfterInitialization负责)
bean的后置处理器会在声明周期的初始化前后添加额外的操作,需要实现BeanPostProcessor接口,且配置到IOC容器中,需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行
创建bean的后置处理器:
package com.sentiment.process;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之前执行——>postProcessBeforeInitialization");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化之后执行——>postProcessAfterInitialization");
return bean; }
}
配置到IOC容器中
<bean id="mybeanprocessor" class="com.sentiment.process.MyBeanProcessor">bean>
此时在执行则会调用bean的后置处理器

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来,只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时,Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。
<T> getObject():通过一个对象交给ioc容器来管理
Class<?> getObjectType():设置所提供对象的类型
boolean isSingleton():所提供的对象是否为单例
Factory
import com.sentiment.pojo.User;
import org.springframework.beans.factory.FactoryBean;
public class UserFactoryBean implements FactoryBean<User> {
@Override
public User getObject() throws Exception {
return new User();
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
配置文件
这里并不是返回UserFactoryBean的类对象,而是该类中getObject方法中返回的User()对象,较以往的工厂来说bean工厂省去了找工厂的过程直接找我们需要的对象。
<bean class="com.sentiment.factory.UserFactoryBean">bean>
测试
@Test
public void factoryTest(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-factory.xml");
User bean = ioc.getBean(User.class);
System.out.println(bean);
}
输出结果
可以看到这里实例化了User对象,并输出了对应内容

根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值
自动装配用到了三层架构,先了解了一下
controller又叫web层、控制层、控制器,主要用于用户交互,接收和响应来自用户端的http请求,并且包含一些web功能
常见技术:cookie seesion jsp servlet listener filter
service层又叫业务层,主要用来处理逻辑,用于事务处理、日志管理、监控等
dao层又叫持久层、mapper层、respotiry层,主要用于操纵数据库返回用户数组,前篇提到的mybatis就属于该层
技术:jdbc druid mybatis
三层架构的调用关系
controller -> service -> dao -> 操作数据库
数据返回关系
dao操作数据库数据 ->service -> controller
回到xml自动装配,感觉跟动态代理好像。。。
先写一个controller
public class UserController {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
public void saveUser() {
userService.saveUser();
}
}
controller中定义了UserService并调用了他的saveUser()方法,所以在创建个UserService接口和实现类
UserService接口
public interface UserService {
void saveUser();
}
实现类
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void saveUser() {
userDao.saveUser();
}
}
接着又调用了UserDao的saveUser所以继续创建接口和实现类
UserDao接口
public interface UserDao {
void saveUser();
}
实现类
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("保存成功");
}
}
最后通过配置文件自动装配,通过其中的set方法进行赋值
<bean id="userController" class="com.sentiment.controller.UserController">
<property name="userService" ref="userService">property>
bean>
<bean id="userService" class="com.sentiment.service.iml.UserServiceImpl">
<property name="userDao" ref="userDao">property>
bean>
<bean id="userDao" class="com.sentiment.dao.iml.UserDaoImpl">bean>
测试
public void test(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-autowire.xml");
UserController bean = ioc.getBean(UserController.class);
bean.saveUser();
}
在场景模拟中,用到的并不是自动装配,而是通过property标签手动设置了对应的属性值,所以这里就通过autowire标签的byType属性实现自动装配,若设置no或default属性则表示不会自动装配即使用默认值
<bean id="userController" class="com.sentiment.controller.UserController" autowire="byType">
<!-- <property name="userService" ref="userService"></property>-->
</bean>
<bean id="userService" class="com.sentiment.service.iml.UserServiceImpl" autowire="byType">
<!-- <property name="userDao" ref="userDao"></property>-->
</bean>
<bean id="userDao" class="com.sentiment.dao.iml.UserDaoImpl"></bean>
此时在运行程序后,会通过byType自动匹配对应的类型属性赋值
但需要注意两个问题:
根据bean的id名来进行bean的匹配。
上边提到当设置多个userService类型的bean后,则会报错,这时就可以使用byName因为他是根据bean的id进行匹配的,所以不管设置几个同类型bean,只要id唯一就能匹配到(而id是唯一标识,所以常规状态下不会出现id相同情况)

基于注解管理bean和xml配置文件管理一样,注解本身并不能执行,注解本身仅仅只做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置,按照注解标记的功能来执行具体的操作
扫描
spring为了知道程序员在那些地方标记了什么注解就需要通过扫描的方式来进行检测,然后根据注解来进行后续操作(将扫描到的包内的类交给spring容器来保存)
常用注解
注:这四个注解功能一模一样,没有任何区别,都是将对应的类交给spring容器来保存,只不过名字不一样(便于分辨组件的作用)
配置个扫描器,扫描对应的注解
<context:component-scan base-package="com.sentiment">context:component-scan>
测试
public void test(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annotation.xml");
UserController controller = ioc.getBean(UserController.class);
System.out.println(controller);
UserService service = ioc.getBean(UserService.class);
System.out.println(service);
UserDao dao = ioc.getBean(UserDao.class);
System.out.println(dao);
}
在exclude-filter标签中可以设置两个参数:
其中包含几个属性:
type:设置扫描的方式有两个常用值—annotation、assignable
expression:设置排除的类的全类名
use-default-filters:设置是否扫描包下所有的包,默认为true,但若使用include-filte包含扫描时,需设置为flase(包含扫描是指只扫描哪个标签,而若use-default-filters设为true则会默认扫描包下的所有标签,就是去了include-filte的意义)
<context:component-scan base-package="com.sentiment" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="assignable" expression="com.sentiment.service.impl.UserServiceImpl"/>
context:component-scan>
测试
getbean方法可以通过bean的id获取ioc容器
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-annotation.xml");
UserController controller = ioc.getBean("controller",UserController.class);
Syste标识在成员变量上,此时不需要设置成员变量的set方法(直接根据策略在ioc容器中查找对应对象)
标识在set方法上
为当前成员变量赋值的有参构造上m.out.println(controller);
建议标识在成员变量上
@Controller("controller")
public class UserController {
@Autowired
private UserService userService;
public void saveUser(){
userService.saveUser();
}
}
例:
② 若此时在下边添加两个bean标签,则默认会通过byName方式获取,因为这里当调用UserService类型是,这里存在两个所以无法匹配。
验证方法也很简单,只需要把id随意修改一下便会报错,出现找不到对应属性的问题
<context:component-scan base-package="com.sentiment">context:component-scan>
<bean id="userService" class="com.sentiment.service.impl.UserServiceImpl">bean>
<bean id="userDao" class="com.sentiment.dao.impl.UserDaoImpl">bean>
③ 如上 边所说id随意修改一下后,便会出现找不到对应的id的问题而且此时也有两个同类型bean,byName和byType就都是失效了,所以就用到了@Qualifier注解
<context:component-scan base-package="com.sentiment">context:component-scan>
<bean id="Service" class="com.sentiment.service.impl.UserServiceImpl">bean>
<bean id="Dao" class="com.sentiment.dao.impl.UserDaoImpl">bean>
测试
通过@Qualifier来指定对应bean的id即可
@Controller("controller")
public class UserController {
@Autowired
@Qualifier("service")
private UserService userService;
public void saveUser(){
userService.saveUser();
}
}
注:在@Autowired注解属性里有个required属性,默认为true,要求必须完成自动装配,可以将required设置为false,此时能装配则装配,不能装配则使用属性默认值
接口类
public interface Calculator {
int add(int i ,int j);
int sub(int i ,int j);
int mul(int i ,int j);
int div(int i ,int j);
}
实现类
在四个方法中模拟了日志功能,但这些日志并不是add方法的核心业务功能,这就会导致一些问题:
package com.sentiment.proxy;
public class CalculatorImpl implements Calculator {
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数"+i+","+j);
int result=i+j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:add,结果"+i+","+j);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数"+i+","+j);
int result=i-j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:sub,结果"+i+","+j);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数"+i+","+j);
int result=i*j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:mul,结果"+i+","+j);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数"+i+","+j);
int result=i/j;
System.out.println("方法内部,result:"+result);
System.out.println("日志,方法:div,结果"+i+","+j);
return result;
}
}
通过上例就引出了代理模式,即:各自的方法由自己来实现就好,附加功能由代理来实现
将CalculatorImpl中的日志功能部分除去,加到代理类中
代理类
package com.sentiment.proxy;
public class CalculatorStiaticProxy implements Calculator {
private CalculatorImpl target;
public CalculatorStiaticProxy(CalculatorImpl target) {
this.target = target;
}
@Override
public int add(int i, int j) {
System.out.println("日志,方法:add,参数"+i+","+j);
int result = target.add(i,j);
System.out.println("日志,方法:add,结果"+i+","+j);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志,方法:sub,参数"+i+","+j);
int result = target.sub(i,j);
System.out.println("日志,方法:sub,结果"+i+","+j);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志,方法:mul,参数"+i+","+j);
int result = target.sub(i,j);
System.out.println("日志,方法:mul,结果"+i+","+j);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志,方法:div,参数"+i+","+j);
int result = target.sub(i,j);
System.out.println("日志,方法:div,结果"+i+","+j);
return result;
}
}
测试类
public void testProxy(){
CalculatorStiaticProxy proxy = new CalculatorStiaticProxy(new CalculatorImpl());
proxy.add(1,2);
}
第三遍回顾动态代理了,之前有一篇文章专门分析过可以参考一下: [Java基础]—动态代理_Sentiment.的博客-CSDN博客
静态代理代理类和业务类都需要实现同样的接口,会造成代码的重复并且静态代理缺乏一定的灵活性,因此引出了动态代理。
动态代理主要涉及java.lang.reflect包下的Proxy类和InvocationHandler接口。
先看下java.lang.reflect.Proxy类,其中有个newProxyInstance就是实现动态代理的方法
package java.lang.reflect;
import java.lang.reflect.InvocationHandler;
public class Proxy implements java.io.Serializable {
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
..........
}
重点在invocationHandler h,该类只有一个方法
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
代理类
package com.sentiment.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class ProxyFactory {
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
public Object getProxy(){
ClassLoader classLoader = this.getClass().getClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("日志参数:" + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("日志结果:" + result);
return result;
}
};
return Proxy.newProxyInstance(classLoader,interfaces,h);
}
}
测试
import com.sentiment.proxy.Calculator;
import com.sentiment.proxy.CalculatorImpl;
import com.sentiment.proxy.CalculatorStiaticProxy;
import com.sentiment.proxy.ProxyFactory;
import org.junit.Test;
public class ProxyTest {
@Test
public void dynamicProxy(){
ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
Calculator proxy = (Calculator) proxyFactory.getProxy();
proxy.add(1,2);
}
}
流程

AOP(aspect oriented programming)是一种设计思想,是软件设计领域中的面向切面编程,他是面向对象编程的一种补充和完善,它通过预编译的方式和运行期动态代理的方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术
横切关注点(日志):
从每个方法中抽取出来的同一类非核心业务,同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强
通知 :
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法叫通知方法
切面:
用于封装横切关注点的类(每个横切关注点都表示为一个通知方法)
注:我们要把横切关注点封装到切面中,而在这个切面中每一个横切关注点都表示一个通知方法
目标
被代理的目标对象(加减乘除功能)
连接点:
表示横切关注点抽出来的位置
切入点:
定位连接点的方式
每一个横切关注点也就是非核心业务方法都会被抽出到切面中,而切面中的每个横切关注点表示一个通知方法,通过切入点就是将通知方法放到连接点处
简化代码:把方法中固定位置的重复代码抽取出来,让抽取的方法更专注于自己的核心功能,提高内聚性
代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了
**注:**AOP依赖于IOC而存在
配置文件
开启基于注解的AOP
测试类
@Component
@Aspect
public class LogAspect {
@Before("execution(public int com.sentiment.aop.CalculatorImpl.add(int ,int))")
public void beforeAdvicedMethdo(){
System.out.println("前置通知");
}
}
bean表达式
bean(bean的id)//没有引号
within表达式
//只拦截具体包下的具体类
within(com.Sentiment.service.User)
//拦截具体包下的所有类
within(com.sentiment.service.*)
//拦截具体包下的所有包类
within(com.sentiment.service..*)
//拦截com.任意包.service包下的所有包类
within(com.*.service..*)
execution表达式
语法:execution(返回值类型 包名.类名.方法名(参数列表))
//拦截返回值任意的具体方法
execution(* com.sentiment.service.UserServiceImpl.addUser())
//拦截返回值任意,参数列表任意,具体包service所有子包与子类的所有方法
execution(* com.sentiment.service..*.*(..))
//拦截返回值任意,参数列表为两个int类型,具体包service所有子包与子类的add方法
execution(* com.sentiment.service..*.add(int,int))
定义一个注解类,在需要扩展的方法上加注解
//对被有该注解标明的方法有效(里面填定义注解的位置)
@annotation(com.sentiment.anno.注解)
在上边定义了一个add方法的前置通知,但是此时"减乘除"都没有设置,若使用刚才的方式则需再定义三个,代码重复量过高,所以就可以用表达式进行简化
第一个表示任意的访问修饰符和返回值类型
第二个表示类中任意的方法
… 表示任意的参数列表
@Before("execution(* com.sentiment.aop.CalculatorImpl.*(..))")
除此外类的地方也可以用*,表示包下所有的类
此时只定义了前置操作,若在定义后置通知,异常通知等,就会导致execution(* com.sentiment.aop.CalculatorImpl.*(..))出现反复重写问题,所以又引入了一个注解@Pointcut
此时After后边只需要填写对应的pointCut方法即可
@Pointcut("execution(* com.sentiment.aop.CalculatorImpl.*(..))")
public void pointCut(){}
@After("pointCut()")
public void after(){
System.out.println("后置通知");
}
在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点对应方法的信息
测试
@Before("pointCut()")
public void beforeAdvicedMethdo(JoinPoint joinPoint){
//获取连接点对应方法的签名信息
Signature signature = joinPoint.getSignature();
//获取连接点对应方法的参数
Object[] args = joinPoint.getArgs();
System.out.println("前置通知,方法:"+signature.getName()+"参数:"+ Arrays.toString(args));
}
结果
前置通知,方法:mul参数:[1, 2]
方法内部,result:2
先用try语句理解一下各个通知的接入点
try {
//前置通知@Before
目标方法执行语句..........
//返回通知@AfterReturning
}catch(exception e){
//异常通知@AfterThrowing
} finally{
//后置通知@After
}
@After
接入点相当于finally位置,无论执行是否异常都会执行

@AfterReturning
接入点在目标方法语句后,若出现异常则不会执行,另外该注释中有一个参数returning,作为为目标函数的返回结果
@AfterReturning(value = "pointCut()",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
Signature signature = joinPoint.getSignature();
System.out.println("返回通知,方法:"+signature.getName()+",结果:"+result);
}
@AfterThrowing
接入点在catch语句中,若出现异常则会执行,另外该注释中有一个参数throwing,作为为目标函数的异常结果
@AfterThrowing(value = "pointCut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Throwable e){
Signature signature = joinPoint.getSignature();
System.out.println("异常通知,方法:"+signature.getName()+",异常:"+e);
}
@Around("pointCut()")
public Object around(ProceedingJoinPoint joinPoint){
Object result=null;
try {
System.out.println("环绕通知-->前置通知");
result = joinPoint.proceed();
System.out.println("环绕通知-->后置通知");
}catch (Throwable throwable){
System.out.println("环绕通知-->异常通知");
}finally {
System.out.println("环绕通知-->后置通知");
}
return result;
}
再添加一个AOP类,加上前置通知
public class OrderAspect {
@Before("com.sentiment.aop.LogAspect.pointCut()")
public void before(){
System.out.println("前置通知:Order");
}
}
此时在程序执行后发现,该通知的执行在LogAspect类的前置通知后,所有就引入了一个注解@Order来设置切面执行的优先级

此时发现Order的前置通知被执行, @Order填写一个int值即可,值越小优先级越高,而默认值为int类型的最大值2147483647,所以上边值随便给个比这个小的即可

将前边的AOP注解都去掉后,可以基于xml实现AOP管理
配置文件
<context:component-scan base-package="com.sentiment.aop.xml">context:component-scan>
<aop:config>
<aop:pointcut id="pointCut" expression="execution(* com.sentiment.aop.xml.CalculatorImpl.*(..))"/>
<aop:aspect ref="logAspect" >
<aop:before method="beforeAdvicedMethdo" pointcut-ref="pointCut">aop:before>
<aop:after method="after" pointcut-ref="pointCut">aop:after>
<aop:after-returning method="afterReturning" returning="result" pointcut-ref="pointCut">aop:after-returning>
<aop:after-throwing method="afterThrowing" throwing="e" pointcut-ref="pointCut">aop:after-throwing>
aop:aspect>
<aop:aspect ref="orderAspect" order="1">
<aop:before method="before" pointcut-ref="pointCut">aop:before>
aop:aspect>
aop:config>
测试
@Test
public void test(){
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("aop-xml.xml");
Calculator bean = ioc.getBean(Calculator.class);
bean.add(1,0);
}
MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。
版本要求:
| MyBatis-Spring | MyBatis | Spring Framework | Spring Batch | Java |
|---|---|---|---|---|
| 2.0 | 3.5+ | 5.0+ | 4.0+ | Java 8+ |
| 1.3 | 3.4+ | 3.2.2+ | 2.1+ | Java 6+ |
依赖
如果mybatis是3.4以下就用1.3版本
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatis-springartifactId>
<version>2.0.7version>
dependency>
Usermapper实现类
由于Spring通过bean来管理,所以这里的sqlSeesionFactory的创建等都集成在了bean中,只留下的最后的getMapper,用类来实现
package com.sentiment.mapper;
import com.sentiment.pojo.User;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.List;
public class UserMapperImpl implements UserMapper{
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
配置文件
下边主要是通过管理bean的方式创建sqlSeesion
<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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/Mybatis">property>
<property name="username" value="root">property>
<property name="password" value="123456">property>
bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:mybatis-config.xml">property>
<property name="mapperLocations" value="classpath:com/sentiment/mapper/*.xml">property>
bean>
<bean id="sqlSeesion" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory">constructor-arg>
bean>
<bean id="userMapper" class="com.sentiment.mapper.UserMapperImpl">
<property name="sqlSession" ref="sqlSeesion">property>
bean>
beans>
总体流程:
查询语句首先要定义对应的类(User.java)
之后就要定义实现查询语句的接口(UserMapper.java)
有了接口后就要有对应实现语句的配置文件(UserMapper.xml)
之后是总的mybatis配置文件(而由于结合spring,所以用了spring配置文件代替—spring-mybatis.xml)
最后添加一个接口实现类来获取配置文件中创建的sqlSeesion对象并实现对应的查询方法(UserMapperImpl.java)
除SqlSeesionTemplate外,官方还给出了另一种方法—SqlSessionDaoSupport,这种方法的本质其实还是SqlSeesionTemplate只是对他进行了一些简化,所以使用起来更方便一些。
使用该种方式配置文件中就不在需要获取sqlSeesion,只需要获取到SQLSessionFactory处即可。
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
@Override
public List<User> selectUser() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.selectUser();
}
}
执行语句也可精简成一句
return getSqlSession().getMapper(UserMapperImpl2.class).selectUser();
可以把一系列要执行的操作称为事务,而事务管理就是管理这些操作要么完全执行,要么完全不执行
在UsermapperImpl.java实现添加和删除方法
@Override
public List<User> selectUser() {
User user = new User(5, "Sentiment", "123456", 20, "男", "123@qq.com");
UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
mapper.insertUser(user);
mapper.deleteUser(5);
return getSqlSession().getMapper(UserMapper.class).selectUser();
}
@Override
public int insertUser(User user) {
return getSqlSession().getMapper(UserMapper.class).insertUser(user);
}
@Override
public int deleteUser(int id) {
return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
并且将mapper的配置文件中delete特意写错

此时执行后发现报错,但查询表后发现insert方法已经被执行了,所以就引入了声明式事务来确保操作要么完全执行,要么完全不执行。
此时添加事务声明配置文件,之后在执行当遇到错误时,就会直接终止完全不执行
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut id="PointCut" expression="execution(* com.sentiment.mapper.UserMapper.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="PointCut"/>
aop:config>
SSM框架还剩下最后一部分SpringMVC,但课堂内容Mybatis还没讲完,所以先等一下课程学学JavaWeb