IOC:
Spring简介
- spring是分层的(一站式)轻量级开源框架,他以IoC(Inversion of Control,控制反转),和AOP(Aspect Oriented Programming,面向切面编程)为内核。
- Spring是一个企业级开发框架,是软件设计层面的框架,优势在于可以将应用程序进行分层,开发者可以自主选择组件。
- Spring框架的优点
- 非侵入式设计:Spring是一种非侵入式(non-invasive)框架,它可以使应用程序代码对框架的依赖最小化。
- 方便解耦、简化开发:Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护工作都交给Spring容器的管理,大大的降低了组件之间的耦合性。
- 支持AOP:Spring提供了对AOP的支持,它允许将一些通用任务,如安全、事物、日志等进行集中式处理,从而提高了程序的复用性。
- 支持声明式事务处理:只需要通过配置就可以完成对事物的管理,而无须手动编程。
- 方便程序的测试:Spring提供了对Junit4的支持,可以通过注解方便的测试Spring程序。
- 方便集成各种优秀框架:Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如Struts、Hibernate、MyBatis、Quartz等)的直接支持。
- 降低Jave EE API的使用难度:Spring对Java EE开发中非常难用的一些API(如JDBC、JavaMail等),都提供了封装,使这些API应用难度大大降低。
第一个Spring
- 新建Maven工程添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
- 创建实体类Student
package pojo;
public class Student {
private long id;
private String name;
private int age;
...请补充get、set
}
- 通过IOC创建对象,在配置文件中添加需要管理的对象,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="student" class="pojo.Student">
<property name="id" value="1"></property>
<property name="name" value="wcy"></property>
<property name="age" value="21"></property>
</bean>
</beans>
1、通过bean
标签来完成对象的管理
id
:对象名class
对象的模板类。(所有交给IoC容器来管理的类必须有无参构造函数,因为Spring底层是通过反射机制来创建对象,调用的是无参构造)
2、成员变量通过property
标签完成赋值
-
name
:成员变量名 -
value
:成员变量(基本数据类型,String等可以直接赋值,如果是其他引用类型,不能通过value赋值) -
ref
:将IoC中的另外一个Bean赋给当前的成员变量(DI)
- 通过IoC容器创建对象
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;
public class Test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.XML");
//通过运行时类获取
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
}
}
Spring的两大核心容器
ApplicationContext是BenaFactor的一个子接口,也被称为应用上下文,是另一种常用的Spring核心容器。它不仅包含了BeanFactor的所有功能,还添加了对国际化,资源访问,事件传播等方面的支持。建议使用该接口。
- BeanFactory(IoC容器)
使用BeanFactory加载spring.xml
XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new FileSystemResource("D:\\files\\idea\\JAVA\\spring_2021.3.22\\src\\main\\resources\\spring.xml"));
Student student =(Student) xmlBeanFactory.getBean("student");
System.out.println(student);
通过绝对路径拿到spring.xml配置文件
- ApplicationContext(IoC容器)
通过FileSystemXmlApplicationContext创建:通过绝对路径
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("D:\\files\\idea\\JAVA\\spring_2021.3.22\\src\\main\\resources\\spring.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
通过ClassPathXmlApplicationContext创建:通过类路径
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Student student =(Student)applicationContext.getBean("student");
System.out.println(student);
- 从IoC中获取对象
通过配置文件已经创建了IoC容器,下面通过两种方式从IoC容器中获取对象
通过Bean的id属性获取Bean
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;
public class Test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.XML");
//通过id获取
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
}
}
通过运行时类获取Bean
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;
public class Test {
public static void main(String[] args) {
//加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.XML");
//通过运行时类获取
Student student = (Student) applicationContext.getBean(Student.class);
System.out.println(student);
}
}
但是这种方式有一种弊端,配置文件中只能有一个实例。否则会抛出异常,因为没有唯一的bean
IOC/DI(依赖注入)
Spring 最认同的技术是控制反转的依赖注入(DI)模式。控制反转(IoC)是一个通用的概念,它可以用许多不同的方式去表达,依赖注入仅仅是控制反转的一个具体的例子。
- 构造注入
通过有参构造方法
<!-- 构造方式注入的三种方式 -->
<!--下标-->
<bean id="student2" class="www.sheep.pojo.Student2">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="c罗"/>
<constructor-arg index="2" value="36"/>
</bean>
<!--名字-->
<bean id="student3" class="www.sheep.pojo.Student2">
<constructor-arg name="id" value="2"/>
<constructor-arg name="name" value="内马尔"/>
<constructor-arg name="age" value="29"/>
</bean>
<!--数据类型-->
<bean id="student4" class="www.sheep.pojo.Student2">
<constructor-arg type="int" value="3"/>
<constructor-arg type="java.lang.String" value="林语堂"/>
<constructor-arg type="java.lang.String" value="100"/>
</bean>
通过有参构造注入(pojo、String[]、List、Map)
private wangchaoyu wangchaoyu;
private String[] strings;
private List<wangchaoyu> list;
private Map<String,wangchaoyu> map;
<!-- 引用类型 -->
<constructor-arg name="wangchaoyu" ref="wangchaoyu"/>
<!-- 数组类型 -->
<constructor-arg name="strings">
<array>
<value>三国演义</value>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</constructor-arg>
<!-- List集合 -->
<constructor-arg name="list">
<list>
<ref bean="wangchaoyu2"/>
</list>
</constructor-arg>
<!-- Map集合 -->
<constructor-arg name="map">
<map>
<entry key="功夫" value-ref="wangchaoyu3"/>
</map>
</constructor-arg>
- Set注入
通过Set方法(通过Set方法注入要写无参构造)
基本数据类型注入
<bean id="student2" class="www.sheep.pojo.Student">
<property name="id" value="1"/>
<property name="name" value="我们梦想奔赴大海"/>
<property name="age" value="21"/>
</bean>
复杂类型注入
private wangchaoyu wangchaoyu;
private String[] strings;
private List<wangchaoyu> list;
private Map<String,wangchaoyu> map;
<bean id="student2" class="www.sheep.pojo.Student">
<!-- javaBean -->
<property name="wangchaoyu" ref="wangchaoyu"/>
<!-- String[] -->
<property name="strings">
<array>
<value>红楼梦</value>
<value>西游记</value>
<value>三国演义</value>
<value>水浒传</value>
</array>
</property>
<!-- List -->
<property name="list">
<list>
<ref bean="wangchaoyu2"/>
</list>
</property>
<!-- Map -->
<property name="map">
<map>
<entry key="hello" value-ref="wangchaoyu3"/>
</map>
</property>
</bean>
- p命名空间注入、c名门空间注入
导入C命名空间依赖、实体类中必须存在有参构造器。属于:<property/>
xmlns:c="http://www.springframework.org/schema/c"
<!-- C命名空间注入 -->
<bean id="wangchaoyu4" class="www.sheep.pojo.wangchaoyu" c:id="1" c:name="梅西" c:age="36"/>
实体类中必须有set方法、实体类中必须有无参构造器(默认存在)。属于:<constructor-arg/>
xmlns:p="http://www.springframework.org/schema/p"
<!-- p命名空间注入 -->
<bean id="wangchaoyu5" class="www.sheep.pojo.wangchaoyu" p:id="2" p:name="c罗" c:age="34"/>
Spring中的Bean
- Bean的实例化三种方式
- 构造器实例化:构造器实例化是指IoC容器通过Bean对应类中默认的无参构造方法来实例化Bean。
- 静态工厂实例化
创建一个实体类
package www.sheep.pojo;
public class Car {
private long id;
private String name;
...请补充get、set
}
创建一个静态工厂类
public class StaticCarFactory {
private static Map<Long, Car> carMap;
static {
carMap = new HashMap<Long,Car>();
carMap.put(1L,new Car(1L,"宝马"));
carMap.put(2L,new Car(2L,"奔驰"));
carMap.put(3L,new Car(3L,"奥迪"));
}
public static Car getCar(long id){
return carMap.get(id);
}
}
在spring.xml中配置静态工厂,通过factory-method:调用静态工厂的方法创建对象
<?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">
<!-- 配置静态工厂创建Car -->
<bean id="car" class="www.sheep.factory.StaticCarFactory" factory-method="getCar">
<constructor-arg value="2"/>
</bean>
</beans>
创建Handler类
public class Test3 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-staticfactory.xml");
Car car =(Car) applicationContext.getBean("car");
System.out.println(car);
}
}
- 实例工厂实例化
创建实体类
package www.sheep.pojo;
public class Car {
private long id;
private String name;
...请补充get、set
}
创建实例工厂
public class InstanceCarFactory {
private Map<Long, Car> carMap;
public InstanceCarFactory(){
carMap = new HashMap<Long,Car>();
carMap.put(1L,new Car(1L,"宝马"));
carMap.put(2L,new Car(2L,"奔驰"));
carMap.put(3L,new Car(3L,"奥迪"));
}
public Car getCar(long id){
return carMap.get(id);
}
}
在spring.xml中配置调用动态工厂的方法创建对象,先创建InstanceCarFactory工厂,在通过InstanceCarFactory调用getCar方法创建对象。
<?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 -->
<bean id="carFactory" class="www.sheep.factory.InstanceCarFactory"/>
<!-- 赔偿实例工厂创建Car -->
<bean id="car2" factory-bean="carFactory" factory-method="getCar">
<constructor-arg value="1"/>
</bean>
</beans>
创建Handler类
public class Test3 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-staticfactory.xml");
Car car =(Car) applicationContext.getBean("car2");
System.out.println(car);
}
}
- Bean的作用域
Spring管理的bean是根据scope来生成的,表示bean的作用域,共有7种。(默认单例模式)
- singleton:单例,表示通过IoC容器获取的bean的唯一的。
- prototype:原型,表示通过IoC容器获取的bean是不同的。
- request:请求,表示一次HTTP请求内生效。
- session:会话,表示一个用户会话内生效。
- globalSession:在一个全局的HTTPSession中,容器会返回该Bean的同一个实例。仅在使用portlet上下文生效。
- application:为每个ServletContext对象创建一个实例,仅在Web相关的ApplicationContext中生效。
- websocket:为每个websocket对象创建一个实例。仅在Web相关的ApplicationContext中生效。
<!-- 单例模式 -->
<!-- singleton模式无论业务代码是否获取IoC容器中的bean时,Spring在加载spring.xml时就会创建bean -->
<bean id="student" class="www.sheep.pojo.Student" scope="singleton">
<!-- 原型模式 -->
<!-- prototype模式当业务代码获取IoC容器中的bean时,Spring才去调用无参构造创建对应的bean -->
<bean id="student" class="www.sheep.pojo.Student" scope="prototype">
- Spring的继承
Spring的继承关注点在于具体对象,而不在于类,即不同的两个类的实例化对象可以完成继承,IoC容器会自动将父bean的数据类型和属性名与子类的数据类型与属性名进行匹配,同时可以在此基础上添加其他属性, 子 Bean 也可以覆盖从父 Bean 继承过来的配置。
<!-- twoectends继承oneectends -->
<bean id="oneextends" class="www.sheep.pojo.oneExtends"></bean>
<bean id="twoectends" class="www.sheep.pojo.twoExtends" parent="oneextends"></bean>
- Spring的依赖
与继承类似,依赖也是描述bean和bean之间的一种关系,配置依赖之后,被依赖的bean一定先创建,再创建依赖的bean,A依赖于B,先创建B,再创建A。
<!-- twoectends依赖于oneectends -->
<bean id="oneextends" class="www.sheep.pojo.oneExtends">
<bean id="twoectends" class="www.sheep.pojo.twoExtends" depends-on="twoectends">
- Bean的装载三种方式
Bean的装配可以理解为依赖关系注入,Bean的装配方式即Bean依赖注入的方式。
- XML装载:IoC容器通过了通过XML方式装配的两种方式:Set、构造。
- 注解(Annotation)的装载
spring.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<!--在使用注解功能之前要告诉IoC现在需要启用注解相关的
功能,通过上下文级别的配置即可开启所有注解相关的功能-->
</beans>
三个实体类
public class Tools {
public void minTool(){
System.out.println("我是小扳手。。。。。");
}
}
public class Utils {
private int id;
private String name;
private String type;
}
/**
* @Autowired注解:当bean中的id名与变量名相同时自动装载,当变量名不同时通过数据类型装载。
* @Autowired
* @Qualifier(value = "utils"):当有多个相同类型的bena时可以使用该注解指定与id对应的名称。
* */
public class Mankind {
private String name;
private String age;
@Autowired
private Tools tools;
@Autowired
@Qualifier(value = "utils")
private Utils utils;
}
在spring.xml中添加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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<!-- Tools -->
<bean id="tools" class="www.sheep.pojo.Tools"/>
<!-- Utils -->
<bean id="utils" class="www.sheep.pojo.Utils">
<property name="id" value="1"/>
<property name="name" value="鼠标"/>
<property name="type" value="学习"/>
</bean>
<!-- Utils -->
<bean id="utils2" class="www.sheep.pojo.Utils">
<property name="id" value="1"/>
<property name="name" value="鼠标"/>
<property name="type" value="学习.com"/>
</bean>
<!-- Mankind -->
<bean id="mankind" class="www.sheep.pojo.Mankind">
<property name="name" value="梅西"/>
<property name="age" value="21"/>
<!-- 没有通过ref的方式,而是通过注解的方式进行装载 -->
</bean>
</beans>
Handler
public class Test5 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-annotation.xml");
Mankind mankind = (Mankind) applicationContext.getBean("mankind");
mankind.getTools().minTool();
System.out.println(mankind);
}
}
- 自动装载(Autowrie)
IoC负责创建对象,DI负责完成对象注入,通过配置property标签的ref属性完成,同时Spring提供了另外一个更加简便的依赖注入方式:自动装载,不需要手动配置property,IoC容器会自动选择bean完成注入。
自动装载有两种方式:
byName:通过属性名自动装载
创建实体类
public class Person {
private long id;
private String name;
private Car car;
}
public class Car {
private long id;
private String name;
}
spring.xml
<bean id="car" class="www.sheep.pojo.Car">
<property name="id" value="1"/>
<property name="name" value="宝马"/>
</bean>
<bean id="person" class="www.sheep.pojo.Person" autowire="byName">
<property name="id" value="1"/>
<property name="name" value="编程"/>
</bean>
Handler
public class Test4 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
Person person = (Person) applicationContext.getBean("person");
System.out.println(person);
}
}
byType:通过属性的数据类型自动装载
spring.xml
<!-- 当bean的id不同时根据数据类型装载 -->
<bean id="car123" class="www.sheep.pojo.Car">
<property name="id" value="1"/>
<property name="name" value="宝马"/>
</bean>
<bean id="person" class="www.sheep.pojo.Person" autowire="byType">
<property name="id" value="1"/>
<property name="name" value="编程"/>
</bean>
- Bean的装载总结:
byName:使用时根据Bean的id自动装载可以有多个Bean。
byType:使用byType时只能由一个与之对应的Bean如果有多个会抛出异常。
注解装载和自动装载:都只能对引用数据类型装载(ref)。
注解开发
springIoC中的注解
- @Component:表示将该类描述为Bean,可以作用在任何层。
- @Repository:与@Component注解一样将该类描述为Bean,作用于DAO层。
- @Service:与@Component注解一样将该类描述为Bean,作用在(service)控制层。
- @Controller:与@Component注解一样将该类描述为Bean,作用在控制层,比如SpringMVC的Controller。
- @Autowired:用于对Bean的属性、set、构造方法进行标注,配置其他注解 完成Bean的注入,默认按类型注入。
- @Resource:与@Autowirsd一样,不同在于@Resource默认按照名称注入,但@Resource有两个属性name、type。分别是按照名字、类型注入
- @Qualifier:与@Autowired注解配置使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,实例名称由@Qualifier注解的参数指定。
使用Java的方式配置Spring
现在完全不使用Spring的.xml配置,全权交给Java来做!
JavaConfig是Spring的一个子项目,在Spring4之后,它成为了一个核心功能!
使用Java方式配置SPring的简单使用
- 创建实体类
package com.sheep.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//通过@Component注解将该类交给IoC容器接管,注册到容器中
@Component
public class User {
private String name;
public String getName() {
return name;
}
public User(){
System.out.println("Program Sheep!!!");
}
@Value("Program Sheep") //属性注入值
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
- 创建管理类
package com.sheep.config;
import com.sheep.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
//这个也会被IoC容器接管,注册到容器中,应为他本来就是一个@Component。
//@Configuration 代表一个配置类,就相当于bean.xml,扫描com.sheep.pojo包下的所有bean
@Configuration
@ComponentScan("com.sheep.pojo")
public class SheepConfig {
@Bean
public User getUser(){
return new User();//就是返回要注入到bean的对象
}
//注册一个bean,就相当于我之前写的一个bean标签
//这个方法的名字,就相当于bean标签中的id属性
//这个方法的返回值,就相当于bean标签中的class属性
}
- 创建测试类
import com.sheep.config.SheepConfig;
import com.sheep.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MySheepConfig {
public static void main(String[] args) {
//如果完全使用了配置类方式去做,就只能通过AnnotationConfigApplicationContext上下文来获取容器,通过配 置运行时类加载
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SheepConfig.class);
User user = (User) applicationContext.getBean("getUser");
System.out.println(user);
}
}
全程并没有使用spring.xml方式配置Bean而是使用纯Java代码配置实现的。
AOP:
AOP简介
- 什么是AOP
- AOP全称Aspect-Orientend Programming,面向切面编程。
- AOP是对面向对象编程的一种补充,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面编程。将不同方法的同一个位置抽象成一个切面对象,对该切面对象进行编程就是AOP。
- 使用AOP,开发人员在编写业务逻辑时可以专心于核心业务,不会再过多的关注于其他业务逻辑的实现。
- 目前流行的AOP框架有两个,SpringAOP、AspectJ。
- AOP的优点
- 降低模块之间的耦合度
- 使系统容易扩展
- 更好的代码复用
- 非业务代码更加集中,不分散,便于统一管理
- 业务代码更加纯粹,没有其他代码的影响
- AOP术语
- Aspect(切面):横切关注点被模块化的抽象对象。
- Advice(通知):切面对象完成的工作
- Target Object(目标对象):被通知的对象,即被横切的对象
- Proxy(代理):切面、通知、目标混合之后的对象
- Joinpoint(连接点):通知要插入业务代码的具体位置
- Pointcut(切入点):AOP通过切点定位到连接点。
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程。
JDK动态代理AOP
- 创建Maven工程,pom.xml添加
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.5</version>
</dependency>
</dependencies>
- 创建一个计数器接口Cal,定义4个方法。
package com.sheep.utils;
public interface Cal {
public int add(int num1,int num2);
public int sud(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
- 创建接口的实现类Callmpl
package com.sheep.utils.impl;
import com.sheep.utils.Cal;
public class CalImpl implements Cal {
public int add(int num1, int num2) {
System.out.println("add方法的参数是["+num1+","+num2+"]");
int result = num1+num2;
System.out.println("add方法的结果是"+result);
return result;
}
public int sub(int num1, int num2) {
System.out.println("sud方法的参数是["+num1+","+num2+"]");
int result = num1-num2;
System.out.println("sub方法的结果是"+result);
return result;
}
public int mul(int num1, int num2) {
System.out.println("mul方法的参数是["+num1+","+num2+"]");
int result = num1*num2;
System.out.println("mul方法的结果是"+result);
return result;
}
public int div(int num1, int num2) {
System.out.println("div方法的参数是["+num1+","+num2+"]");
int result = num1/num2;
System.out.println("div方法的结果是"+result);
return result;
}
}
- 测试
package com.sheep.test;
import com.sheep.utils.Cal;
import com.sheep.utils.MyInvocationHandler;
import com.sheep.utils.impl.CalImpl;
public class Test1 {
public static void main(String[] args) {
Cal cal = new CalImpl();
cal.add(1,1);
cal.sub(4,2);
cal.mul(2,3);
cal.div(6,2);
}
}
上述代码中,日志信息和业务逻辑的耦合性很高,不利于系统的维护,使用AOP可以进行优化,如何来实现AOP?使用动态代理的方式来实现。
- 使用动态代理
创建代理类
package com.sheep.utils;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
public class MyInvocationHandler implements InvocationHandler {
//接收委托对象
private Object object = null;
//返回代理对象
public Object bind(Object object){
this.object = object;
return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法的参数是"+ Arrays.toString(args));
Object result = method.invoke(this.object, args);
System.out.println(method.getName()+"结果是"+result);
return result;
}
}
- 测试
public class Test1 {
public static void main(String[] args) {
//原始
// Cal cal = new CalImpl();
// cal.add(1,1);
// cal.sub(4,2);
// cal.mul(2,3);
// cal.div(6,2);
//代理对象开发
Cal cal = new CalImpl(); //创建委托对象
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();//创建代理对象
Cal proxy = (Cal) myInvocationHandler.bind(cal);//将委托对象交给代理对象实现
proxy.add(1,1);
proxy.sub(2,1);
proxy.mul(2,3);
proxy.div(6,2);
}
}
以上是通过动态代理实现AOP的过程,比较复杂,不好理解,Spring框架对AOP进行了封装,使用Spring框架可以用面向对象的思想来实现AOP。
控制台
add方法的参数是[1, 1]
add结果是2
sub方法的参数是[2, 1]
sub结果是1
mul方法的参数是[2, 3]
mul结果是6
div方法的参数是[6, 2]
div结果是3
Spring框架代理类的AOP实现三种方式
AOP是实现方式一
使用Spring的API接口(获取参数)
- 添加依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
- 定义一个接口
package com.sheep.service;
public interface UserService {
public void add();
public void delete();
public void update();
public void select();
}
- 定义接口实现类
package com.sheep.service;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加一个用户");
}
@Override
public void delete() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("更新一个用户");
}
@Override
public void select() {
System.out.println("查询一个用户");
}
}
- 配置前置类
package com.sheep.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"被执行了");
}
}
- 配置后置类
package com.sheep.log;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了"+method.getName()+"方法。返回结果为:"+o);
}
}
- spring.xml中配置AOP约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
- 在spring.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 注册Bean -->
<bean id="userService" class="com.sheep.service.UserServiceImpl"/>
<bean id="log" class="com.sheep.log.Log"/>
<bean id="afterLog" class="com.sheep.log.AfterLog"/>
<!-- 方式一使用原生的SpringAPI接口 -->
<!-- 配置AOP: 需要导入aop的约束-->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增强 -->
<!-- 通知 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
- 测试类
import com.sheep.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test01 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
//动态代理的必须是接口
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
userService.delete();
userService.update();
userService.select();
}
}
控制台
com.sheep.service.UserServiceImpl的add被执行了
增加一个用户
执行了add方法。返回结果为:null
com.sheep.service.UserServiceImpl的delete被执行了
删除一个用户
执行了delete方法。返回结果为:null
com.sheep.service.UserServiceImpl的update被执行了
更新一个用户
执行了update方法。返回结果为:null
com.sheep.service.UserServiceImpl的select被执行了
查询一个用户
执行了select方法。返回结果为:null
在方式一中前置通知与后置通知被抽象为一个类,并在xml中配置该类。
AOP是实现方式二
自定义类来实现
- 将方法一的前后置类变成一个类前后置变成两个方法,并且不要AfterReturningAdvice接口
package com.sheep.diy;
public class DiyPointCut {
public void before(){
System.out.println("------方法执行前------");
}
public void after(){
System.out.println("------方法执行后------");
}
}
- 在spring.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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="userService" class="com.sheep.service.UserServiceImpl"/>
<!--===================================================================================-->
<!-- 方式一使用原生的SpringAPI接口 -->
<!-- 注册Bean -->
<bean id="log" class="com.sheep.log.Log"/>
<bean id="afterLog" class="com.sheep.log.AfterLog"/>
<!-- 配置AOP: 需要导入aop的约束-->
<aop:config>
<!-- 切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>
<!-- 执行环绕增强 -->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
<!--===================================================================================-->
<!-- 方式二:自定义类 -->
<bean id="diy" class="com.sheep.diy.DiyPointCut"/>
<aop:config>
<!-- 自定义切面 -->
<aop:aspect ref="diy">
<!-- 切入点 -->
<aop:pointcut id="point" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>
<!-- 通知点 -->
<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="point"/>
<!-- 后置通知 -->
<aop:after method="after" pointcut-ref="point"/>
</aop:aspect>
</aop:config>
<!--===================================================================================-->
</beans>
- 测试类
import com.sheep.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test01 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
//动态代理的必须是接口
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
userService.delete();
userService.update();
userService.select();
}
}
控制台
------方法执行前------
增加一个用户
------方法执行后------
------方法执行前------
删除一个用户
------方法执行后------
------方法执行前------
更新一个用户
------方法执行后------
------方法执行前------
查询一个用户
------方法执行后------
AspectJ 注解实现AOP
案例一:
- 自动生成动态
<!-- 让Spring容器结合切面类和目标对象自动生成动态代理对象-->
<aop:aspectj-autoproxy/>
- 新建切面类使用注解配置(延续AOP三种实现方式的类)
package com.sheep.diy;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect //表示这个类是一个切面
@Component //表示将该类交给IoC容器创建(注意在spring.xml中要配置扫描该该类)
public class Annotation {
//@Before:表示前置通知的切入点
@Before("execution(* com.sheep.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("-=-=-=方法执行前-=-=-=");
}
// @After:表示后置通知的切入点
@After("execution(* com.sheep.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("-=-=-=方法执行后-=-=-=");
}
//@Around表示环绕通知切入点
@Around("execution(* com.sheep.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pj) throws Throwable {
System.out.println("环绕前");
Object proceed = pj.proceed(); //执行前后置方法
System.out.println("环绕后");
}
}
- 在spring.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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 注册Bean -->
<bean id="userService" class="com.sheep.service.UserServiceImpl"/>
<bean id="log" class="com.sheep.log.Log"/>
<bean id="afterLog" class="com.sheep.log.AfterLog"/>
<!-- <!– 方式一使用原生的SpringAPI接口 –>-->
<!-- <!– 配置AOP: 需要导入aop的约束–>-->
<!-- <aop:config>-->
<!-- <!– 切入点 –>-->
<!-- <aop:pointcut id="pointcut" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>-->
<!-- <!– 执行环绕增强 –>-->
<!-- <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>-->
<!-- <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>-->
<!-- </aop:config>-->
<!-- <!– 方式二:自定义类 –>-->
<!-- <bean id="diy" class="com.sheep.diy.DiyPointCut"/>-->
<!-- <aop:config>-->
<!-- <!– 自定义切面 –>-->
<!-- <aop:aspect ref="diy">-->
<!-- <!– 切入点 –>-->
<!-- <aop:pointcut id="point" expression="execution(* com.sheep.service.UserServiceImpl.*(..))"/>-->
<!-- <!– 通知点 –>-->
<!-- <!– 前置通知 –>-->
<!-- <aop:before method="before" pointcut-ref="point"/>-->
<!-- <!– 后置通知 –>-->
<!-- <aop:after method="after" pointcut-ref="point"/>-->
<!-- </aop:aspect>-->
<!-- </aop:config>-->
<!-- 方式三 -->
<!-- 将com.sheep.diy包下的所有类交给IOC容器创建 -->
<context:component-scan base-package="com.sheep.diy*"/>
<!-- <bean class="com.sheep.diy.Annotation"/>-->
<!-- 让Spring容器结合切面类和目标对象自动生成动态代理对象-->
<aop:aspectj-autoproxy/>
</beans>
- 测试类
import com.sheep.log.Log;
import com.sheep.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test01 {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
//动态代理的必须是接口
UserService userService = (UserService) applicationContext.getBean("userService");
userService.add();
userService.delete();
userService.update();
userService.select();
}
}
控制台
环绕前
-=-=-=方法执行前-=-=-=
增加一个用户
-=-=-=方法执行后-=-=-=
环绕后
环绕前
-=-=-=方法执行前-=-=-=
删除一个用户
-=-=-=方法执行后-=-=-=
环绕后
可以看出该环绕在方法的最前和最后
案例二:(获取参数)
- 创建接口
package com.sheep.utils;
public interface Cal {
public int add(int num1,int num2);
public int sub(int num1,int num2);
public int mul(int num1,int num2);
public int div(int num1,int num2);
}
- 创建接口实习类(交给IoC容器)
package com.sheep.utils.impl;
import com.sheep.utils.Cal;
import org.springframework.stereotype.Component;
@Component //将该类交给IoC容器
public class CalImpl implements Cal {
public int add(int num1, int num2) {
//System.out.println("add方法的参数是["+num1+","+num2+"]");
int result = num1+num2;
//System.out.println("add方法的结果是"+result);
return result;
}
public int sub(int num1, int num2) {
int result = num1-num2;
return result;
}
public int mul(int num1, int num2) {
int result = num1*num2;
return result;
}
public int div(int num1, int num2) {
int result = num1/num2;
return result;
}
}
- 使用注解创建切面类
package com.sheep.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect //声明该类为切面类
@Component //将来类交给IoC容器
public class LoggerAspect {
//前置通知
@Before("execution(public int com.sheep.utils.impl.CalImpl.*(..))")
public void before(JoinPoint joinPoint){
//获取方法名
String name = joinPoint.getSignature().getName();
//获取参数
String args = Arrays.toString(joinPoint.getArgs());
System.out.println(name+"方法的参数是"+args);
}
//后置通知
@After("execution(* com.sheep.utils.impl.CalImpl.*(..)))")
public void after(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
System.out.println(name+"执行完毕");
}
//返回值通知
@AfterReturning(value = "execution(* com.sheep.utils.impl.CalImpl.*(..)))",returning = "result")
public void afterReturning(JoinPoint joinPoint,Object result){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法的结果是"+result);
}
//异常通知
@AfterThrowing(value = "execution(* com.sheep.utils.impl.CalImpl.*(..)))",throwing = "exception")
public void fterThrowing(JoinPoint joinPoint,Exception exception){
String name = joinPoint.getSignature().getName();
System.out.println(name+"方法抛出了"+exception+"异常");
}
}
- 在spring.xml中配置注解扫描、和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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 自动扫描 -->
<context:component-scan base-package="com.sheep.aop*"/>
<context:component-scan base-package="com.sheep.utils*"/>
<!-- 让Spring容器结合切面类和目标对象自动生成动态代理对象-->
<aop:aspectj-autoproxy/>
</beans>
控制台
add方法的参数是[1, 1]
add方法的结果是2
add执行完毕
sub方法的参数是[2, 1]
sub方法的结果是1
sub执行完毕
- 总结:注解开发中常用的注解、通知的执行先后顺序
@Aspect :表示这个类是一个切面
@Component :表示将该类交给IoC容器创建(注意在spring.xml中要配置扫描该该类)
@Before:表示前置通知
@After:表示后置通知
@AfterReturning:表示返回值通知
@AfterThrowing:异常通知
@Around:表示环绕通知
前环绕通知 >> 前置通知 >> 返回值通知 >> 后置通知 >> 后环绕通知。
还历史以真诚,还生命以过程。 ——余秋雨