Spring笔记
1、Spring概况
Spring雏形:interface21
下载地址:https://repo.spring.io/release/org/springframework/spring/
1.1、Spring的优点
- 一个轻量级、非入侵式的开源免费框架(容器)
- 控制反转(IoC)、面向切面编程(AOP)
- 支持事务处理,可对框架整合
1.2、Spring的本质
- 控制反转,依靠依赖注入的方式来实现。
以一个servcie对象为例,即是service暴露注入接口(构造,set方法),由spring配置对象注入(设置)给该service对象,这样可以做到Service层专注业务,不需要改变自身代码,只需要在调用(注入)的时候改变对象,即可改变service的具体实现,service面向接口编程,由service主动构建对象到被动接收外部注入的对象。同时Spring作为容器,会自己构建对象,这些对象可以作为参数来注入。对象由Spring来创建,管理,装配。
1.3、Spring Maven 依赖
org.springframework
spring-webmvc
5.2.2.RELEASE
org.springframework
spring-jdbc
5.2.2.RELEASE
1.4、组成(七大模块)
-
Spring Core (核心容器):
核心容器提供spring框架的基础功能。核心容器的主要组件是BeanFactory,它是工厂模式的实
现。BeanFactory使用控制反转(IoC)模式将应用程序的配置和依赖性规范的应用程序代码分开。
-
Spring Context(上下文):
spring上下文是一个配置文件,向spring提供上下文信息,spring上下文包括企业服务,例如
JBDI、EJB、电子邮件、国际化、校验和调度功能。
Spring AOP:
通过配置管理特性、AOP模块直接面向切面的棉城功能集成到了Spring框架中。所以,可以很容
易地使 Spring 框架管理的任何对象支持 AOP。Spring AOP 模块为基于 Spring 的应用程序中
的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务
管理集成到应用程序中。
- Spring DAO:
JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应
商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量
(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:
Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、
Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web:
Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所
以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求
参数绑定到域对象的工作。
- Spring MVC:
MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为
高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
2、控制反转(IoC)
2.1、IoC的本质
IoC (Inversion of Control 控制反转)是一种设计思想,通过描述 (XML或注解) 并通过第三方去生成或获取特定对象的方式,在Spring中实现控制反转的是IoC容器,DI (Dependency Injection 依赖注入)是实现IoC的一种方式。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。
2.2、如何理解IoC控制反转
- 谁控制谁?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由IoC容器来控制对象的创建,所以是IoC容器控制对象。 - 控制什么?
主要控制外部资源的获取(不只是对象也包括文件等等) - 为何是反转?
有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转,而反转则是 由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受
依赖对象,所以是反转。 - 哪些方面反转了?
所依赖对象的获取被反转了。
2.3、IoC能做什么
IoC不是一种技术,只是一种思想,一个重要的面对对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合、难测试,有了IOC容器后,把创建和查找依赖对象的控制权交给容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,也方便测试,利于功能复用,使得程序体系架构变得灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
IoC很好的体现了面向对象设计法则之一 ———— 好莱坞法则:“别找我们,我们找你”;即由IoC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。
2.4、实例与创建对象的方式
2.4.1、 IoC原型实例
1、先写一个UserDao接口
public interface UserDao {
public void getUser();
}
2、再写Dao的实现类
public class UserDaoImpl implements UserDao {
@Override
public void getUser() {
System.out.println("获取用户数据");
}
}
3、然后写UserService的接口
public interface UserService {
public void getUser();
}
4、最后写Service的实现类
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
5、测试
@Test
public void test(){
UserService service = new UserServiceImpl();
service.getUser();
}
以上是传统Java SE程序的写法,修改如下:
增加一个Userdao的实现类 UserDaoMySqlImpl.java
public class UserDaoMySqlImpl implements UserDao {
@Override
public void getUser() {
System.out.println("MySql获取用户数据");
}
}
要使用MySql时 , 就需要去service实现类里面修改对应的实现
public class UserServiceImpl implements UserService {
private UserDao userDao = new UserDaoMySqlImpl();
@Override
public void getUser() {
userDao.getUser();
}
}
假设, 再增加一个Userdao 的实现类 UserDaoOracleImpl.java
public class UserDaoOracleImpl implements UserDao {
@Override
public void getUser() {
System.out.println("Oracle获取用户数据");
}
}
这时要调用Oracle, 就需要去service实现类里面修改对应的实现,如果类似的需求非常多,这种方式便不适用,耦合性非常高。
如何解决 ?
利用set方法 , 修改代码如下:
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 利用set实现
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void getUser() {
userDao.getUser();
}
}
修改测试类
@Test
public void test(){
UserServiceImpl service = new UserServiceImpl();
service.setUserDao(new UserDaoMySqlImpl());
service.getUser();
//如果又想用Oracle去实现呢?
service.setUserDao(new UserDaoOracleImpl());
service.getUser();
}
区别:在使用set方法之前,所有需要的对象都是由程序员主动创建,使用set方法注入之后,主动权由程序员转移到了调用者,程序员不用再理会对象的创建,可以更专注业务的实现,耦合性大大降低,这便是IoC 的原型。
2.4.2、第一个Spring程序
1、导入Maven依赖项
org.springframework
spring-webmvc
5.2.2.RELEASE
2、编写User实体类 User.java
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 + '\'' +'}';
}
}
3、编写spring配置文件 applicationContext.xml
4、创建测试类
public class UserTest {
public static void main(String[] args){
//解析applicationContext.xml文件,生成并管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//getBean的参数为spring配置文件中bean的id
User user = (User) applicationContext.getBean("user");
System.out.println(user.toString());
}
}
输出
扩展
在上述IoC原型实例中新增加Spring配置文件 applicationContext.xml
测试
@Test
public void test2(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
serviceImpl.getUser();
}
如此一来就实现了解耦,要实现不同的操作,只需要在xml配置文件中修改配置。
2.4.3、IoC创建对象的方式
2.4.3.1、通过无参构造方法
1、修改 2.4.2第一个spring程序 实例中 User 类,增加User显式无参构造方法,其他不变
public class User {
private String name;
public User() {
System.out.println("user无参构造方法");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + '}';
}
}
2、测试类
public class UserTest {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.toString());
}
}
输出
结论:在执行 getBean 时,user对象已经创建好。(通过无参构造)
2.4.3.2、通过有参构造方法
1、修改 第一个spring程序 实例中 User 类,增加User有参构造方法
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" + "name='" + name + '\'' + '}';
}
}
2、修改applicationContext.xml配置文件,针对有参构造方法有三种配置方式
3、测试类
public class UserTest {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.toString());
}
}
输出
结论:管理的对象在配置文件加载的时候初始化完成。
2.5、依赖注入(DI)
概念
DI (Dependency Injection) 依赖注入:组件之间的依赖关系由容器在运行期决定,也就是说,由容器动态将某个依赖关系注入到组件中。依赖注入并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过注入机制,只需要通过简单的配置,无需任何代码就可以指定目标需要的资源,完成自身的业务逻辑,不需要关心具体的资源来自何处,由谁实现。
IoC在系统运行中,会动态的向某个对象提供它(对象)所需的其他对象,这一点是通过DI(依赖注入)来实现的,比如对象A需要操作数据库,以前我们总是要在A中自己编写代码来获得一个Connection对象,有了 spring我们就只需要告诉spring,A中需要一个Connection,至于这个Connection怎么构造,何时构造,A不需要知道。在系统运行时,spring会在适当的时候制造一个Connection,然后像打针一样,注射到A当中,这样就完成了对各个对象之间关系的控制。A需要依赖 Connection才能正常运行,而这个Connection是由spring注入到A中的,依赖注入的名字就这么来的。那么DI是如何实现的呢? Java 1.3之后一个重要特征是反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
如何理解DI?
-
谁依赖谁?
应用程序依赖IoC容器
-
为什么需要依赖?
应用程序需要IoC容器来提供对象需要的外部资源
-
谁注入谁?
IoC容器注入应用程序的某个对象
-
注入了什么?
注入某个对象所需要的外部资源(包括对象、资源、常量数据)
IoC Service Provider 为被注入对象提供被依赖对象有如下几种方式
- 构造器注入
- set方法注入
- 自动装配注入
- 注解注入
2.5.1、构造器注入
(参考 2.4.3.1 跟 2.4.3.2 实例)
2.5.2、set方法注入
要求被注入的属性必须有set方法,set方法的方法名由 set + 属性名首字母大写(如果属性的类型为boolean,则为 is + 属性名首字母大写)。
实例:
Address.java
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Student.java
public class Student {
private String name;
private Address address;
private String[] books;
private List hobby;
private Map card;
private Set games;
private String wife;
private Properties info;
/**省略 getter 跟 setter 方法**/
public void show() {
StringBuilder sb = new StringBuilder()
.append("name = ").append(name).append("\n")
.append("address = ").append(address.getAddress()).append("\n")
.append("hobby = ").append(hobby).append("\n")
.append("card = ").append(card).append("\n")
.append("games = ").append(games).append("\n")
.append("wife = ").append(wife).append("\n")
.append("info = ").append(info).append("\n")
.append("books = ");
if (books != null)
for (String book : books)
sb.append(String.format("《%s》 ", book));
System.out.println(sb.toString());
}
}
1、常量注入
2、Bean注入 ( ref 引用 )
3、数组注入
Java从入门到夺门而出
Spring概述
4、List注入
敲代码
看影视
5、Map注入
6、Set注入
LOL
csgo
7、Null注入
8、Properties注入
20191219001
男
测试类
@Test
public void diTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
student.show();
}
输出
2.5.3、自动装配
自动装配(autowire)是使用Spring满足bean依赖的一种方式,可以在applicationContext.xml配置文件的bean标签添加 autowire 属性,使得Spring可以自动在上下文给对象注入属性。
自动装配做的两件事:
- 组件扫描 (component scanning):Spring自动发现应用上下文中所创建的bean。
- 按照规则装配 (autowiring):Spring自动满足bean之间的依赖。(IoC/DI)
Spring中bean有三种装配机制:
- 在xml配置文件中显式配置
- 在java中显式配置
- 隐式的bean发现机制和自动装配
自动装配方式:
- byName
- byType
- constructor
实例环境
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
public class User {
private String name;
private Cat cat;
private Dog dog;
/**省略 getter 跟 setter 方法**/
}
public class AutowireTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(String.format("%s的宠物", user.getName()));
user.getCat().shout();
user.getDog().shout();
}
}
输出
2.5.3.1、byName
1、修改applicationContext.xml配置文件中的bean如下:
2、测试输出
3、继以上修改再将cat 的bean id 改为 catXXX
4、测试输出 (空指针异常)
使用byName小结:
byName属性会自动在上下文里找和自己属性对应set方法后边的名字(首字母改为小写)一样的bean id。若bean id 跟set方法后面的名字不一样,则报空指针异常。(id必须唯一)
2.5.3.2、byType
1、修改xml配置文件如下
2、测试输出(正常)
3、继续修改xml配置文件如下:
4、测试输出(NoUniqueBeanDefinitionException 异常)
5、继续修改xml配置文件如下:
6、测试输出 (正常)
使用byType小结:
byType会自动在上下文里找和自己属性类型相同的bean,类型要唯一,要被注入的bean没有id值也可以,class要唯一。
2.5.3.3、constructor
1、修改xml配置文件如下:
2、修改User.java,添加构造方法,如下:
public class User {
private String name;
private Cat cat;
private Dog dog;
public User(Cat cat, Dog dog) {
this.cat = cat;
this.dog = dog;
}
/**省略 getter 跟 setter 方法**/
}
3、测试输出
使用constructor小结:
constructor会尝试把它的构造函数的参数与配置文件中 beans 名称中的一个进行匹配和连线。如果找到匹配项,它会注入这些 bean,否则,会抛出异常。
2.5.4、注解注入
以2.5.3实例继续
2.5.4.1、@Autowired
@Autowired 注解表示按照类型自动装配(属于Spring规范),不支持id匹配。可以写在字段上,也可以写在setter方法上(使用@Autowired 注解可以省略set方法)。默认情况下要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false,如:@Autowired(required=false) 。
1、在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
2、开启属性注解支持
3、修改后完整的xml配置文件如下:
4、修改User.java,将set方法去掉,添加 @Autowired 注解
public class User {
private String name;
@Autowired
private Cat cat;
@Autowired(required = false) //如果允许对象为null,可以设置required为false,默认为true
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
}
5、测试输出
2.5.4.2、@Qualifier
@Qualifier 不能单独使用,需跟 @Autowired 配合使用,两者配合使用后可根据 byName 的方式来自动装配。
1、修改xml配置文件中的bean id不为类的默认名字,如下:
2、在User.java类中添加 @Qualifier 注解
@Autowired
@Qualifier(value = "cat22")
private Cat cat;
@Autowired(required = false) //如果允许对象为null,可以设置required为false,默认为true
@Qualifier(value = "dog11")
3、测试输出成功
2.5.4.3、@Resource
@Resource 注解(属于J2EE规范)为 @Autowired 和 @Qualifier 的结合版。先尝试以 byName 方式对属性进行查找装配,若不成功,则以 byType 的方式进行装配,若两者都不成功,则报异常。
@Resource 注解 默认按照名称匹配,名称可以通过name属性指定,如果没指定,当注解写在字段上时,默认取字段名进行查找,注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Autowired先byType,@Resource先byName。
1、修改xml配置文件,如下:
2、修改User.java类
@Resource(name = "cat22")
private Cat cat;
@Resource
private Dog dog;
3、测试输出成功
4、再修改xml配置文件如下:
5、修改User.java类,只保留 @Resource 注解
@Resource
private Cat cat;
@Resource
private Dog dog;
6、测试输出成功
2.6、Bean部分配置说明
别名
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
2.7、Bean的作用域
在Spring中,组成应用程序的主题及有Spring IoC容器所管理的对象,被称为Bean。换言之,Bean就是由IoC容器初始化、装配以及管理的对象。
Bean的作用域如下表:
以上4种作用域中的request跟session只能用在基于web的Spring ApplicationContext环境中。
2.7.1、Singleton
- 当一个bean的作用域为Singleton,那么Spring IoC容器中值存在一个共性的bean对象,并且所有对bean的请求只要id跟bean定义的相匹配,就只会返回bean的同一实例。
- Singleton是单例类型,在创建容器时就同时自动创建了bean的对象,每次获取到的对象都是同一个。
- Singleton作用域是Spring中的缺省作用域。
- 较适合单线程使用
User.java
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 + '\'' +'}';
}
}
在xml配置文件中将bean定义成Singleton
... ...
测试
public class UserTest {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
User user2 = (User) applicationContext.getBean("user");
System.out.println(user == user2);
}
}
输出
2.7.2、Prototype
- 当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的 getBean() 方法) 时都会创建一个新的bean实例。
- Prototype是原型类型,在创建容器时并没有实例化,而是在获取bean的时候才会创建一个对象,每次获取到对象都不是同一个对象。
- 对有状态的bean应该使用prototype作用域,而对无状态的bean则应使用Singleton作用域。
在xml配置文件中将bean定义成Prototype
... ...
public class UserTest {
public static void main(String[] args){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
User user2 = (User) applicationContext.getBean("user");
System.out.println(user == user2);
}
}
测试输出
2.7.3、Request
- 当一个bean的作用于为Request,表示在一次HTTP请求中,一个bean定义对应一个实例。即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。
- 该作用域仅在基于web的Spring ApplicationContext环境下有效。
在xml配置文件中将bean定义成Request
... ...
2.7.4、Session
- 当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。
- 该作用域仅在基于web的Spring ApplicationContext环境下有效。
在xml配置文件中将bean定义成Session
... ...
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与Request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域的bean也会被废弃掉。
3、Spring注解式开发
Spring 在 4.0 版本后引入了全注解开发模式,去除了编写繁琐的配置文件,使用配置类即可进行bean的扫描和装配。
3.1、简单使用和说明
步骤
1、使用注解模式必须先确保 aop包 的引入:
2、在配置文件引入context约束:
3、配置扫描哪些包下的注解:
4、使用和说明
/**
@Component 注解将此类标注为Spring的一个组件,并导入到IoC容器中。
此处相当于在xml配置文件中
为了更好的分层,Spring可以使用其他三个注解(功能一样),分别是:
@Controller 对Web层实现类标注
@Service 对Service层实现类标注
@Repository 对Dao层实现类标注
*/
@Component("cat")
public class Cat {
public void shout(){
System.out.println("miao~");
}
}
@Component("dog")
public class Dog {
public void shout(){
System.out.println("wang~");
}
}
@Component("user") //@Component("值") 注解将此类标注为Spring的一个组件,值相当于XML配置文件中的name属性。
@Scope("Singleton") //可用 @Scope 注解标注 Singleton 或 Prototype 模式
public class User {
// @Value注解相当于配置文件中 ,使用@Value注解可以不需要set方法。
@Value("woitumi")
private String name;
private int count;
@Autowired // @Autowired按照类型自动装配,也可使用 @Resource("cat")指定id
private Cat cat;
@Autowired
private Dog dog;
public String getName() {
return name;
}
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
@Value("2") // 如果有set方法也可以将 @Value("值") 设在set方法上。
public void setCount(int count) {
this.count = count;
}
public int getCount() {
return count;
}
}
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(String.format("%s有%d只宠物", user.getName(), user.getCount()));
user.getCat().shout();
user.getDog().shout();
}
}
输出
XML配置文件与注解装配比较
1、XML可以适用于任何场景,结构清晰,维护方便。
2、注解开发简单方便,但在非自己写的类里难以使用。
3.2、基于 JavaConfig 配置
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 版本后, JavaConfig 已正式成为 Spring 的核心功能 。
User.java类
public class User {
private String name;
private Car car;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
Car.java类 :
public class Car {
private String brand;
private String type;
//使用构造方法注入值
public Car(String brand, String type) {
this.brand = brand;
this.type = type;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
新建一个config配置包,编写一个MyConfig配置类,如下:
@Configuration //代表这是配置类
public class MyConfig {
@Bean("myCar") //@Bean("myCar") 注解会向容器中注册一个叫 myCar 的对象
public Car getCar() {
return new Car("保时捷", "911");
}
//向容器中注册装配一个 user 对象,并通过byType的方式注入car,对象car需要有set方法
@Bean(value = "user", autowire = Autowire.BY_TYPE)
public User getUser() {
User user = new User();
user.setName("woitumi");
return user;
}
}
写法二:
public class User {
@Value("woitumi") //使用 @Value("值") 注解将值注入,相应的字段可以不需要set方法
private String name;
private Car car;
public String getName() {
return name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
public class Car {
@Value("保时捷") //使用 @Value("值") 注解将值注入,可以不需要set方法
private String brand;
@Value("911")
private String type;
public String getBrand() {
return brand;
}
public String getType() {
return type;
}
}
@Configuration //@Configuration 代表这是配置类
// @ComponentScan("com.woitumi.springtest") //指定扫描包 (了解可以这么用)
// @Import(MyConfig2.class) //融合多个配置类
public class MyConfig {
@Bean("myCar") //@Bean("myCar") 注解会向容器中注册一个叫 myCar 的对象
public Car getCar() {
return new Car();
}
//向容器中注册装配一个 user 对象,并通过byType的方式注入car,对象car需要有set方法
@Bean(value = "user", autowire = Autowire.BY_TYPE)
public User getUser() {
return new User();
}
}
测试类
@org.junit.Test
public void test() {
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("user", User.class);
System.out.println(String.format("%s的车是%s%s",
user.getName(),
user.getCar().getBrand(),
user.getCar().getType()
));
}
输出
4、代理模式
为什么要学习代理模式?因为AOP的底层机制是动态代理。
代理模式作为23种经典设计模式之一,其比较官方的定义为“为其他对象提供一种代理以控制对这个对象的访问”,简单点说就是,之前A类自己做一件事,在使用代理之后,A类不直接去做,而是由A类的代理类B来去做。代理类其实是在之前类的基础上做了一层封装。
它的设计思路是:定义一个抽象角色,让代理角色和真实角色分别去实现它。
代理模式的核心作用
- 符合开闭原则,不用修改被代理者的任何代码就能扩展新的功能。
- 方便项目的扩展和维护。
角色分类
- 抽象角色:一般使用接口或抽象类来实现。
- 真实角色:被代理角色,实现抽象角色,定义真实角色所需要的业务逻辑,供代理角色调用。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑来实现抽象方法,并可在前后添加新操作。
- 客户:使用代理角色来实现一些操作。
代理模式分为
- 静态代理
- 动态代理
4.1、静态代理
什么是静态代理?
- 代理者和被代理者都实现了相同接口。(或继承了相同的父类)
- 代理者包含了一个被代理者的对象。
- 调用功能时,代理者会调用被代理者的功能,同时附加新的操作。
静态代理的缺点
- 只适合一种业务,如果有新的业务,就必须创建新的接口和新的代理,工作量变大了,开发效率降低。
实例一
Rent.java 抽象角色
//抽象角色:租房
public interface Rent {
public void rent();
}
Host.java 真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
Proxy.java 代理角色
//代理角色:中介
public class Proxy implements Rent {
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
//租房
public void rent(){
seeHouse();
host.rent();
fare();
}
//看房
public void seeHouse(){
System.out.println("带房客看房");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
Client.java 客户
//租客通过中介租到房东的房子
public class Client {
public static void main(String[] args) {
//房东要租房
Host host = new Host();
//中介帮助房东
Proxy proxy = new Proxy(host);
//租客找中介
proxy.rent();
}
分析: 在这个过程中,客户直接接触的是中介,就如同现实生活中的样子,你看不到房东,但是客户通过中介租到了房东的房子,这就是静态代理模式。
实例二
1、创建抽象角色,比如增删改查的业务逻辑
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
2、需要一个真实对象来完成增删改查操作
//真实对象,完成增删改查操作的人
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 query() {
System.out.println("查询");
}
}
3、如果需要增加一个日志功能,如何实现?
思路一:在实现类上增加代码。(麻烦)
思路二:使用代理,在不改变原有业务逻辑的情况下实现日志功能
4、定义一个代理角色来实现日志功能
//代理角色,在这里面增加日志的实现
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
public void add() {
log("add");
userService.add();
}
public void delete() {
log("delete");
userService.delete();
}
public void update() {
log("update");
userService.update();
}
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("执行了" + msg + "方法");
}
}
5、测试类
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能
proxy.setUserService(userService);
proxy.add();
}
}
4.2、动态代理
动态代理的特点
- 在不修改原有类的基础上,为原有类添加新的功能。
- 不需要依赖某个具体业务。
动态代理的好处
- 可以使得真实角色更加纯粹,不需再关注一些公共的业务。
- 公共的业务由代理完成,实现了业务的分工。
- 公共业务发生扩展时变得更加集中和方便。
动态代理分为
- JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,需要指定一个类加载器,然后生成的代理对象实现类的接口或类的类型,接着处理额外功能。
- CGLib动态代理:动态利用sam的开源包,对代理对象的Class文件加载进来,通过修改其字节码生成的子类来出来,CGLib是基于继承父类生成的代理类。
JDK代理和CGLib代理的区别
- JDK动态代理的被代理者必须实现任何接口。
- CGLib动态代理不用实现接口,主要对指定的类生成一个子类,覆盖其中的方法,添加额外的功能,通过继承实现。所以该类方法不能用final来修饰。
动态代理的实现步骤
- 代理类需要实现 InvocationHandler 接口。
- 实现 invoke 方法。
- 通过 Proxy 类的 newProxyInstance 方法来创建代理对象
JDK动态代理实例一 :
以 4.1租房实例 继续
//抽象角色:租房
public interface Rent {
public void rent();
}
Host . java 真实角色
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
ProxyInvocationHandler. java 即代理角色
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类,第二个参数获取要代理的抽象角色
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
// proxy : 代理类 method : 代理类的调用处理程序的方法对象.
// 处理代理实例上的方法调用并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHouse();
//核心:利用反射实现
Object result = method.invoke(rent, args);
fare();
return result;
}
public void seeHouse(){
System.out.println("带房客看房");
}
public void fare(){
System.out.println("收中介费");
}
}
Client . java
//租客
public class Client {
public static void main(String[] args) {
//被代理对象(真实角色)
Host host = new Host();
//代理实例的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setRent(host);
Rent proxy = (Rent)pih.getProxy(); //创建代理对象
proxy.rent();
}
}
JDK动态代理实例二:
//用户管理接口
public interface UserManager {
void addUser(String name, String password);
void delUser(String name);
}
//用户管理实现类,实现用户管理接口
public class UserManagerImpl implements UserManager {
public void addUser(String name, String password) {
System.out.println("addUser:" + name + ":" + password);
}
public void delUser(String name) {
System.out.println("delUser:" + name);
}
}
//JDK动态代理实现InvocationHandler接口
public class JdkProxy implements InvocationHandler {
private Object target;
//定义获取代理对象方法
public Object getJdkProxy(Object object) {
target = object;
//JDK动态代理只能针对实现了接口的类进行代理
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理,重写invoke方法调用被代理者的方法");
Object result = method.invoke(target,args);
System.out.println("JDK动态代理,可以执行附加操作");
return result;
}
}
@Test
public void JdkProxy() {
JdkProxy jdkProxy = new JdkProxy(); //实例化JdkProxy对象
//获取代理对象
UserManager userManager = (UserManager) jdkProxy.getJdkProxy(new UserManagerImpl());
userManager.addUser("woitumi", "wanantumi");
}
CGLib动态代理实例
首先需要导入asm跟cglib的 Maven依赖
org.ow2.asm
asm
7.2
cglib
cglib
3.3.0
public class CglibProxy implements MethodInterceptor {
private Object target;
//重写拦截方法
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("CGLib动态代理,重写invoke方法调用被代理者的方法");
Object result = method.invoke(target, objects);
System.out.println("CGLib动态代理,可以执行附加操作");
return result;
}
//定义获取代理对象方法
public Object getCglibProxy(Object object) {
target = object;
Enhancer enhancer = new Enhancer();
//设置父类,Cglig是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this); //设置回调
return enhancer.create(); //创建并返回代理对象
}
}
@Test
public void CglibProxy() {
CglibProxy cglibProxy = new CglibProxy();
UserManager userManager = (UserManager) cglibProxy.getCglibProxy(new UserManagerImpl());
userManager.delUser("张三");
}
扩展
实现JDK动态代理的工具类
public class ProxyInvocationHandler implements InvocationHandler {
private Object object;
/**
* 创建代理对象
* @param object 被代理者
* @return 代理者
*/
public Object createProxy(Object object) {
this.object = object;
//Proxy.newProxyInstance创建动态代理的对象,传入被代理对象的类加载器,接口,InvocationHandler对象
//注意 Proxy 是 java.lang.reflect.Proxy 包下的 Proxy
return Proxy.newProxyInstance(object.getClass().getClassLoader(),
object.getClass().getInterfaces(), this);
}
//调用被代理者方法,同时可以添加新操作
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//TODO 调用被代理者的方法
Object result = method.invoke(object, args);
//TODO 可添加新的操作
return result;
}
}
使用(如上述租房实例)
@Test
public void rentHost() {
Host host = new Host();
Rent proxy = (Rent) new ProxyInvocationHandler().createProxy(host);
proxy.rent();
}
5、面向切面编程(AOP)
5.1、AOP概述
AOP(Aspect Oriented Programming)意为:面向切面编程,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置上。面向切面编程是一种编程范式,它作为OOP面向对象编程的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、权限控制、缓存控制、日志打印等等。AOP采取横向抽取机制,取代了传统纵向继承体系的重复性代码。
AOP把软件的功能模块分为两个部分:
- 核心关注点
- 横切关注点
业务处理的主要功能为核心关注点,需要拓展的功能为横切关注点,利用AOP可以对业务逻辑的各个关注点进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。
例如,在一个业务系统中,用户登录是基础功能,凡是涉及到用户的业务流程都要求用户进行系统登录。如果把用户登录功能代码写入到每个业务流程中,会造成代码冗余,维护也非常麻烦,当需要修改用户登录功能时,就需要修改每个业务流程的用户登录代码,这种处理方式显然是不可取的。比较好的做法是把用户登录功能抽取出来,形成独立的模块,当业务流程需要用户登录时,系统自动把登录功能切入到业务流程中,以下为用户登录功能切入到业务流程示意图:
AOP相关概念
- 切面(Aspect):横切关注点,被模块化的特殊对象,是一个类。
- 连接点(Joinpoint):程序执行过程中的某一行为。
- 通知(Advice):切面 对于某个 连接点 所产生的动作。类中的方法。
- 切入点(Pointcut):匹配连接点,在AOP中通知和一个切入点表达式关联。
- 目标对象(Target Object):被一个或多个切面所通知的对象。
- AOP代理(AOP Proxy):向目标对象应用通知之后创建的对象。
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
- 前置通知(Before advice):在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛异常。
- 正常返回通知(After returning advice):在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
- 异常返回通知(After throwing advice):在连接点抛出异常后执行。
- 返回通知(After finally advice):在连接点执行完成后执行,不管是正常还是抛异常,都会返回通知中的内容。
- 环绕通知(Around advice):环绕通知围绕在连接点前后。这是一个强大的通知类型,能在方法调用前后自定义一些操作,环绕通知还需要负责决定是继续处理连接点(调用ProceedingJoinPoint的proceed方法)还是中断执行。
5.2、OOP与AOP对比理解
OOP (Object Oriented Programming) 面向对象编程,AOP (Aspect Oriented Programming) 面向切面编程。
纵向关系OOP,横向角度AOP
举个小例子:
设计一个日志打印模块。按 OOP 思想,我们会设计一个打印日志 LogUtils 类,然后在需要打印的地方引用即可。
public class ClassA {
private void initView() {
Log.d(TAG, "onInitView");
}
}
public class ClassB {
private void onDataComplete(Bean bean) {
Log.d(TAG, bean.attribute);
}
}
public class ClassC {
private void onError() {
Log.e(TAG, "onError");
}
}
看起来没有任何问题是吧?
但是这个类是横跨并嵌入众多模块里的,在各个模块里分散得很厉害,到处都能见到。从对象组织角度来讲,我们一般采用的分类方法都是使用类似生物学分类的方法,以「继承」关系为主线,我们称之为纵向,也就是 OOP。设计时只使用 OOP思想可能会带来两个问题:
- 对象设计的时候一般都是纵向思维,如果这个时候考虑这些不同类对象的共性,不仅会增加设计的难度和复杂性,还会造成类的接口过多而难以维护(共性越多,意味着接口契约越多)。
- 需要对现有的对象 动态增加 某种行为或责任时非常困难。
而AOP就可以很好地解决以上的问题,怎么做到的?除了这种纵向分类之外,我们从横向的角度去观察这些对象,无需再去到处调用 Log 打印日志了,声明哪些地方需要打印日志,这个地方就是一个切面,AOP 会在适当的时机把打印语句插进切面。
// 只需要声明哪些方法需要打印 log,打印什么内容
public class ClassA {
@Log(msg = "onInitView")
private void initView() {
}
}
public class ClassB {
@Log(msg = "bean.attribute")
private void onDataComplete(Bean bean) {
}
}
public class ClassC {
@Log(msg = "onError")
private void onError() {
}
}
如果说 OOP 是把问题划分到单个模块的话,那么 AOP 就是把涉及到众多模块的某一类问题进行统一管理。AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。利用 AOP 思想,这样对业务逻辑的各个部分进行了隔离,从而降低业务逻辑各部分之间的耦合,提高程序的可重用性,提高开发效率。
OOP 与 AOP 的区别
- 面向目标不同:OOP面向名词领域,AOP面向动词领域。
- 思想结构不同:OOP是纵向结构,AOP是横向结构。
- 注重方面不同:OOP注重业务逻辑单元的划分,AOP偏重业务处理过程中的某个步骤或阶段。
OOP 与 AOP 两者是一个相互补充和完善的关系。
5.3、使用
使用AOP织入,需要导入 aspectjweaver 依赖包
org.aspectj
aspectjweaver
1.9.5
在applicationContext.xml文件中配置AOP需要引入AOP约束,如下:
5.3.1、通过Spring API实现
//业务接口
public interface DataManager {
public void add();
public void delete();
public void update();
public void query();
}
//业务实现类
public class DataManagerImpl implements DataManager {
public void add() {
System.out.println("添加");
}
public void delete() {
System.out.println("删除");
}
public void update() {
System.out.println("更新");
}
public void query() {
System.out.println("查询");
}
}
//前置增强类
public class BeforeLog implements MethodBeforeAdvice {
/**
* @param method 要执行的目标对象的方法
* @param objects 被调用的方法的参数
* @param target 目标对象
*/
public void before(Method method, Object[] objects, Object target) throws Throwable {
System.out.println(String.format("执行 %s 类的 %s 方法前的日志",
target.getClass().getName(),
method.getName()
));
}
}
//后置增强类
public class AfterLog implements AfterReturningAdvice {
/**
* @param returnValue 返回值
* @param method 被调用的方法
* @param objects 被调用方法的对象参数
* @param target 被调用的目标对象
*/
public void afterReturning(Object returnValue, Method method, Object[] objects, Object target)
throws Throwable {
System.out.println(String.format("执行 %s 类的 %s 方法后的日志,返回值为 %s",
target.getClass().getName(),
method.getName(),
returnValue
));
}
}
@Test
public void aopTest() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
DataManager manager = (DataManager) context.getBean("dataManager");
manager.add();
}
附录:execution 表达式含义:
execution(* com.woitumi.aoptest.DataManagerImpl.*(..))
5.3.2、自定义类实现AOP
继上述例子 业务接口跟业务实现类不变,修改增加
//自定义切入类
public class CustomPointcut {
public void before() {
System.out.println("before");
}
public void after() {
System.out.println("after");
}
}
5.3.3、使用注解实现AOP
继以上实例修改增加
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.woitumi.aoptest.DataManagerImpl.*(..))")
public void before() {
System.out.println("before");
}
@After("execution(* com.woitumi.aoptest.DataManagerImpl.*(..))")
public void after() {
System.out.println("after");
}
@Around("execution(* com.woitumi.aoptest.DataManagerImpl.*(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:" + joinPoint.getSignature());
//执行目标方法proceed
Object proceed = joinPoint.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
6、整合MyBatis
(后续补充)
7、声明式事务
(后续补充)