Spring
目录
1. 简介
Spring: 春天 给软件行业带来了春天
Spring理念:使现有的技术更加容易使用,本身是一个大杂烩,整合了现有的技术框架
- Spring是一个开源的免费的框架(容器)!
- Spring是一个轻量级的、非入侵式的框架!
- 控制反转(IOC),面向切面编程(AOP)!
- 支持事务的处理,对框架整合的支持!
总结一句话:Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!
Spring 最核心的思想就是不重新造轮子,开箱即用,提高开发效率。
Spring 官网:https://spring.io/open in new window
Github 地址: https://github.com/spring-projects/spring-framework
Spring:功能介绍
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是
BeanFactory
,它是工厂模式的实现。BeanFactory
使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。 - Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- 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:控制反转) 是一种设计思想,而不是一个具体的技术实现,DI(依赖注入)是实现IoC的一种方法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。
control what? 对象创建(实例化、管理)的权力
inverse to what? 控制权交给外部环境(Spring 框架、IoC 容器)
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出来。 IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。
在实际项目中一个 Service 类可能依赖了很多其他的类,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
在 Spring 中, IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
Spring 时代我们一般通过 XML 文件来配置 Bean,后来开发人员觉得 XML 文件来配置不太好,于是 SpringBoot 注解配置就慢慢开始流行起来。
总结:面向对象编程,导致项目的耦合性太强,这样改动会造成很大的困难,而IOC容器把控制权交给了Spring,这样就让项目的耦合性大大下降。
2.2 DI与Bean的装配
DI依赖注入,本质就是装配bean,有三种方式注入,构造器注入,set注入,p,c命名空间注入,比较简单
而bean的装配有三种方式进行,
- xml显式配置(很普通)
- 隐式的Bean发现机制和自动装配:xml 和 java注解
- java显式配置 重要
2.2.1 DI 构造器注入xml
首先默认走无参构造器,所以程序里最好有无参构造,否则会报错
接着如果有constructor-arg标签会智能匹配有参构造器,这时候有三种写法
pojo:
package com.cao.pojo;
public class User {
private String name;
private int id;
public User() {
System.out.println("1");
}
public User(String name) {
System.out.println("2");
this.name = name;
}
public User(int id){
System.out.println("3");
this.id=id;
}
public User(String name, int id) {
System.out.println("4");
this.name = name;
this.id=id;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public void setId(int id) {
System.out.println("setID");
this.id = id;
}
public String getName() {
return name;
}
public int getId() {
return id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
}
<bean id="user" class="com.cao.pojo.User">
<!--通过下标来完成构造器注入-->
<constructor-arg index="0" value="cao"/>
<constructor-arg index="1" value="1"/>
<!--通过名字来完成构造器注入(推荐)-->
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="cao"/>
<!--通过类型来完成构造器注入-->
<constructor-arg type="java.lang.String" value="cao"/>
<constructor-arg type="int" value="1"/>
</bean>
最推荐通过name完成构造器注入
2.2.2 DI set注入(重要)xml
构造器永远没有那么细腻的赋值,即使匹配的再智能,所以需要set注入来完成更加细腻的注入
就是通过智能匹配值类型,**value(基本类型)和ref(引用类型,自定义)**即可
例子:
package com.kuang.pojo;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
public void setName(String name) {
this.name = name;
}
public void setAddress(Address address) {
this.address = address;
}
public void setBooks(String[] books) {
this.books = books;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public void setGames(Set<String> games) {
this.games = games;
}
public void setWife(String wife) {
this.wife = wife;
}
public void setInfo(Properties info) {
this.info = info;
}
public void toString(){
System.out.println("name="+ name
+ ",address="+ address.getAddress()
+ ",books="
);
for (String book:books){
System.out.print("<<"+book+">>\t");
}
System.out.println("\n爱好:"+hobbys);
System.out.println("card:"+card);
System.out.println("games:"+games);
System.out.println("wife:"+wife);
System.out.println("info:"+info);
}
}
<bean id="student" class="com.cao.pojo.Student">
<property name="name" value="cao"/>
<property name="user" ref="user"/>
<property name="wife">
<null/>
</property>
<property name="info">
<props>
<prop key="driver">com.mysql.jdbc.Driver</prop>
<prop key="user">root</prop>
</props>
</property>
<property name="books">
<array>
<value>三国演义</value>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
</array>
</property>
<!-- set集合如果未指定泛型可以放任何东西,甚至放个bean在里面-->
<property name="games">
<set>
<value>LOL</value>
<value>CSGO</value>
</set>
</property>
<!-- list有序列表如果未指定泛型可以放任何东西-->
<property name="hobbys">
<list>
<value>eat</value>
<value>play</value>
</list>
</property>
<!-- Map映射如果未指定泛型可以放任何东西-->
<property name="card">
<map>
<entry key="中国邮政" >
<value>你好</value>
</entry>
</map>
</property>
</bean>
2.2.3 DI 其他方式注入xml
P命名空间注入
需要在头文件中加上约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p"
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" p:name="狂神" p:age="18"/>
c命名空间注入
需要在头文件中加上约束文件
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" c:name="狂神" c:age="18"/>
发现问题:爆红了,刚才我们没有写有参构造!
解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!
2.3 Bean的作用域
2.3.1 Singleton
当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。Singleton是单例类型,就是在创建起容器时就同时自动创建了一个bean的对象,不管你是否使用,他都存在了,每次获取到的对象都是同一个对象。注意,Singleton作用域是Spring中的缺省作用域。要在XML中将bean定义成singleton,可以这样配置:
<bean id="ServiceImpl" class="cn.csdn.service.ServiceImpl" scope="singleton">
测试:
@Test
public void test03(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
System.out.println(user==user2);
}
2.3.2 Prototype
当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域。在XML中将bean定义成prototype,可以这样配置:
<bean id="account" class="com.foo.DefaultAccount" scope="prototype"/> 或者<bean id="account" class="com.foo.DefaultAccount" singleton="false"/>
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) 注解实现
2.3.3 Request
当一个bean的作用域为Request,表示在一次HTTP请求中,一个bean定义对应一个实例;即每个HTTP请求都会有各自的bean实例,它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
xml<bean id="loginAction" class=cn.csdn.LoginAction" scope="request"/>
针对每次HTTP请求,Spring容器会根据loginAction bean的定义创建一个全新的LoginAction bean实例,且该loginAction bean实例仅在当前HTTP request内有效,因此可以根据需要放心的更改所建实例的内部状态,而其他请求中根据loginAction bean定义创建的实例,将不会看到这些特定于某个请求的状态变化。当处理请求结束,request作用域的bean实例将被销毁。
2.3.4 Session
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
针对某个HTTP Session,Spring容器会根据userPreferences bean定义创建一个全新的userPreferences bean实例,且该userPreferences bean仅在当前HTTP Session内有效。与request作用域一样,可以根据需要放心的更改所创建实例的内部状态,而别的HTTP Session中根据userPreferences创建的实例,将不会看到这些特定于某个HTTP Session的状态变化。当HTTP Session最终被废弃的时候,在该HTTP Session作用域内的bean也会被废弃掉。
2.4 Bean的自动装配(*)
Spring的自动装配需要从两个角度来实现,或者说是两个操作:
- 组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
- 自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使的显式的配置降低到最少。
自动装配本质是set注入,会去寻找set方法,然后自动匹配注入
pojo:
package com.cao.pojo;
public class Person {
Cat cat;
Dog dog;
public Person() {
System.out.println(0);
}
public Person(Cat cat, Dog dog) {
System.out.println(1);
this.cat = cat;
this.dog = dog;
}
public void setCat(Cat cat) {//会去寻找这个方法
System.out.println(2);
this.cat = cat;
}
public void setDog(Dog dog) {//会去寻找这个方法
System.out.println(3);
this.dog = dog;
}
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
}
2.4.1 byName
依赖于id
<bean id="cat" class="com.cao.pojo.Cat"/>
<bean id="dog" class="com.cao.pojo.Dog"/>
<bean id="person" class="com.cao.pojo.Person" autowire="byName">
<!-- <property name="cat" ref="cat"/>-->
<!-- <property name="dog" ref="dog"/>-->
</bean>
当一个bean节点带有 autowire byName的属性时。
- 将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。注意id的标准写法是小写
- 去spring容器中寻找是否有此字符串名称id的对象。
- 如果有,就取出注入;如果没有,就报空指针异常。
2.4.2 byType
对id没有要求
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。所以单独使用还是不太行。
<bean id="cat" class="com.cao.pojo.Cat"/>
<bean id="dog" class="com.cao.pojo.Dog"/>
<bean id="person" class="com.cao.pojo.Person" autowire="byType">
<!-- <property name="cat" ref="cat"/>-->
<!-- <property name="dog" ref="dog"/>-->
</bean>
2.4.3 注解装配
导入aop包
导入context约束
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<context:annotation-config/>
@Autowired
是按类型自动转配的,不支持id匹配。
@Autowired
package com.cao.pojo;
import org.springframework.beans.factory.annotation.Autowired;
public class Person {
@Autowired
Cat cat;
@Autowired
Dog dog;
public Person() {
System.out.println(0);
}
public Person(Cat cat, Dog dog) {
System.out.println(1);
this.cat = cat;
this.dog = dog;
}
public void setCat(Cat cat) {
System.out.println(2);
this.cat = cat;
}
public void setDog(Dog dog) {
System.out.println(3);
this.dog = dog;
}
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
}
注意注解不用set方法注入,注解由反射实现可以直接拿到对象,所以不用set就能拿到
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)
private Cat cat;
@Qualifier
@Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配 @Qualifier不能单独使用。
@Autowired
@Qualifier(value = "cat1")
private Cat cat;
@Autowired
@Qualifier(value = "dog2")
private Dog dog;
@Resource
- @Resource如有指定的name属性,先按该属性进行byName方式查找装配;
- 其次再进行默认的byName方式进行装配;
- 如果以上都不成功,则按byType的方式自动装配。
- 都不成功,则报异常。
public class User {
//如果允许对象为null,设置required = false,默认为true
@Resource(name = "cat2")
private Cat cat;
@Resource
private Dog dog;
}
比较
- @Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
- @Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
- @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
2.5 使用注解开发(*)
<context:component-scan base-package="com.cao.pojo"/>
写上这句话
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null!
2.5.1 Bean的实现
@Component("user")//这样就相当于bean id为user(默认也是这个)
@Scope("singleton")
public class User {
@Value("cao")//注解注入,也可以在set方法上
private String name;
@Value("1")//注解注入,也可以在set方法上
private int id;
.......
@Component 代表这个类是个Bean,会自动注册
@Value 设置基本属性 可以放在属性上也可以放在set方法上
其他引用属性可以使用自动装配实现
@Scope(“singleton”) 设置bean的作用域
2.5.2 衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
2.5.3 xml与注解整合开发
推荐荐最佳实践
- xml管理Bean
- 注解完成属性注入
2.5.4 Java类进行配置(*)
完全不需要xml配置bean
使用@Bean注解 与@Configuration通常一起使用
@Configuration
public class Config {
@Bean
public Person getPerson(){
return new Person();
}
}
@Import(MyConfig2.class) 导入合并其他配置类,类似于配置文件中的 inculde 标签
2.5.5 @Component 和 @Bean区别
@Component 和 @Bean 是两种使用注解来定义bean的方式。
@Component
(和@Service
和@Repository
)用于自动检测和使用类路径扫描自动配置bean。注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。
这种方法对需要进行逻辑处理的控制非常有限,因为它纯粹是声明性的。
@Bean
用于显式声明单个bean,而不是让Spring像上面那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean。
如果想将第三方的类变成组件,你又没有没有源代码,也就没办法使用@Component
进行自动配置,这种时候使用@Bean
就比较合适了。不过同样的也可以通过xml方式来定义。
另外@Bean注解的方法返回值是对象,可以在方法中为对象设置属性。
3. AOP面向切面
3.1 静态代理
抽象角色:一般会使用接口或者抽象类来解决
真实角色:被代理的角色
代理角色:代理真实角色去做事情
客户:访问代理角色的人
好处:
可以使真实角色的操作更加的纯粹
公共业务也就交给代理角色,实现了业务的分工
公共业务生扩展时候,方便管理
缺点:
类多了 , 多了代理类 , 工作量变大了 . 开发效率降低
静态代理:代理的是一个对象,是一个真实对象
而接下来的动态代理则不同
其实代理可以去实现添一些操作,而不用去改源代码,直接在代理上实现,我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想
3.2 动态代理
动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们直接写好的
动态代理分为俩类:基于接口,基于类
- 基于接口:JDK动态代理(反射)
- 基于类:cglib
- java字节码实现:javasist 用的比较多
JDK动态代理
InvocationHandler 和 Proxy
动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。是因为所有被代理执行的方法,都是通过在InvocationHandler中的invoke方法调用的,所以我们只要在invoke方法中统一处理,就可以对所有被代理的方法进行相同的操作了。例如,这里的方法计时,所有的被代理对象执行的方法都会被计时,然而我只做了很少的代码量。
//抽象角色:租房
public interface Rent {
public void rent();
}
//真实角色: 房东,房东要出租房子
public class Host implements Rent{
public void rent() {
System.out.println("房屋出租");
}
}
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("收中介费");
}
}
//租客
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();
}
}
另一个例子:
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// proxy : 代理类
// method : 代理类的调用处理程序的方法对象.
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
public void log(String methodName){
System.out.println("执行了"+methodName+"方法");
}
}
public class Test {
public static void main(String[] args) {
//真实对象
UserServiceImpl userService = new UserServiceImpl();
//代理对象的调用处理程序
ProxyInvocationHandler pih = new ProxyInvocationHandler();
pih.setTarget(userService); //设置要代理的对象
UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
proxy.delete();
}
}
静态代理有的它都有,静态代理没有的,它也有!
- 一个动态代理 , 一般代理某一类业务
- 一个动态代理可以代理多个类,代理的是接口!
3.3 AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
3.3.1 术语
术语 | 含义 |
---|---|
目标(Target) | 被通知的对象 |
代理(Proxy) | 向目标对象应用通知之后创建的代理对象 |
连接点(JoinPoint) | 目标对象的所属类中,定义的所有方法均为连接点 |
切入点(Pointcut) | 被切面拦截 / 增强的连接点(切入点一定是连接点,连接点不一定是切入点) |
通知(Advice)—》方法 | 增强的逻辑 / 代码,也即拦截到目标对象的连接点之后要做的事情 |
切面(Aspect)—》类 | 切入点(Pointcut)+通知(Advice) |
Weaving(织入) | 将通知应用到目标对象,进而生成代理对象的过程动作 |
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多
AspectJ 定义的通知类型
3.3.2 实现AOP
导AOP织入包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.9.1</version>
</dependency>
第一种实现,实现通知接口
环境测试:
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("add");
}
@Override
public void delete() {
System.out.println("delete");
}
@Override
public void update() {
System.out.println("update");
}
@Override
public void query() {
System.out.println("query");
}
}
通知Advice:
public class AfterLog implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了"+target.getClass().getName()+"的"+method.getName()+"返回值为"+ returnValue);
}
}
public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(target.getClass().getName()+"的"+method.getName()+"方法");
}
}
设置AOP,包括切入点
<bean id="userService" class="com.cao.service.UserServiceImpl"/>
<bean id="log" class="com.cao.service.Log"/>
<bean id="afterLog" class="com.cao.service.AfterLog"/>
<aop:config>
<!-- 切入点-->
<aop:pointcut id="pointcut1" expression="execution(* com.cao.service.UserServiceImpl.*(..))"/>
<!-- 执行通知-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut1"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut1"/>
</aop:config>
注意执行过织入后userService不再是UserServiceImpl类下的对象了,而是代理类的对象了,这点非常重要,这就是基于接口的代理
测试:
@org.junit.Test
public void myTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");//所以这里用UserService而不是 UserServiceImpl
userService.add();
userService.delete();
userService.update();
userService.query();
}
com.cao.service.UserServiceImpl的add方法
add
执行了com.cao.service.UserServiceImpl的add返回值为null
com.cao.service.UserServiceImpl的delete方法
delete
执行了com.cao.service.UserServiceImpl的delete返回值为null
com.cao.service.UserServiceImpl的update方法
update
执行了com.cao.service.UserServiceImpl的update返回值为null
com.cao.service.UserServiceImpl的query方法
query
执行了com.cao.service.UserServiceImpl的query返回值为null
Process finished with exit code 0
第二种实现,自定义切面类
纯使用AOP标签来实现
public class DIYLog {
public void before(){
System.out.println("前");
}
public void after(){
System.out.println("后");
}
}
<bean id="diyLog" class="com.cao.service.DIYLog"/>
<aop:config>
<aop:aspect ref="diyLog">
<aop:pointcut id="pointcut" expression="execution(* com.cao.service.UserServiceImpl.*(..))"/>
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:before method="before" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
第三种实现,注解
@Component
@Aspect
public class DIYLog {
@Before("execution(* com.cao.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("前");
}
@After("execution(* com.cao.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("后");
}
}
<context:component-scan base-package="com.cao.service"/>//扫描注解,否则无效
<aop:aspectj-autoproxy/>
注意环绕的顺序
@Around("execution(* com.cao.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
jp.proceed();
System.out.println("环绕后");
}
环绕前
前
add
后
环绕后
环绕前
前
delete
后
环绕后
环绕前
前
update
后
环绕后
环绕前
前
query
后
环绕后
4. 整合
看SSM整合
5. spring事务
导入mybatis-spring包
- 编程式事务(没有spring的特点)
- 声明式事务
使用aop实现声明式事务的实现
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--s-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--所有方法-->
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="txPointcut" expression="execution(* com.cao.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>