Spring
1.1 什么是Spring
- Spring是一个开源的免费的框架(容器)
- Spring是一个轻量级的、非入侵式的框架
- 核心是IOC和AOP
- 支持事务的处理,支持框架的整合
Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架
1.2 Spring的组成
Spring七大核心模块
1.2.1 核心容器(Spring Core)
- 核心容器提供Spring框架的基本功能。Spring以bean的方式组织和管理Java应用中的各个组件及其关系。Spring使用BeanFactory来产生和管理Bean,它是工厂模式的实现。BeanFactory使用控制反转(IoC)模式将应用的配置和依赖性规范与实际的应用程序代码分开。
1.2.2 应用上下文(Spring Context)
- Spring上下文是一个配置文件,向Spring框架提供上下文信息。Spring上下文包括企业服务,如JNDI、EJB、电子邮件、国际化、校验和调度功能。
1.2.3 Spring面向切面编程(Spring AOP)
- 通过配置管理特性,Spring AOP 模块直接将面向方面的编程功能集成到了 Spring框架中。所以,可以很容易地使 Spring框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
1.2.4 JDBC和DAO模块(Spring DAO)
- JDBC、DAO的抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理,和不同数据库供应商所抛出的错误信息。异常层次结构简化了错误处理,并且极大的降低了需要编写的代码数量,比如打开和关闭链接。
1.2.5 对象实体映射(Spring ORM)
- Spring框架插入了若干个ORM框架,从而提供了ORM对象的关系工具,其中包括了Hibernate、JDO和 IBatis SQL Map等,所有这些都遵从Spring的通用事物和DAO异常层次结构。
1.2.6 Web模块(Spring Web)
- Web上下文模块建立在应用程序上下文模块之上,为基于web的应用程序提供了上下文。所以Spring框架支持与Struts集成,web模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
1.2.7 MVC模块(Spring Web MVC)
- MVC框架是一个全功能的构建Web应用程序的MVC实现。通过策略接口,MVC框架变成为高度可配置的。MVC容纳了大量视图技术,其中包括JSP、POI等,模型来有JavaBean来构成,存放于m当中,而视图是一个街口,负责实现模型,控制器表示逻辑代码,由c的事情。Spring框架的功能可以用在任何J2EE服务器当中,大多数功能也适用于不受管理的环境。Spring的核心要点就是支持不绑定到特定J2EE服务的可重用业务和数据的访问的对象,毫无疑问这样的对象可以在不同的J2EE环境,独立应用程序和测试环境之间重用。
1.3 IOC
使用常规的方法将dao层与service层连接
public class UserServiceImpl implements UserService {
//将dao和service连接
private UserDao userDao=new UserDaoImpl();
public void getUser() {
//直接调用dao中的方法
userDao.getUser();
}
}
这种方法存在的弊端
- 用户每次更改需求,都需要修改源代码,耗时,代价昂贵
用户提需求:通过Mysql获取User
//编写一个MysqlDao实现类
public class UserDaoMysqlImpl implements UserDao{
public void getUser() {
System.out.println("通过Mysql获取User");
}
}
在Service层修改源代码
public class UserServiceImpl implements UserService {
private UserDao userDao=new UserDaoMysqlImpl();
public void getUser() {
userDao.getUser();
}
}
使用IOC思想解决这种问题
- 在service层中添加一个set方法
- 每次修改需求,只需要修改传递的参数即可
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
//直接调用dao中的方法
userDao.getUser();
}
}
测试
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
((UserServiceImpl) userService).setUserDao(new UserDaoMysqlImpl());
userService.getUser();
}
}
使用IOC思想前后的区别
- 之前,程序是主动创建对象
- 使用set注入后,程序不再具有主动性,而是变成了被动的接收对象
这种思想,从本质上解决了问题,不用再去管理对象的创建了,系统的耦合性大大降低,可以更加专注的在业务的实现上。这是IOC的原型
1.3.1 IOC本质
控制反转(Inversion of Control)是一种设计思想,DI(依赖注入)是实现IOC的一种方法。
- 没有IOC的程序中,使用面向对象编程,对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制。
- 控制反转后,将对象的创建交给第三方,动态的根据需求创建不同的对象
- 所谓控制反转:控制对象创建的方式反转了
控制反转是一种通过描述((XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是loC容器,其实现方法是依赖注入(Dependency Injection,DI)。
1.3.2 IOC实现
创建实体类,getset方法
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
ApplicationContext.xml(Spring主配置文件)
- bean标签中的id属性的作用:通过上下文对象根据id获取该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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用Spring来创建对象,在Spring中,这些都称为Bean
以前;User user = new User();
id:等价于变量名
class:等价于User类
property:相当于通过set方法对类中的属性赋值
-->
<bean id="user" class="com.rm.pojo.User">
<property name="name" value="张三"/>
</bean>
</beans>
测试,获取User对象
- 注意:id值区分大小写,必须和bean标签中的id完全一致
- xml配置文件管理的对象通过CPXAC类获取,JavaConfig管理的对象通过ACAC类获取
public class MyTest {
public static void main(String[] args) {
//通过ApplicationContext.xml获取spring的上下文对象(参数可以传递多个xml文件)
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//对象现在都被spring托管,需要对象直接从spring中获取即可(区分大小写)
User user = (User) context.getBean("user");
System.out.println(user);
}
}
以前
- User user = new User();
现在
- id:等价于变量名
- class:等价于User类
- property:相当于通过set方法对类中的属性赋值(普通属性用value,对象属性用ref)
这个过程就叫做控制反转
- 控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建的,使用Spring后,对象是由Spring来创建的。
- 反转:程序本身不再创建对象,而变成被动的接收对象
- 依赖注入:就是通过set方法注入对象的属性值
现在,改变对象的创建不用去程序中修改了,要实现不同的操作,只需要在XML配置文件中进行修改即可
所谓IOC:对象由Spring来创建,管理,装配
如果一个实体类被Spring托管
1.3.3 使用无参构造对象(默认)
- pojo类需要有无参构造器
<bean id="user" class="com.rm.pojo.User">
<property name="name" value="李四"/>
</bean>
1.3.4 使用有参构造对象
- pojo类需要有有参构造器
- 通过下标赋值
<bean id="user" class="com.rm.pojo.User">
<!--通过属性下标索引为其赋值-->
<constructor-arg index="0" value="张三"/>
</bean>
- 通过参数类型赋值(不建议使用)
<bean id="user" class="com.rm.pojo.User">
<constructor-arg type="java.lang.String" value="李四"/>
</bean>
- 通过类的属性进行赋值
<bean id="user" class="com.rm.pojo.User">
<constructor-arg name="name" value="王五"/>
</bean>
总结
- 在配置文件加载的时候,容器中管理的所有对象就已经被初始化了
- 无论该对象是否被调用,只要在配置文件中配置,当配置文件加载的时候,就已经初始化了。
User类
public class User {
private String name;
public User(){
System.out.println("通过无参构造创建User对象");
}
}
UserT类
public class UserT {
public UserT(){
System.out.println("UserT无参构造器被调用了");
}
}
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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.rm.pojo.User">
</bean>
<bean id="userT" class="com.rm.pojo.UserT">
</bean>
</beans>
测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
}
}
结果
1.4 Spring配置
1.4.1 别名
- 为一个在配置文件中配置的Bean取一个别名,获取该对象的时候可以通过别名获取
<alias name="user" alias="userNew"/>
1.4.2 Bean的配置
<!--
id:bean的唯一标识符,也就是对象名
class:bean对象所对应的全限定类名,包名+类名
name:也是别名,而且name可以取多个别名
-->
<bean id="user" class="com.rm.pojo.User" name="u2,userNew">
<property name="name" value="张三"/>
</bean>
1.4.3 import
- 一般用于团队开发使用,可以将多个配置文件,导入合并为一个总的applicationContext.xml
<import resource="bean1.xml"/>
<import resource="bean2.xml"/>
1.5 依赖注入
- 依赖:bean对象的创建依赖于容器
- 注入:bean中的所有属性,由容器来注入
1.5.1 构造器注入
- 前面说过
1.5.2 Set方式注入(重点)
不同类型的属性如何注入值
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="address" class="com.rm.pojo.Address">
<property name="address" value="北京"/>
</bean>
<bean id="student" class="com.rm.pojo.Student">
<!--第一种,普通值注入,value-->
<property name="name" value="张三"/>
<!--第二种,Bean注入,ref-->
<property name="address" ref="address"/>
<!--第三种,数组注入,array-->
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
</array>
</property>
<!--第四种,list集合注入,list-->
<property name="hobby">
<list>
<value>听歌</value>
<value>看电影</value>
</list>
</property>
<!--第五种,map集合注入,map-->
<property name="score">
<map>
<entry key="数学" value="100"/>
<entry key="英语" value="1"/>
</map>
</property>
<!--第六种,set集合注入,set-->
<property name="games">
<set>
<value>LOL</value>
<value>DNF</value>
<value>CF</value>
</set>
</property>
<!--第七种,空值注入,nul-->
<property name="wife">
<null/>
</property>
<!--第八种,properties,props-->
<property name="info">
<props>
<prop key="username">root</prop>
<prop key="password">root</prop>
</props>
</property>
</bean>
</beans>
测试结果
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
System.out.println(student);
/*
Student
{name='张三',
address=Address{address='北京'},
books=[西游记, 红楼梦],
hobby=[听歌, 看电影],
score={数学=100, 英语=1},
games=[LOL, DNF, CF],
wife='null',
info={password=root, username=root}}
*/
}
}
1.5.3 拓展方式注入(p命名空间)
p命名空间
- 需要导入约束
- 对应的就是set方法注入
- P(Property)
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用P命名空间对一些简单的属性及对象属性赋值-->
<bean id="user" class="com.rm.pojo.User" p:name="李四" p:age="25" p:address-ref="address"/>
<bean id="address" class="com.rm.pojo.Address" p:address="上海"/>
</beans>
c命名空间
- 对应的就是构造器注入
- C(Construct)
注意:
- 使用c命名空间传递参数的值得前提是必须要有有参构造器
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--使用c命名空间对一些简单的属性及对象属性赋值(必须要有有参构造参数)-->
<bean id="user" class="com.rm.pojo.User" c:name="王五" c:address-ref="address" c:age="18"/>
<bean id="address" class="com.rm.pojo.Address" c:address="郑州"/>
</beans>
总结
- p命名空间和c命名空间不能直接使用,需要导入xml约束
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
1.6 Bean的作用域
1.6.1 singleton(单例模式)
单例模式(Spring默认)
- 全局唯一
<bean id="user" class="com.rm.pojo.User" c:name="王五" c:address-ref="address" c:age="18" scope="singleton"/>
测试
@Test
public void Test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user2= (User) context.getBean("user");
System.out.println(user==user2);//true
}
1.6.2 prototype(原型模式)
原型模式(prototype)(在多线程中可以使用)
- 每次从容器中取时,都会产生一个新的对象
- 这是基于线程安全性的考虑,如果使用有状态的Bean对象用原型作用域,而无状态的Bean对象用单例作用域。
<bean id="user" class="com.rm.pojo.User" c:name="王五" c:address-ref="address" c:age="18" scope="prototype"/>
@Test
public void Test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user2= (User) context.getBean("user");
System.out.println(user==user2);//false
}
其余几种在web开发中使用。
1.6.3 request
- 该对象在一次请求中有效
1.6.4 session
- 该对象存在session中
1.6.5 application
- 该对象在全局生效
1.7 Bean的自动装配
- 自动装配是Spring满足bean依赖的一种方式
- Spring会在上下文中自动寻找,并自动给bean装配属性
在Spring中有三种装配的方式
1. 在xml中显式的配置
2. 在java中显式的配置
3. 隐式的自动装配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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="cat" class="com.rm.pojo.Cat"/>
<bean id="dog" class="com.rm.pojo.Dog"/>
<bean id="people" class="com.rm.pojo.People">
<property name="name" value="李四"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
1.7.1 ByName自动装配
- 会自动在容器上下文中查找,和自己对象set方法后面的值对应的bean的id
<bean id="cat" class="com.rm.pojo.Cat"/>
<bean id="dog" class="com.rm.pojo.Dog"/>
<bean id="people" class="com.rm.pojo.People" autowire="byName">
<property name="name" value="李四"/>
</bean>
1.7.2 ByType自动装配
<bean class="com.rm.pojo.Cat"/>
<bean class="com.rm.pojo.Dog"/>
<bean id="people" class="com.rm.pojo.People" autowire="byType">
<property name="name" value="李四"/>
</bean>
小结
- 使用ByName的时候,需要保证所有bean的id唯一,并且这个bean的id需要和自动注入的属性的set方法后面的值一致。
- 使用ByType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致。
1.7.3 使用注解实现自动装配
- jdk1.5后支持注解
- Spring2.5后支持注解
使用注解须知
-
导入约束(context约束)
-
开启注解的支持 context:annotation-config/
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置注解的支持-->
<context:annotation-config/>
</beans>
@Autowired()
- 直接在属性上使用即可,也可以在set方法上使用
@Data
public class People {
private String name;
@Autowired
private Cat cat;
@Autowired
private Dog dog;
}
科普:
@Nullable 属性标记了这个注解,说明这个属性可以为null
public @interface Autowired {
boolean required() default true;
}
测试
@Data
public class People {
private String name;
@Autowired(required = false)
private Cat cat;
@Autowired
private Dog dog;
}
如果显式的设置该注解的required,说明该对象可以为null
@Qualifier(value=“xxx”)
- 如果@Autowired自动装配环境比较复杂,自动装配无法通过一个注解【@Autowired】完成的时候
- 可以使用@Qualifier(value=“id值”)来配合@Autowired使用,指定一个唯一的bean对象注入
@Data
public class People {
private String name;
@Autowired
@Qualifier(value = "cat")
private Cat cat;
private Dog dog;
}
@Resource(name=“xxx”)
- @Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,
如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Data
public class People {
private String name;
@Resource(name = "cat")
private Cat cat;
@Resource
private Dog dog;
}
1.8 使用注解开发
1.8.1 导入aop包
在Spring4之后,要使用注解开发,必须要保证aop的包导入了
1.8.2 开启注解支持
使用注解需要导入context约束,开启注解的支持
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
1.8.3 @Component()
该注解放在类上,说明这个类被spring管理了,等价于<bean id="user" class="com.rm.pojo.User"/>
- 属性如何注入
@Component()
public class User {
/*@Value("李白")等价于
<bean id="user" class="com.rm.pojo.User">
<property name="name" value="李白"/>
</bean>
*/
@Value("李白")
private String name;
}
@Component衍生的注解
在web开发中,会按照MVC三层架构分层
- dao层【@Repository】
- service层【@Service】
- controller层 【@Controller】
这四个注解功能都是一样的,都是代表将某个类注册到Spring中,装配bean
1.8.4 自动装配
- 前面讲到过
1.8.5 作用域
- @Scope(“prototype”) 该类的作用域,根据属性值不同,标记该类的作用域
小结
xml和注解的最佳实现
- xml用来管理bean
- 注解只用来完成属性的注入
- 使用过程中,需要注意,注解生效需要开启注解的支持
<!--扫描包就不需要再开启注解的支持-->
<context:component-scan base-package="com.rm"/>
<!--<context:annotation-config/>-->
1.9 使用Java的方式配置Spring(重点)
- 完全不使用Spring的xml配置了,全部使用Java。
- JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能。
1.9.1 实体类
- 编写实体类,加上Component注解,并为其的每一个属性赋值
@Data
@Component
public class User {
@Value("张三")
private String name;
@Value("18")
private int age;
}
1.9.2 配置类
- 编写一个配置类,加上@Configuration注解,并在其中注册Bean到Spring中
@Configuration
-
@Configuration注解相当于配置文件的,可以在该注解标记下的类中注册bean
-
方法返回值类型,等价于配置文件中bean标签的class属性
-
方法名是唯一标识符,等价于配置文件中bean标签的id属性,获取该bean的时候使用。
@Configuration
public class AppConfig {
@Bean
public User user(){
return new User();
}
}
编写一个测试类,使用AnnotationConfigApplicationContext类获取上下文对象
获取bean使用new AnnotationConfigApplicationContext(配置类.class)获取
public class MyTest {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
User user = context.getBean("user", User.class);
System.out.println(user);
}
}
结果
1.10 代理模式
代理模式是SpringAOP的底层实现原理【SpringAOP和SpringMVC】
- 抽象角色【用接口或者抽象类表示】真实对象和代理对象实现相同的接口
- 真实对象:被代理的角色
- 代理对象:代理真实对象,一般在代理真实对象后,进行一些附属操作
- 客户:访问代理对象的人
1.10.1 代理模式的好处
- 可以使真实角色的功能更加纯粹,不用去关注一些公共业务
- 公共业务交给代理角色去实现,实现了业务的分工
- 公共业务发生拓展的时候,方便集中管理
1.10.1 代理模式的缺点
-
一个真实角色就会产生一个代理角色,代码量会翻倍【可以使用动态代理避免】
-
AOP的实现机制
1.10.3 静态代理
使用代码实现
抽象对象
//抽象角色
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() {
this.seeHouse();
host.rent();
this.agreement();
}
//代理对象可以增加一些附属操作
public void seeHouse(){
System.out.println("中介带你看房子");
}
public void agreement(){
System.out.println("中介和你签订房合同");
}
}
客户
//客户【需要租房的人】,找中介【代理对象】租房
public class Client {
public static void main(String[] args) {
Host host = new Host();
//通过构造器传递需要被代理的真实对象
Proxy proxy = new Proxy(host);
proxy.rent();
}
}
1.10.4 动态代理
- 动态代理的角色分类和静态代理角色一样
- 动态代理的代理类是动态生成的,不是直接写好的
- 动态代理分为两大类;基于接口的动态代理,基于类的动态代理
- 基于接口–JDK动态代理
- 基于类:cglib
- java字节码实现:javassist
需要了解两个类:
- Proxy:代理类
- InvocationHandler:调用处理程序
动态代理的好处
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
抽象对象
//抽象对象【业务接口】
public interface UserDao {
void delete();
void update();
}
真实对象
//真实对象【真实业务】
public class UserService implements UserDao {
public void delete() {
System.out.println("删除一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
}
动态生成代理对象的类
//用这个类。自动生成一个代理对象【可以将该类抽象为一个工具类】
public class MyInvocationHandler 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 msg){
System.out.println("调用了"+msg+"方法");
}
}
用户对象
public class Client {
public static void main(String[] args) {
//真实角色
UserService userService = new UserService();
//动态生成代理对象的类
MyInvocationHandler handler = new MyInvocationHandler();
//传入需要代理的真实对象
handler.setTarget(userService);
//获得代理对象
UserDao userDao = (UserDao) handler.getProxy();
//代理对象可以处理一类业务
userDao.update();
userDao.delete();
}
}
1.11 AOP
1.11.1 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
不影响原来业务类的前提下,实现业务的增强
1.11.2 AOP的作用
提供声明式事务,允许用户自定义切面
- 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志,安全,缓存,事务等等…
- 切面(ASPECT):横切关注点被模块化的特殊对象。即,它是一个类。【Log类】
- 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。【Log类中的一个方法】
- 目标(Target)︰被通知对象。【接口】
- 代理(Proxy):向目标对象应用通知之后创建的对象。【代理对象】
- 切入点(PointCut):切面通知执行的“地点"的定义。
- 连接点(JointPoint):与切入点匹配的执行点。
1.12 实现AOP
使用AOP织入,需要先导入一个织入包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
1.12.1 使用Spring的API接口
- 业务接口
public interface UserService {
void add();
void delete();
void update();
void select();
}
- 接口实现类
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("更新了一个用户");
}
public void select() {
System.out.println("查询了一个用户");
}
}
- 前置日志实现
public class BeforeLog implements MethodBeforeAdvice {
/*
method:要执行的目标对象的方法
args:参数
target:目标对象
*/
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(method.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
- 后置日志实现
public class AfterLog implements AfterReturningAdvice {
//returnValue:方法执行后的返回值结果
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+method.getName()+"方法,返回值结果为:"+returnValue);
}
}
- 注册bean及配置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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userServiceImpl" class="com.rm.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.rm.log.BeforeLog"/>
<bean id="afterLog" class="com.rm.log.AfterLog"/>
<!--方式一,使用原生的SpringAPI接口-->
<!--配置aop,需要导入aop的约束-->
<aop:config>
<!--切入点,expression:表达式execution(返回值类型 哪个类的哪个方法(参数[..表示任意参数])-->
<aop:pointcut id="pointcut" expression="execution(* com.rm.service.UserServiceImpl.*(..))"/>
<!--执行环绕增加-->
<aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试类
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的是接口,返回值类型是接口类型,而不是实现类
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.add();
}
}
1.12.2 自定义类来实现AOP【建议使用】
- 自定义切面
public class DiyPointcut {
public void before(){
System.out.println("方法执行前。。。。");
}
public void after(){
System.out.println("方法执行后。。。。");
}
}
- 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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userServiceImpl" class="com.rm.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.rm.log.BeforeLog"/>
<bean id="afterLog" class="com.rm.log.AfterLog"/>
<!--方式二,使用自定义类实现-->
<bean id="diyPointcut" class="com.rm.diy.DiyPointcut"/>
<aop:config>
<!--自定义切面,ref:要引用的类-->
<aop:aspect id="diy" ref="diyPointcut">
<!--切入的位置-->
<aop:pointcut id="pointcut" expression="execution(* com.rm.service.UserServiceImpl.*(..))"/>
<!--增加环绕-->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
- 测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的是接口,返回值类型是接口类型,而不是实现类
UserService userService = (UserService) context.getBean("userServiceImpl");
userService.add();
}
}
结果
方法执行前。。。。
增加了一个用户
方法执行后。。。。
1.12.3 使用注解实现
- 自定义注解类切面
@Aspect//标注这个类是一个切面
public class AnnotationPoint {
@Before("execution(* com.rm.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("==方法执行前==");
}
@After("execution(* com.rm.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("==方法执行后==");
}
@Around("execution(* com.rm.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("==环绕前==");
//执行方法【相当于过滤器】
pj.proceed();
System.out.println("==环绕后==");
}
}
- 在Spring核心配置文件中开启注解的支持
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--注册bean-->
<bean id="userServiceImpl" class="com.rm.service.UserServiceImpl"/>
<bean id="beforeLog" class="com.rm.log.BeforeLog"/>
<bean id="afterLog" class="com.rm.log.AfterLog"/>
<!--方式三:使用注解实现AOP-->
<bean id="annotationPoint" class="com.rm.diy.AnnotationPoint"/>
<!--开启注解支持 JDK【默认】(proxy-target-class="false") cglib(proxy-target-class="true")-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
- 测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理的是接口,返回值类型是接口类型,而不是实现类
UserServiceImpl userService = (UserServiceImpl) context.getBean("userServiceImpl");
userService.add();
}
}
- 结果
==环绕前==
==方法执行前==
增加了一个用户
==环绕后==
==方法执行后==
Spring整合Mybatis
步骤:
导入相关jar包
- junit
- mybatis
- mysql数据库
- spring相关
- aop织入
- mybatis-spring
编写配置文件
测试
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--Spring操作数据库,还需要一个spring-jdbc的包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.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.3</version>
</dependency>
</dependencies>
2.1 回顾Mybatis
- 编写实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private String password;
}
- 编写核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis核心配置文件-->
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<package name="com.rm.pojo"/>
</typeAliases>
<!--可以包含多个环境,default选择使用哪个环境-->
<environments default="mybatis">
<environment id="mybatis">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<!--在xml中,特殊字符需要转义&相当于&-->
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/rm/mapper/UserMapper.xml"/>
</mappers>
</configuration>
- 编写接口
public interface UserMapper {
List<User> findAll();
}
- 编写接口对应的Mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--该配置文件相当于过去的接口实现类-->
<!--nameSpace命名空间,绑定一个指定的Mapper(过去的Dao接口)-->
<mapper namespace="com.rm.mapper.UserMapper">
<!--id属性对应着接口实现类的方法名-->
<select id="findAll" resultType="user">
select * from user
</select>
</mapper>
- 工具类
public class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory;
//静态代码块,类加载的同时获取到sqlSessionFactory
static {
InputStream inputStream =null;
try {
//根据流得到sqlSessionFactory(根据工厂建造者对象获得)
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭流,释放资源
if(inputStream!=null){
try {
inputStream.close();
} catch (IOException e) {
}
}
}
}
//创建一个获取sqlSession对象的方法(根据工厂获得)
//sqlSession用来执行sql语句
public static SqlSession getSqlSession(){
SqlSession session = sqlSessionFactory.openSession();
return session;
}
}
- 测试
public class MyTest {
@Test
public void findAll(){
//获取sqlSession对象
SqlSession sqlsession = MybatisUtil.getSqlSession();
//执行sql语句
UserMapper mapper = sqlsession.getMapper(UserMapper.class);
List<User> users = mapper.findAll();
for (User user : users) {
System.out.println(user);
}
sqlsession.close();
}
}
测试结果
User(id=1, name=张三, password=123456)
User(id=2, name=hhhhh, password=123456)
...
2.2 Mybatis-spring
- 编写数据源配置
- sqlSessionFactory
- sqlSessionTemplate
- 接口实现类【多的一步,目的是注入sqlSession】
- 整合applicationContext
- 测试
接口(UserMapper)
public interface UserMapper {
List<User> findAll();
}
接口映射文件(UserMapper.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--该配置文件相当于过去的接口实现类-->
<!--nameSpace命名空间,绑定一个指定的Mapper(过去的Dao接口)-->
<mapper namespace="com.rm.mapper.UserMapper">
<!--id属性对应着接口实现类的方法名-->
<select id="findAll" resultType="user">
select * from user
</select>
</mapper>
Mybatis核心配置文件(mybatis-config.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--mybatis核心配置文件-->
<configuration>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--一般将别名和设置交给mybatis管理,其余的交给spring-->
<typeAliases>
<package name="com.rm.pojo"/>
</typeAliases>
</configuration>
(spring-dao.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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--DataSources:使用Spring的数据源替换Mybatis的数据源配置-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&charsetEncoding=utf-8"/>
</bean>
<!--sqlSessionFactory-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--绑定Mybatis配置文件,使spring配置和mybatis配置连接-->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--注册映射器,mybatis配置中可以省略-->
<property name="mapperLocations" value="classpath:com/rm/mapper/*.xml"/>
</bean>
<!--sqlSessionTemplate:就是我们使用的sqlSession-->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<!--没有set方法,只能通过构造器传递参数-->
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
</beans>
接口实现类(UserMapperImpl)
public class UserMapperImpl implements UserMapper{
//使用sqlSessionTemplate(sqlSession)
private SqlSessionTemplate sqlSession;
//通过set方法注入
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
public List<User> findAll() {
//调用该类的方法即可获取结果
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.findAll();
}
}
整合到spring核心配置文件中(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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<import resource="spring-dao.xml"/>
<!--将实现类注册到spring中-->
<bean id="userMapper" class="com.rm.mapper.UserMapperImpl">
<!--注入sqlSession-->
<property name="sqlSession" ref="sqlSession"/>
</bean>
</beans>
测试
public class MyTest {
@Test
public void findAll(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
List<User> users = userMapper.findAll();
for (User user : users) {
System.out.println(user);
}
}
}
整合Mybatis方式二
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> findAll() {
SqlSession sqlSession = getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
return mapper.findAll();
}
}
测试
public class MyTest {
@Test
public void findAll(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
List<User> users = userMapper.findAll();
for (User user : users) {
System.out.println(user);
}
}
}
2.3 事务
2.3.1 声明式事务
- 使用AOP横切
- 不影响业务原有代码
事务织入
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<!--结合AOP,实现事务的织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--给哪些方法配置事务(*表示所有方法)-->
<!--配置事务的传播特性:propagation="REQUIRED(默认,如果当前没有事务,就新建一个事务)-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--配置事务切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.rm.mapper.*.*(..))"/>
<!--将事务切入-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
2.3.2 编程式事务
- 需要改变业务逻辑代码