- 为啥要用 Spring 张三是一个编程小白,他每次在 service 层写代码都要本人 new 一堆 Dao 接口的实现类。public class ProjectServiceImpl implements ProjectService {
UserDao userDao = new UserDaoImpl();
ProjectSectionDao projectSessionDao = new ProjectSessionDaoImpl();
ProjectDao projectDao = new ProjectDaoImpl();
SupplyDao supplyDao = new SupplyDaoImpl();
…….
}
复制代码有一天正 new 着对象,张三心想:” 我这一个 service 都须要 new 好多 Dao,那如果有一堆 service,那我不得破费好长时间?”” 有没有一个工具类或者什么框架能帮我治理这些对象?我只须要配置一下,须要的时候它就能主动帮我 new 个对象进去?” 张三陷入了深深的深思之中。张三的室友李四也是一个编程小白。李四呢想给本人的小我的项目减少一个性能:记录办法执行的工夫。后果他脑子一热居然给所有的办法都减少了一堆打印办法:System.out.println(“ 我的项目开始执行 ”);
// 开始工夫
long start = System.currentTimeMillis();
// 业务代码
// 完结工夫
long end = System.currentTimeMillis();
// 计算执行工夫
System.out.printf(“ 执行工夫:%d 毫秒.”, (end – start));
复制代码过了半个小时,李四终于给我的项目中所有的办法都复制粘贴上了打印语句。他长舒一口气:” 我真是个大聪慧!”
张三看了一眼李四的代码,连连鼓掌:” 妙啊!咱们宿舍的技术大神!” 旁边的王五切实忍不住了,对张三说:” 妙个屁!最近的 Spring 框架课你俩是不是都没去?光顾着打游戏了?我都替你俩答了三次到了!” 李四问王五:” 这个 Spring 框架学了有用吗?” 王五:” 不仅能解决张三说的治理对象的问题,还能帮你解决记录日志的问题。配置完 Spring,你只须要定义一个切面类,基本不须要在一堆类下面复制粘贴一堆代码。” 张三摸摸后脑勺笑着说:” 原来 Spring 框架那么好用,我当前再也不逃课了。我这就去翻课本学习 Spring 框架去。”2. Spring 简介 Spring 是一个轻量级的 Java 开发框架。Spring 的外围是管制反转 (IOC) 和面向切面编程(AOP)。Spring 次要有如下长处:1. 解耦 2. 反对面向切面编程 3. 便于集成其余框架 3. 环境搭建 1. 创立 Maven 我的项目 File -> New -> Project -> Maven
2. 引入依赖 <dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
</dependencies>
复制代码 3. 创立接口和实现类 UserServicepublic interface UserService {
void print();
}
复制代码 UserServiceImplpublic class UserServiceImpl implements UserService{
@Override
public void print() {System.out.println("hello world");
}
}
复制代码 4. 创立配置文件 applicationContext.xml<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.xxl.service.impl.UserServiceImpl"/>
</beans>
复制代码 5. 测试 @Test
public void testSpring(){
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
UserService userService = (UserService)act.getBean("userService");
// 3. 调用办法
userService.print();
}
复制代码测试后果:
-
IOC4.1 IOC 简介 IOC,全称 Inversion of Control,意思是管制反转。它是 Spring 框架中的一种思维。管制反转就是将对象的控制权从程序中的代码转移到了 Spring 的工厂,通过 Spring 的工厂实现对象的创立以及赋值。
也就是说之前是咱们本人 new 对象、给对象中的成员变量赋值。当初是让 Spring 来帮忙咱们创建对象、给成员变量赋值。
4.2 Spring 核心内容形容 1. 配置文件 Spring 的配置文件能够放到我的项目中的任意一个中央,也能够随便命名,然而倡议应用:applicationContext.xml。你能够将这个配置文件看成一个装有一堆 bean 标签的容器。2.bean 标签 Spring 工厂创立的对象,叫做 bean,所以一个 bean 标签代表一个对象。<bean id=”userService” class=”com.xxl.service.impl.UserServiceImpl”/>
复制代码 bean 标签中必须要有 class 属性,它的值是一个类的全限定名(包名 + 类名)。除了 class 属性,bean 标签还能够设置 id、name、scope 属性。id:id 必须以字母结尾,相当于这个 bean 的身份证号,是惟一的。如果这个 bean 只应用一次,id 能够省略不写。如果这个 bean 须要被其余 bean 援用,或者这个 bean 要应用很屡次,则必须要有 id 属性。如果只配置 class 属性,Spring 框架会给每一个 bean 配置一个默认的 id:” 全限定名 #1″。例如:com.xxl.service.impl.UserServiceImpl#1
复制代码 name:name 相当于这个 bean 的别名,它能够配置多个,例如:<bean id=”user” name=”aa,bb,cc” class=”com.xxl.model.User”/>
复制代码 scope:scope 属性能够管制简略对象的创立次数,它有两个值:1.singleton:每次只会创立惟一⼀个简略对象,默认值。2.prototype:每⼀次都会创立新的对象。例如:<bean id=”user” class=”com.xxl.model.User” scope=”singleton”/>
复制代码 3.ApplicationContextApplicationContext 是 Spring 的工厂,次要用来创建对象。Spring 通过读取配置文件创立工厂。因为 Spring 的工厂会占用大量内存,所以一个程序个别只会创立一个工厂对象。4. 工厂罕用办法 1. 依据 id 获取对象 UserService userService = (UserService)act.getBean(“userService”);
复制代码 2. 依据 id 和类名获取对象 UserService userService = (UserService)act.getBean(“userService”,UserService.class);
复制代码 3. 只依据类名获取对象 UserService userService = (UserService)act.getBean(UserService.class);
复制代码 4. 获取配置文件中所有 bean 标签的 id 值 String[] beanDefinitionNames = act.getBeanDefinitionNames();
for (String beanDefinitionName : beanDefinitionNames) {
System.out.println(beanDefinitionName);
}
复制代码后果:5. 判断是否存在指定 id 或者 name 的 beanact.containsBean(“userService”)
复制代码 6. 判断是否存在指定 id 的 bean,只能用来判断 idact.containsBeanDefinition(“userService”)
复制代码 5. 创建对象 Spring 是如何创建对象的呢?工厂和反射。首先说下反射,咱们能够通过一个类的全限定名获取 Class 对象,而后再通过 Class 实例化一个对象:Class serviceClass = Class.forName(“com.xxl.service.impl.UserServiceImpl”);
UserService userService = (UserService)serviceClass.newInstance();
复制代码 Spring 配置文件中 bean 标签的 id 和类的全限定名一一对应,所以 Spring 工厂的 getBean 办法其实就是先依据 bean 的 id 获取该类的全限定名,而后再利用反射依据类的全限定名创建对象并返回。4.3 IOC 长处解耦。说起解耦之前先说下耦合:耦合是指代码之间的关联性太强,我如果改了这一段代码,可能会影响到一堆代码。那创建对象哪里有耦合了?其实就是 new 关键字带来的耦合。如果你发现一个接口的实现类须要批改,你须要手动改变程序中的代码,比方批改 new 关键字前面的实现类,这样可能会影响到其余的代码。然而应用了 Spring 之后,咱们只须要批改配置文件中 bean 标签的 class 属性对应的类的全限定名,不必批改程序中的代码,这样就做到理解耦。解耦就是解除不同代码之间的关联性、依赖性。5. DIDI 全称 Dependency Injection,意思是依赖注入,它是 IOC 的具体实现。依赖就是说我须要你,比方 Service 层依赖 Dao 层,注入就是赋值。依赖注入:应用 Spring 的工厂和配置文件为一个类的成员变量赋值。没有应用 Spring 的依赖注入咱们是这样赋值的:User user = new User();
user.setName(“ 张三 ”);
复制代码如果设置有误,就须要手动批改代码,代码耦合度较高,而依赖注入的呈现就是为理解耦。Spring 的依赖注入蕴含两种形式:5.1 set 注入 set 注入:Spring 调用 Set 办法通过配置文件为成员变量赋值。1. 创建对象,为属性增加 set/get 办法 public class User {
private String name;
private int age;public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
复制代码 2. 批改配置文件 <bean id=”user” class=”com.xxl.model.User”>
<property name=”name” value=” 知否君 ” />
<property name=”age” value=”18″ />
</bean>
复制代码 3. 测试 // 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext(“/applicationContext.xml”);
// 2、通过工厂类取得对象
User user = (User)act.getBean(“user”);
System.out.println(“ 姓名:”+user.getName());
System.out.println(“ 性别:”+user.getAge());
复制代码测试后果:从下面的例子能够看出 Set 注入就是在 property 标签中为属性赋值。spring 能够为 JDK 内置的数据类型进行赋值,也能够为用户自定义的数据类型进行赋值。5.1.1 JDK 内置数据类型 1. 根本类型 <property name=”name” value=” 知否君 ” />
<property name=”age” value=”18″ />
复制代码 2.List 汇合 <property name=”phones”>
<list><value>15799999918</value> <value>15788888819</value> <value>15766666620</value>
</list>
</property>
复制代码 3.Set 汇合 <property name=”phones”>
<set><value>15799999918</value> <value>15788888819</value> <value>15766666620</value>
</set>
</property>
复制代码 4.Map 汇合 <property name=”mapInfo”>
<map><entry> <key><value>name</value></key> <value> 知否君 </value> </entry> <entry> <key><value>age</value></key> <value>23</value> </entry>
</map>
</property>
复制代码 5. 数组 <property name=”phones”>
<list><value>15799999918</value> <value>15788888819</value> <value>15766666620</value>
</list>
</property>
复制代码 6.Properites<property name=”prop”>
<props><prop key="key1">value1</prop> <prop key="key2">value2</prop>
</props>
</property>
复制代码 5.1.2 用户自定义数据类型 1. 为成员变量增加 set/get 办法 public class UserServiceImpl implements UserService {private UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void print() {userDao.print();
}
}
复制代码 2.bean 标签应用 ref 属性 <bean id=”userDao” class=”com.xxl.dao.impl.UserDaoImpl” />
<bean id=”userService” class=”com.xxl.service.impl.UserServiceImpl”>
<property name=”userDao” ref=”userDao”/>
</bean>
复制代码 3. 测试 @Test
public void testSpring(){
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext(“/applicationContext.xml”);
// 2、通过工厂类取得对象
UserService userService = (UserService)act.getBean(“userService”);
// 3. 调用办法
userService.print();
}
复制代码测试后果:解释:下面的例子中,因为 userDao 是 userService 的一个成员变量,所以在配置文件中须要应用 property 标签,ref 指向了 userDao 这个对象,而后调用 userDao 的 set 办法为 userDao 赋值。
4. 主动注入咱们还能够应用 bean 标签的 autowire 属性为自定义变量主动赋值。当类中援用类型的属性名和 bean 标签的 id 值雷同时,咱们能够应用 byName。例如:<bean id=”userDao” class=”com.xxl.dao.impl.UserDaoImpl” />
<bean id=”userService” autowire=”byName” class=”com.xxl.service.impl.UserServiceImpl” />
复制代码当类中援用类型的全限定名和 bean 标签的 class 属性的值雷同,或者是子类、实现类,咱们能够应用 byType。例如:<bean id=”userDao” class=”com.xxl.dao.impl.UserDaoImpl” />
<bean id=”userService” autowire=”byType” class=”com.xxl.service.impl.UserServiceImpl” />
复制代码 5.2 结构注入结构注入:Spring 调用构造方法通过配置文件为成员变量赋值。1. 为类增加构造方法 public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
}
复制代码 2. 批改配置文件在 bean 标签中应用 constructor-arg 标签。<bean class=”com.xxl.model.User”>
<constructor-arg value="张三"/>
<constructor-arg value="18"/>
</bean>
复制代码 3. 测试 @Test
public void testSpring(){
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
User user= (User)act.getBean(User.class);
System.out.println("姓名:"+user.getName());
System.out.println("年龄:"+user.getAge());
}
复制代码测试后果:
5.3 注入总结注入就是通过 Spring 的配置文件为类的成员变量赋值。在理论开发中,咱们个别采纳 Set 形式为成员变量赋值。6. Bean 的生命周期 Bean 的生命周期指的就是由 Spring 治理的对象从创立到销毁的过程,和人生老病死的过程一样。它次要分为三个阶段:创立 –> 初始化 –> 销毁 6.1 创立阶段 Spring 的工厂创建对象的形式分两类:1. singleton 模式当 scope 属性为 singleton,创立 Spring 工厂的同时创立所有单例对象。例如:新建 User 类:public class User {
String name;
int age;
public User() {System.out.println("调用 User 的构造方法");
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
复制代码 spring 配置文件注册 bean:<bean id=”user” class=”com.xxl.model.User”>
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
复制代码测试: @Test
public void testSpring(){ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
}
复制代码执行后果:
咱们发现当创立 Spring 工厂的同时就会调用对象的构造方法。因为 spring 中 bean 默认的 scope 就是 singleton,所以创立工厂的同时默认就会创立多个单例对象。如果想批改创立单例对象的形式为获取的时候才创立,只须要在 bean 标签下面增加如下属性:lazy-init=”true”
复制代码例如:<bean id=”user” class=”com.xxl.model.User” lazy-init=”true”>
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
复制代码 2. prototype 模式只有获取对象的时候才会创建对象。批改 bean 标签的 scope 属性:<bean id=”user” class=”com.xxl.model.User” scope=”prototype”>
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
复制代码测试:@Test
public void testSpring(){ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
复制代码执行后果:
通过下面的例子咱们发现只有当执行 getBean() 办法的时候才会调用该类的构造方法。6.2 初始化阶段 spring 中 bean 的初始化操作指的是在创建对象的时候实现一些附加的性能。bean 的初始化操作有两种实现形式:1. 实现 InitializingBean 接口 public class 类名 implements InitializingBean {
public void afterPropertiesSet(){// 初始化办法操作}
}
复制代码例如:public class User implements InitializingBean {
String name;
int age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 初始化操作
@Override
public void afterPropertiesSet(){
this.name = "张无忌";
this.age = 30;
}
}
复制代码测试:@Test
public void testSpring(){ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
复制代码执行后果:
2. 通过创立一般办法实现初始化在 User 类中创立一个办法 // 初始化办法
public void initMethod() {
this.name = "张无忌";
}
复制代码在配置文件中配置 init-method 属性 <bean id=”user” class=”com.xxl.model.User” init-method=”initMethod” >
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
复制代码测试:@Test
public void testSpring(){ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
复制代码执行后果:
咱们发现该初始化办法在创建对象之后批改了 user 对象的名字。总结:初始化办法批改了注入的值,所以初始化办法肯定在注入之后执行。6.3 销毁阶段 Spring 销毁对象前,会调用对象的销毁办法,实现销毁操作。Spring 什么时候销毁所创立的对象?当 Spring 工厂敞开时,Spring 工厂会调用咱们自定义的销毁办法。销毁办法的定义有两种形式:1. 实现 DisposableBean 接口 public class 类名 implements DisposableBean {
// 销毁操作
@Override
public void destroy(){// 销毁操作业务}
}
复制代码 2. 创立一般办法在 User 类中创立一个办法 // 销毁办法
public void destroyMethod() {
// 销毁操作业务
}
复制代码在配置文件中配置 destroy-method 属性 <bean id=”user” class=”com.xxl.model.User” destroy-method=”destroyMethod”>
<property name="name" value="知否君"/>
<property name="age" value="23"/>
</bean>
复制代码 7. Bean 的后置解决 Spring 工厂创立完对象后如果还想对对象干点别的事件,除了初始化阶段,还能够采纳 Bean 的后置解决。Bean 的后置解决:对 Spring 工厂创立的对象进行二次加工解决,就是创立完对象后再干点别的事。Bean 后置解决的流程:1. 实现 BeanPostProcessor 接口 public class BeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("后置 bean:before 办法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("后置 bean:after 办法");
if (bean instanceof User) {User user = (User) bean;
user.setName("亚里士多德");
return user;
}
return bean;
}
}
复制代码 2. 配置文件增加 bean<bean id=”beanProcessor” class=”com.xxl.config.BeanProcessor”/>
复制代码 3. 测试 @Test
public void testSpring(){ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
Object user = act.getBean("user");
System.out.println(user);
}
复制代码执行后果:
后面咱们学习了对象的初始化办法,那么初始化办法和 Bean 的后置解决的执行程序是什么?咱们来批改一下 User 类,测试一下:public class User implements InitializingBean {
String name;
int age;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public void afterPropertiesSet(){System.out.println("初始化办法");
}
}
复制代码测试执行程序:
其实在理论开发中,咱们很少对 Spring 工厂创立的对象进行初始化操作,个别是采纳 Bean 的后置解决的形式来加工对象。BeanPostProcessor 接口有两个办法,这里简称 before 和 after 办法。这两个办法都是先获取 Spring 创立的对象,而后再对其加工,加工实现后再交给 Spring。因为这两个办法的作用一样,所以咱们个别采纳其中的一个办法,这里倡议采纳 after 办法。从下面的例子中咱们失去了 Spring 操作 bean 的程序:
-
代理设计模式 8.1 为啥要用代理设计模式?咱们先来看一个需要:在所有办法的执行前后输入一段日志。程序小白可能会这样写:接口:public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
复制代码实现类:public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {System.out.println("办法执行前打印"); int result = a + b; System.out.println("办法执行后打印"); return result;
}
@Override
public int sub(int a, int b) {System.out.println("办法执行前打印"); int result = a - b; System.out.println("办法执行后打印"); return result;
}
}
复制代码然而这样写有 3 个问题:1. 代码凌乱:业务代码和非业务代码混在一起,看着太乱了 2. 代码反复:如果有多个办法,那就要写一堆输入日志的代码片段,吃力不讨好。3. 代码耦合:如果非业务代码 (日志打印) 要做批改,那所有相干的业务办法都要改一遍,代码耦合度太高。那有什么解决办法呢?应用代理。生存中无关代理的例子无处不在,例如:一些大学能够面向寰球招生,所以会衍生很多留学中介,这些中介能够帮学校招生。所以中介的作用就是帮忙雇主做事,有了中介,雇主变得很轻松。而在 java 开发中,也存在这样的代理关系,它的专业术语是代理设计模式。代理设计模式能够很好解决下面开发中遇到的三个问题,帮忙咱们简化代码、进步工作效率。8.2 代理设计模式代理设计模式:通过代理类为指标类做一些额定 (非业务) 的性能。业余名词解释:1. 指标类 (原始类):指的是实现业务的外围类,个别指的是 service 层的各种实现类。2. 指标办法(原始办法):指标类中的办法是指标办法(原始办法)。3. 额定性能(附加性能):打印日志等非业务性能。代理设计模式开发步骤:(1) 代理类和指标类实现雷同的接口 (2) 代理类中除了要调用指标类的办法实现业务性能,还要实现额定性能。例如:// 接口
public interface CalculateService {
业务办法
}
// 指标类
public CalculateServiceImpl implements CalculateService {
业务办法
}
// 代理类:要实现目标类雷同的接口
public CalculateServiceProxy implements CalculateService {
// 业务办法
// 额定性能
}
复制代码 8.3 动态代理动态代理:给每一个指标类手动开发一个代理类。例如:public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
// 指标类
public CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
int result = a + b;
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
return result;
}
}
// 代理类:要实现目标类雷同的接口
public CalculateServiceProxy implements CalculateService {
private CalculateService calculateService = new CalculateServiceImpl();
@Override
public int add(int a, int b) {System.out.println("办法执行前打印");
int result = calculateService.add(a,b);
System.out.println("办法执行后打印");
return result;
}
@Override
public int sub(int a, int b) {System.out.println("办法执行前打印");
int result = calculateService.sub(a,b);
System.out.println("办法执行后打印");
return result;
}
}
复制代码通过下面的例子咱们发现动态代理也存在很多问题:1. 如果存在很多指标类,咱们就要手动创立一堆代理类,太繁琐。2. 代理类中混杂着指标类办法和额定性能,代码耦合度高。那有没有这样一种代理模式?1. 指标类和代理类互不烦扰 2. 代码耦合度低,便于保护有的,动静代理闪亮退场!8.4 动静代理动静代理:也是通过代理类为指标类做一些额定的性能,然而不必手动写一堆代理类,而是动静地为指标类创立代理类。开发流程:引入依赖 <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
复制代码
这里咱们次要是引入了 aspectj 这个技术,aspectj 是 spring 社区中十分风行的基于动静代理技术的框架。创立指标类和指标办法接口:public interface CalculateService {
// 加法
int add(int a,int b);
// 减法
int sub(int a,int b);
}
复制代码实现类(指标类):public class CalculateServiceImpl implements CalculateService {
@Override
public int add(int a, int b) {
int result = a + b;
System.out.println("加法操作。。。");
return result;
}
@Override
public int sub(int a, int b) {
int result = a - b;
System.out.println("减法操作。。。");
return result;
}
}
复制代码 3. 在 spring 配置文件中注册 bean <bean id=”calculateService” class=”com.xxl.service.impl.CalculateServiceImpl” />
复制代码 4. 实现额定性能这里咱们须要创立一个类实现 MethodInterceptor 接口:/**
- @Desc: 动静代理实现非业务性能
- @Author: 知否技术
- @date: 下午 8:49 2022/5/4
*/
public class PrintLog implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {System.out.println("在指标办法执行之前打印。。。。。");
// 执行指标办法
Object object = methodInvocation.proceed();
System.out.println("在指标办法执行之后打印。。。。。");
return object;
}
}
复制代码 5. 注册实现额定性能的 bean <bean id=”printLog” class=”com.xxl.aop.PrintLog” />
复制代码定义切入点 <!– 切入点: 给哪些办法退出额定性能 –>
<aop:config>
<aop:pointcut id=”pc” expression=”execution( (..))”/>
</aop:config>
复制代码组装切入点和额定性能 <!– 切入点: 给哪些办法退出额定性能 –>
<aop:config>
<aop:pointcut id=”pc” expression=”execution( (..))”/>
<aop:advisor advice-ref=”printLog” pointcut-ref=”pc”/>
</aop:config>
复制代码
8. 测试 @Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3. 调用办法
int result = calculateService.add(1, 2);
System.out.println("result:" + result);
}
复制代码
解说:1. 下面的例子中咱们定义了一个 PrintLog 打印日志的类,并实现了 MethodInterceptor 接口的 invoke 办法。invoke 办法外面实现了在指标办法执行前后打印日志的性能。2.invoke 办法的返回值就是原始办法的返回值,上个例子中的原始办法就是 add 办法。3.aop:config 这个标签用来配置切入点和额定性能。下面例子中额定性能就是在要执行的办法前后打印日志,而切入点就是额定性能要作用的地位:比方某些类上或者某些办法上。4.execution( (..)) 是切入点表达式,示意作用在所有类的所有办法上,这个前面会讲。5. 下面的例子示意:你无论执行哪个办法,这个办法的后面和前面都会打印一段日志。8.5 动静代理实现原理咱们通过 spring 的工厂获取的对象,其实是通过动静代理技术创立的代理类。那这个代理类在哪里?当程序运行的时候,spring 框架通过动静字节码技术在 JVM 内存中为指标类创立代理类。当程序运行完结的时候,这个代理类就会随之沦亡。所以应用动静代理不须要手动创立多个代理类。9. AOP9.1 AOP 概念 AOP: 全称 Producer Oriented Programing,即面向切面编程。那啥是面向切面编程?其实说白了还是 Spring 的动静代理,通过代理类为原始类减少一些 额定性能(例如打印等)。那啥是切面?切面 = 切入点 + 额定性能。切入点:额定性能作用的地位,在哪些类哪些办法上。
额定性能作用在不同的类下面,咱们都晓得点连接起来形成面,所以不同的切入点连接起来形成了切面,这个切面就像刀切西瓜一样切在不同的类下面,所以额定性能就对这些类中的办法起了作用。9.2 AOP 底层实现原理 AOP 的底层还是应用 Spring 的动静代理技术创立代理类对象。动静代理的形式分为两种:基于接口实现动静代理:JDK 动静代理基于继承实现动静代理:Cglib 动静代理 9.2.1 JDK 动静代理创立代理对象的三个元素:1. 原始对象 2. 额定性能 3. 原始对象实现的接口代码格局:Proxy.newPorxyInstance(classloader,interfaces,invocationHandler)
复制代码解说:(1)classloader:叫做类加载器,它能够用来创立代理对象。创立形式:类.class.getClassLOader()
复制代码 (2)interfaces:原始对象实现的接口创立形式接口.getClass().getInterfaces()
复制代码(3)invocationHandler:额定性能创立形式:InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("---- 办法执行前打印 ----");
// 执行原始办法
Object ret = method.invoke(caculateService, args);
System.out.println("---- 办法执行后打印 ----");
return ret;
}
};
复制代码残缺代码:@Test
public void testJDKProxy() {
// 1. 原始对象
CalculateService calculateService = new CalculateServiceImpl();
// 2. JDK 动静代理:蕴含额定性能
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("---- 办法执行前打印 ----");
// 执行原始办法
Object result = method.invoke(calculateService, args);
System.out.println("---- 办法执行后打印 ----");
return result;
}
};
// 3. 代理类
CalculateService calService = (CalculateService) Proxy.
newProxyInstance(CalculateService.class.getClassLoader(),
calculateService.getClass().getInterfaces(),
handler);
// 4. 执行办法
int result = calService.add(1, 2);
System.out.println("result:" + result);
}
复制代码测试后果:
9.2.2 Cglib 动静代理 CGlib 创立动静代理的原理:原始类作为父类,代理类作为子类,通过继承关系创立代理类。代码格局:Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(classLoader);
enhancer.setSuperclass(calculateService);
enhancer.setCallback(interceptor);
复制代码解说:(1)classLoader:类加载器(理解即可)(2)Superclass:父类,就是原始类(3)interceptor:额定性能 MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("---- 办法执行前打印 ----");
// 执行原始办法
Object result = method.invoke(calculateService, args);
System.out.println("---- 办法执行后打印 ----");
return result;
}
};
复制代码残缺代码:@Test
public void testCglibProxy() {
// 1. 原始对象
CalculateService calculateService = new CalculateServiceImpl();
// 2. Cglib 动静代理:蕴含额定性能
MethodInterceptor interceptor = new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("---- 办法执行前打印 ----");
// 执行原始办法
Object result = method.invoke(calculateService, args);
System.out.println("---- 办法执行后打印 ----");
return result;
}
};
Enhancer enhancer = new Enhancer();
enhancer.setClassLoader(CalculateService.class.getClassLoader());
enhancer.setSuperclass(calculateService.getClass());
enhancer.setCallback(interceptor);
// 3. 创立代理类
CalculateService calService = (CalculateService)enhancer.create();
// 4. 执行办法
int result = calService.add(3, 4);
System.out.println("result:" + result);
}
复制代码执行后果:
9.2.3 Spring 如何创立代理对象?Spring 是如何为原始对象创立指标对象的呢?是通过 BeanPostProcessor。后面咱们讲过 BeanPostProcessor 能够对对象进行二次加工,所以能够用来创立代理对象。Spring 创立代理对象的流程:实现 BeanPostProcessor 接口 /**
- @Desc: 后置 bean 创立代理对象
- @Author: 知否技术
- @date: 上午 11:59 2022/5/5
*/
public class ProxyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {return bean;}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {InvocationHandler handler = (proxy, method, args) -> {System.out.println("--- 办法执行前打印 6666666---");
Object ret = method.invoke(bean, args);
System.out.println("--- 办法执行后打印 7777777---");
return ret;
};
return Proxy.newProxyInstance(ProxyBeanPostProcessor.class.getClassLoader(), bean.getClass().getInterfaces(), handler);
}
}
复制代码注册 bean <bean id=”calculateService” class=”com.xxl.service.impl.CalculateServiceImpl” />
<bean id=”proxyBeanPostProcessor” class=”com.xxl.aop.ProxyBeanPostProcessor”/>
复制代码测试 @Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3. 调用办法
int result = calculateService.sub(7, 2);
System.out.println("result:" + result);
}
复制代码
9.3 基于注解开发 AOP 开发流程:开发切面类 @Aspect
public class TestAspect {
// 前置告诉:办法执行前增加额定性能
@Before("execution(* *(..))")
public void beforePrint(){System.out.println("------before: 办法执行前打印~");
}
// 后置告诉:办法执行后增加额定性能
@After("execution(* *(..))")
public void afterPrint(){System.out.println("------after: 办法执行前打印~");
}
// 盘绕告诉:办法执行前后增加额定性能
@Around("execution(* *(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("办法执行前打印~");
Object result = joinPoint.proceed();
System.out.println("办法执行后打印~");
return result;
}
}
复制代码配置切面类和扫描注解 <bean id=”testMyAspect” class=”com.xxl.aop.TestAspect” />
<!– 扫描 aop 相干注解 –>
<aop:aspectj-autoproxy/>
复制代码
测试 @Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
calculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3. 调用办法
int result = calculateService.add(100, 1);
System.out.println("result:" + result);
}
复制代码
解说:1. 咱们新建了一个 TestMyAspect 类,而后增加 @Aspect 注解,示意这是一个切面类,专门用来实现非业务性能的。2. 在这个类中,咱们创立了三个办法,其中 @Before 注解标注的办法示意在指标办法操作前执行。@After 注解标注的办法示意在指标办法操作后执行。@Around 注解标注的办法示意在指标办法操作前后执行。3. 在理论开发中个别应用 @Around 注解标注的办法实现非业务性能。4. 咱们新建了这个切面类,然而 spring 不晓得啊,所以须要在 Spring 的配置文件中注册一下 bean。5. 当初 Spring 工厂可能治理这个类了,然而 Spring 不晓得他是切面类啊!所以须要配置一下扫描注解的标签。6. 而后通过 Spring 获取创立的类,咱们获取的其实是 Spring 通过后置 Bean 加工后的代理类。切入点复用咱们能够在切面类中定义⼀个办法,办法下面标注 @Pointcut 注解。而后就能够重复使用切入点表达式了:@Aspect
public class TestAspect {
@Pointcut("execution(* *(..))")
public void myPointcut() {}
// 盘绕告诉:办法执行前后增加额定性能
@Around(value = "myPointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("办法执行前打印~");
Object result = joinPoint.proceed();
System.out.println("办法执行后打印~");
return result;
}
}
复制代码 9.4 切入点表达式切入点:额定性能退出的地位。<aop:pointcut id=”pc” expression=”execution( (..))”/>
复制代码 execution():切入点函数( (..)):切入点表达式 public int add(int a, int b)
-
- (..)
复制代码第一个 示意办法的修饰符和返回值 第二个 是办法名 .. 示意办法中的参数 1.(包. 类. 办法)切入点:修饰符 - 返回值 包. 类. 办法(参数)
expression=”execution(* com.xxl.service.caculateServiceImpl.add(..))”
复制代码 2. 指定切入点为某个包下的所有类中的所有办法:修饰符 - 返回值 包. 类. 办法(参数)
expression=”execution( com.xxl.service..*(..))”
复制代码 3.@annotation 作用:用于匹配以后执行办法持有指定注解的办法,并为之退出额定的性能。例如咱们自定义了一个注解:NoToken@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoToken {
}
复制代码办法中增加自定义注解:@Override
@NoToken
public int add(int a, int b) {
int result = a + b;
System.out.println("加法操作。。。");
return result;
}
复制代码而后咱们要为蕴含 NoToken 注解的办法退出额定性能:@Aspect
public class TestAspect {
// 盘绕告诉:办法执行前后增加额定性能
@Around("@annotation(com.xxl.annotion.NoToken)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("蕴含 NoToken 注解 --------------");
Object result = joinPoint.proceed();
return result;
}
}
复制代码测试:@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
CalculateService calculateService = (CalculateService) act.getBean("calculateService");
// 3. 调用办法
int result1 = calculateService.add(99, 1);
System.out.println("-----------------------");
int result2 = calculateService.sub(99, 1);
System.out.println("result1:" + result1);
System.out.println("result2:" + result2);
}
复制代码
- Spring 相干注解在讲注解之前咱们先看一下啥是注解。代码格局:@英文单词,例如:
作用地位:罕用在类上或者办法上用途:简化代码、实现某些性能所以 Spring 引入注解也是为了简化咱们的代码,通过应用简略的注解实现一些性能。10.1 创建对象相干注解咱们后面在学 IOC 的时候晓得如果想让 Spring 创建对象,必须要在配置文件中写 bean 标签。<bean id=”calculateService” class=”com.xxl.service.impl.CalculateServiceImpl” />
<bean id=”proxyBeanPostProcessor” class=”com.xxl.aop.ProxyBeanPostProcessor”/>
<bean id=”testMyAspect” class=”com.xxl.aop.TestAspect” />
……
复制代码可是如果想让 Spring 治理一堆对象,咱们就要写一堆 bean 标签。所以 Spring 为了简化代码,提供了一些与创建对象相干的注解。10.1.1 @Component 作用:替换 bean 标签,用来创建对象。就是在类下面加了这个注解,就不必在配置文件上写 bean 标签了。地位:类下面 id 属性:默认首单词首字母小写。// id 属性:默认首单词首字母小写。
@Component(“user”)
public class User{
}
复制代码
10.1.2 @Component 衍生注解咱们在开发程序的时候个别会将程序分层,例如分为管制层(controller),业务层(service),长久层(dao)。然而 @Component 注解并不能辨别这些类属于那些层,所以 Spring 提供了以下衍生注解:1.@Controller:示意创立控制器对象 @Controller
public class UserController {
}
复制代码 2.@Service:示意创立业务层对象 @Service
public class UserServiceImpl implements UserService {
}
复制代码 3.@Repository:示意创立长久层对象 @Repository
public class UserDaoImpl implements UserDao {
}
复制代码这三个注解的作用和 @Component 的作用一样,都是用来创建对象。10.1.3 @Scope 咱们晓得 Spring 工厂创立的对象默认都是单例的,也就是 bean 标签中 scope 属性默认是 singleton。@Scope 注解能够用来批改创建对象的 scope 属性。默认:也就是说你不写 @Scope 注解,默认就是 singleton,所以能够省略。@Component
// 能够省略不写
@Scope(“singleton”)
public class User {
}
复制代码批改多例:@Component
@Scope(“prototype”)
public class User {
}
复制代码 10.1.4 生命周期相干注解 1.@PostConstruct 初始化办法注解,作用在办法上。用来替换 bean 标签的 init-method 属性。例如:@Component
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 + '\'' +
'}';
}
// 初始化办法
@PostConstruct
public void init(){this.name = "王小波";}
}
复制代码测试:@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
User user = (User) act.getBean("user");
System.out.println(user);
}
复制代码
报错了!这是为什么?咱们先看报错内容:No bean named ‘user’
复制代码也就是说找不到这个对象。咱们尽管加了 @Component 注解,然而 Spring 不晓得啊,所以须要在 Spring 的配置文件中配置注解扫描:<context:component-scan base-package=”com.xxl”/>
复制代码
base-package: 增加注解的类所在的包地位。配置了注解扫描,当程序启动的时候 Spring 会先扫描一下相干的注解,这些注解才会失效。就比方你去快递驿站拿快递,你看到了本人的快递而后对快递小哥说:” 这我的快递我拿走了啊。” 然而人家小哥无奈确认是你的啊!所以他须要用扫码枪扫一下能力出货!咱们配置注解扫描之后再次测试:
2.@PreDestory(理解即可)销毁办法注解,作用在办法上。用来替换 bean 标签的 destory-method 属性。10.2 注入相干注解 10.2.1 @Autowired 咱们之前学 DI 的时候晓得:注入就是赋值。@Autowired 次要是为自定义的类型赋值,例如 service、dao 层的各种类。@Controller
public class UserController {
@Autowired
private UserService userService;
}
复制代码 @Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
}
复制代码 @Autowired 是基于类型进行注入,所注入对象的类型必须和指标 变量类型雷同或者是他的子类、实现类。如果想基于名字注入,能够和 @Qualifier 注解连用:@Autowired
@Qualifier(“orderDAOImpl”)
private OrderDAO orderDAO;
复制代码 10.2.2 @Resource@Resource 注解是 JAVAEE 标准中提供的注解,他和 @Autowired 注解的作用一样,然而他是基于名字进行注入:@Resource(“orderDAOImpl”)
private OrderDAO orderDAO;
复制代码在理论开发中,用 @Autowired 注解比拟多一点。10.2.3 案例 Product 类 @Component
public class Product {
private String productName;
public String getProductName() {return productName;}
public void setProductName(String productName) {this.productName = productName;}
@Override
public String toString() {
return "Product{" +
"productName='" + productName + '\'' +
'}';
}
@PostConstruct
public void init(){this.productName = "西瓜";}
}
复制代码 User 类:@Component
public class User {
private String name;
@Autowired
private Product product;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Product getProduct() {return product;}
public void setProduct(Product product) {this.product = product;}
@PostConstruct
public void init(){this.name = "小明";}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", product=" + product +
'}';
}
}
复制代码测试:@Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new ClassPathXmlApplicationContext("/applicationContext.xml");
// 2、通过工厂类取得对象
User user = (User) act.getBean("user");
System.out.println(user);
}
复制代码
10.3 Spring 配置文件相干注解 10.3.1 @Configuration@Configuration 注解用于替换 xml 配置文件。@Configuration
public class SpringConfig {
}
复制代码意思就是说你在一个类下面加一个 @Configuration 注解,这个类就能够看成 Spring 的配置类,你就不必再写 xml 文件了。
咱们之前是依据 xml 文件创建 Spring 的工厂,那怎么依据配置类创立工厂呢?有两种形式:形式一:依据类.classApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
复制代码形式二:依据配置类所在的门路 ApplicationContext ctx = new AnnotationConfigApplicationContext(“com.xxl”);
复制代码 10.3.2 @Bean@Bean 注解也是用来创建对象的,相当于 spring 配置文件中的 bean 标签。@Configuration
public class SpringConfig {
@Bean
public Product getProduct(){return new Product();
}
}
复制代码
自定义 id 值:@Configuration
public class SpringConfig {
@Bean("product")
public Product getProduct(){return new Product();
}
}
复制代码不过在理论开发中咱们个别会用 @Bean 注解创立一些简单的对象,例如 Redis、MQ 等一些组件对象。@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
复制代码 10.3.3 @ComponentScan@ComponentScan 注解相当于 xml 配置文件中的注解扫描标签:<context:component-scan base-package=”com.xxl”/>
复制代码作用:用来扫描 @Component 等相干注解属性:basePackages:注解所在的包门路例如:@Configuration
@ComponentScan(basePackages = “com.xxl”)
public class SpringConfig {
}
复制代码 11. 注解小案例 1.User 类 @Component
public class User {
private String name;
@Autowired
private Product product;
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Product getProduct() {return product;}
public void setProduct(Product product) {this.product = product;}
@PostConstruct
public void init(){this.name = "渣渣辉";}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", product=" + product +
'}';
}
}
复制代码 2.Product 类 @Component
public class Product {
private String productName;
public String getProductName() {return productName;}
public void setProductName(String productName) {this.productName = productName;}
@Override
public String toString() {
return "Product{" +
"productName='" + productName + '\'' +
'}';
}
}
复制代码 3. 配置类:@Configuration
@ComponentScan(basePackages = “com.xxl”)
public class SpringConfig {
@Bean
public Product product() {Product product = new Product();
product.setProductName("草莓味的番茄");
return product;
}
}
复制代码 4. 测试 @Test
public void testSpring() {
// 1、获取工厂
ApplicationContext act = new AnnotationConfigApplicationContext(SpringConfig.class);
// 2、通过工厂类取得对象
User user = (User) act.getBean("user");
System.out.println(user);
}
复制代码