spring
文章目录
- spring
- 什么是spring
- spring框架作用:
- ioc
- aop
- spring基本配置
- IOC掌握什么?
- IOC创建对象方式
- Spring配置
- 别名配置
- bean配置
- import
- Spring注入
- 构造注入
- set注入
- 扩展注入
- bean的作用域
- Spring自动装配
- 装配方式
- Bean的自动装配
- 测试环境搭建
- byName
- 小结:
- **byType**
- 使用注解
- @Autowired
- Qualifier
- Resource
- 小结
- 注解开发
- Bean的实现
- 属性注入
- 衍生注解
- 作用域
- @cope
- XML与注解比较
- xml与注解整合开发
- 注解扫描:
- 获得properties中的数据
- Spring中的EL表达式
- xml中使用:
- 注解使用
- AOP需要掌握什么?
- 代理模式
- 代理类的分类:
- 静态代理:
- **静态代理角色分析**
- 案例一
- 案例二
- 静态代理的好处
- 静态代理的缺点
- 动态代理:
- 动态代理分类
- 案例一
- 案例二
- 动态代理的好处
- AOP
- Aop在Spring中的作用
- aop中必须明白的名词
- Spring实现Aop(三种)
- 第一种方式 API
- 第二种方式 自定义
- 第三种方式 注解
- 第三种方式 注解
什么是spring
Spring是一个开源框架,它由[Rod Johnson](https://baike.baidu.com/item/Rod Johnson)创建。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。
Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
轻量——从大小与开销两方面而言Spring都是轻量的。完整的Spring框架可以在一个大小只有1MB多的JAR文件里发布。并且Spring所需的处理开销也是微不足道的。此外,Spring是非侵入式的:典型地,Spring应用中的对象不依赖于Spring的特定类。
控制反转——Spring通过一种称作控制反转(IoC)的技术促进了松耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
面向切面——Spring提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务(例如审计(auditing)和事务(transaction)管理)进行内聚性的开发。应用对象只实现它们应该做的——完成业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的系统级关注点,例如日志或事务支持。
容器——Spring包含并管理应用对象的配置和生命周期,在这个意义上它是一种容器,你可以配置你的每个bean如何被创建——基于一个可配置原型(prototype),你的bean可以创建一个单独的实例或者每次需要时都生成一个新的实例——以及它们是如何相互关联的。然而,Spring不应该被混同于传统的重量级的EJB容器,它们经常是庞大与笨重的,难以使用。
框架——Spring可以将简单的组件配置、组合成为复杂的应用。在Spring中,应用对象被声明式地组合,典型地是在一个XML文件里。Spring也提供了很多基础功能(事务管理、持久化框架集成等等),将应用逻辑的开发留给了你。
所有Spring的这些特征使你能够编写更干净、更可管理、并且更易于测试的代码。它们也为Spring中的各种模块提供了基础支持。
spring框架作用:
ioc
控制反转;创建对象和管理对象(将对象的创建和销毁工作交给spring)默认是饿汉单例模式、
aop
面向切面;提供声明式事务,允许用户自定义切面.简单来说就是不改变原有的代码对代码增强(在原有的代码上进行代码添加,比如在原来类的方法中添加日志功能)。
spring基本配置
- 导入jar
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
- 配置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">
</beans>
- ClassPathXmlApplcationContext(“配置文件”)
ClassPathXmlApplicationContext c1 =new ClassPathXmlApplicationContext("applicationContext.xml");
IOC掌握什么?
IOC创建对象方式
//通过ClassPathXmlApplicationContext获得配置文件
//getBean()获得对象实例
ClassPathXmlApplicationContext c1 =new ClassPathXmlApplicationContext("applicationContext.xml");
StudentPojo s1 = c1.getBean("s1", StudentPojo.class);
Spring配置
别名配置
alias 设置别名 , 为bean设置别名 , 可以设置多个别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="userT" alias="userNew"/>
bean配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id:是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
name: name可以设置多个别名,可以用逗号,分号,空格隔开, 如果配置id,又配置了name,那么name是别名
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class:bean的全限定名=包名+类名。反射创建对象的依据
init-method="" 调用自定义初始方法
destroy-method=""调用自定义销毁方法
autowire="byType" 自动装配分为 byname 和bytype
lazy-init="true"单例模式的转换,true时为饿汉式 false为懒汉式
factory-bean="" 指定工厂id
factory-method=" 创建对象的方法
scope="" 作用域 singleton(单例)、prototype(多例)、request、session
扩展注入:c、p;
需要添加约束:
xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
c:用户注入构造函数;格为:c:fieldName="value",根据构造函数的参数来注入
p:用户注入字段;格式为:p: fieldName="value"
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
<property name="name" value="Spring"/>
</bean>
import
把多个配置文件引用到一个配置文件;再用过程中子需要要获得主配置就可,其他配置文件的内容同样可以读取。
<import resource="{path}/beans.xml"/>
Spring注入
构造注入
<constructor-arg name="address" value="中国"></constructor-arg>
set注入
通过bean方式注入要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型 , 没有set方法 , 是 is .如果是通过注解注入类中不需要set方式
案列
Address类
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Student类
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 show(){
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);
}
}
xml文件注入:
<!--常量注入-->
<property name="name" value="姜兴"></property>
<!--实例注入-->
<property name="address" ref="address"></property>
<!--集合注入-->
<property name="hobbys">
<list>
<value>a0</value>
<value>a1</value>
<value>a2</value>
<value>a3</value>
</list>
</property>
<!--数组注入-->
<property name="books">
<array>
<value>java</value>
<value>java ee</value>
<value>sring</value>
<value>h5</value>
</array>
</property>
<!--map注入-->
<property name="card">
<map >
<entry key="name" value="张三"></entry>
<entry key="age" value="20"></entry>
<entry key="add" value="中国"></entry>
</map>
</property>
<!--set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>COC</value>
</set>
</property>
<!--property注入-->
<property name="info">
<props>
<prop key="学号">20190604</prop>
<prop key="性别">男</prop>
<prop key="姓名">小明</prop>
</props>
</property>
<!--null注入-->
<property name="wife"><null/></property>
扩展注入
扩展注入:c、p;
需要添加约束:
xmlns:p=“http://www.springframework.org/schema/p”
xmlns:c=“http://www.springframework.org/schema/c”
c:用户注入构造函数;格为:c:fieldName=“value”,根据构造函数的参数来注入
p:用户注入字段;格式为:p: fieldName=“value”
测试类Address
package com.taxing.pojo;
import org.springframework.context.annotation.Lazy;
public class Address {
private String address;
private String number;
public Address() {
System.out.println("实例化了");
}
public Address(String address, String number) {
this.address = address;
this.number = number;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
", number='" + number + '\'' +
'}';
}
}
xml配置:
<bean class="com.taxing.pojo.Address" id="addressc" c:address="guizh" c:number="122" ></bean>
<bean class="com.taxing.pojo.Address" id="addressp" p:address="china" p:number="1111"></bean>
bean的作用域
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象 .
几种作用域中,request、session作用域仅在基于web的应用中使用(不必关心你所采用的是什么web应用框架),只能用在基于web的Spring ApplicationContext环境。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqHckY9z-1605103089995)(C:\Users\a\AppData\Roaming\Typora\typora-user-images\image-20201110155433680.png)]
Spring自动装配
装配方式
Bean的自动装配
1、自动装配是使用spring满足bean依赖的一种方法,spring会在应用上下文中为某个bean寻找其依赖的bean。
2、Spring中bean有三种装配机制,分别是:
在xml中显式配置;
在java中显式配置;
隐式的bean发现机制和自动装配。
3、Spring的自动装配需要从两个角度来实现,或者说是两个操作:
组件扫描(component scanning):spring会自动发现应用上下文中所创建的bean;
自动装配(autowiring):spring自动满足bean之间的依赖,也就是我们说的IoC/DI;
组件扫描和自动装配组合发挥巨大威力,使的显示的配置降低到最少。
4、推荐不使用自动装配xml配置 , 而使用注解 .
测试环境搭建
新建一个项目
新建两个实体类,Cat Dog 都有一个叫的方法
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
新建一个用户类 User
public class User {
private Cat cat;
private Dog dog;
private String str;
}
编写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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="qinjiang"/>
</bean></beans>
测试
public class MyTest {
@Test
public void testMethodAutowire() {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
user.getCat().shout();
user.getDog().shout();
}
}
结果正常输出,环境OK
byName
**autowire byName (**按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
测试:
修改bean配置,增加一个属性 autowire=“byName”
<bean id="user" class="com.kuang.pojo.User" autowire="byName">
<property name="str" value="qinjiang"/>
</bean>
再次测试,结果依旧成功输出!
我们将 cat 的bean id修改为 catXXX
再次测试, 执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结:
当一个bean节点带有 autowire byName的属性时。
将查找其类中所有的set方法名,例如setCat,获得将set去掉并且首字母小写的字符串,即cat。
去spring容器中寻找是否有此字符串名称id的对象。
如果有,就取出注入;如果没有,就报空指针异常。
byType
**autowire byType (**按类型自动装配)
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException
测试:
将user的bean配置修改一下 : autowire="byType"
测试,正常输出
在注册一个cat 的bean对象!
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User" autowire="byType">
<property name="str" value="qinjiang"/>
</bean>
测试,报错:NoUniqueBeanDefinitionException
删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
这就是按照类型自动装配!
使用注解
jdk1.5开始支持注解,spring2.5开始全面支持注解。
准备工作: 利用注解的方式注入属性。
在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
开启属性注解支持
<context:annotation-config/>
@Autowired
@Autowired是按类型自动转配的,不支持id匹配。
需要导入 spring-aop的包!
测试:
将User类中的set方法去掉,使用@Autowired注解
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
此时配置文件内容
<context:annotation-config/>
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
测试,成功输出结果!
@Autowired(required=false) 说明: false,对象可以为null;true,对象必须存对象,不能为null。
//如果允许对象为null,设置required = false,默认为true
@Autowired(required = false)private Cat cat;
Qualifier
· @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
· @Qualifier不能单独使用。
测试实验步骤:
配置文件修改内容,保证类型存在对象。且名字不为类的默认名字!
<bean id="dog1" class="com.kuang.pojo.Dog"/>
<bean id="dog2" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
没有加Qualifier测试,直接报错
在属性上添加Qualifier注解
@Autowired
@Qualifier(value = "cat2")
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;
private String str;
}
beans.xml
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
<bean id="cat2" class="com.kuang.pojo.Cat"/>
<bean id="user" class="com.kuang.pojo.User"/>
测试:结果OK
配置文件2:beans.xml , 删掉cat2
<bean id="dog" class="com.kuang.pojo.Dog"/>
<bean id="cat1" class="com.kuang.pojo.Cat"/>
实体类上只保留注解
@Resource
private Cat cat;
@Resource
private Dog dog;
结果:OK
结论:先进行byName查找,失败;再进行byType查找,成功。
小结
@Autowired与@Resource异同:
@Autowired与@Resource都可以用来装配bean。都可以写在字段上,或写在setter方法上。
@Autowired默认按类型装配(属于spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用
@Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。
注解开发
在spring4之后,想要使用注解形式,必须得要引入aop的包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ijftEw2C-1605103090004)(file:///C:/Users/a/AppData/Local/Temp/msohtmlclip1/01/clip_image002.gif)]
在配置文件当中,还得要引入一个context约束
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
Bean的实现
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
配置扫描哪些包下的注解
<!--指定注解扫描包-->
<context:component-scan base-package="com.kuang.pojo"/>
在指定包下编写类,增加注解
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
public String name = "秦疆";
}
测试
@Test
public void test(){
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("beans.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user.name);
}
属性注入
使用注解注入属性
可以不用提供set方法,直接在直接名上添加@value(“值”)
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {
@Value("姜兴")
// 相当于配置文件中 <property name="name" value="姜兴"/>
public String name;
}
如果提供了set方法,在set方法上添加@value(“值”);
@Component("user")
public class User {
public String name;
@Value("姜兴")
public void setName(String name) {
this.name = name;
}
}
衍生注解
我们这些注解,就是替代了在配置文件当中配置步骤而已!更加的方便快捷!
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
@Controller:web层
@Service:service层
@Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
作用域
@cope
· singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
· prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Controller("user")
@Scope("prototype")
public class User {
@Value("姜兴")
public String name;
}
IOC小结
XML与注解比较
XML可以适用任何场景 ,结构清晰,维护方便;
注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发
推荐最佳实l践
xml管理Bean
注解完成属性注入
注解扫描:
使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:component-scan base-package="com.taxing.paly"></context:component-scan>
<context:annotation-config/>
作用:
进行注解驱动注册,从而使注解生效
用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
如果不扫描包,就需要手动配置bean
如果不加注解驱动,则注入的值为null!
获得properties中的数据
dariverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=admin
<!--读取配置文件xxx.properties-->
<utill:properties id="date" location="classpath:data.properties"></utill:properties>
<bean id="date1" class="com.taxing.pojo.date">
<property name="properties" value="date1"></property>
</bean>
public class date {
private Properties properties;
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void getData(){
String url = properties.getProperty("url");
String name = properties.getProperty("username");
String password = properties.getProperty("password");
String DriverClass = properties.getProperty("dariverClassName");
System.out.println("url = " + url);
System.out.println("name = " + name);
System.out.println("password = " + password);
System.out.println("DriverClass = " + DriverClass);
}
public date() {
}
}
@Test
public void dateset(){
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("applicationContext.xml");
date date = cac.getBean("date1", date.class);
date.getData();
}
Spring中的EL表达式
Spring中的El表达式和其他的表达式一样可以进行赋值和做四则运算。
xml中使用:
xml中格式#{},需要注意的是,被注入的值必须有set方法。
创建book类用于注入Student类中
package el;
public class Book {
private String name;
private String number;
public Book() {
}
public Book(String name, String number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
}
创建Student类,用来el表达式传值
package el;
public class Student {
private Book book;
private int mynumber;
public int getMynumber() {
return mynumber;
}
public void setMynumber(int mynumber) {
this.mynumber = mynumber;
}
public Student() {
}
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public void getbook(){
book.getName();
}
}
<?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="book" class="el.Book">
<property name="name" value="java"></property>
<property name="number" value="1234567"></property>
</bean>
<bean class="el.Student" id="student">
<property name="book" value="#{book}"></property>//为Student中的book字段赋值
<property name="mynumber" value="#{23+9 }"></property>//为Student中的mynumber字段做加法
</bean>
</beans>
@Test
public void el(){
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("el.xml");
Student studentService = cac.getBean("student", Student.class);
int mynumber = studentService.getMynumber();
System.out.println(studentService.getBook().toString());
System.out.println(mynumber);
}
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ohE1fE4-1605103090011)(C:\Users\a\AppData\Roaming\Typora\typora-user-images\image-20201111213031846.png)]
注解使用
创建book类:
package el;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Book {
private String name;
private String number;
public Book() {
}
public Book(String name, String number) {
this.name = name;
this.number = number;
}
public String getName() {
return name;
}
@Value("java EE")
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
@Value("123321")
public void setNumber(String number) {
this.number = number;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
}
创建Student
package el;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Student {
@Value("#{book}")
private Book book;
@Value("#{123}")
private int mynumber;
@Value("#{book.number}")
private int booknumber;
@Override
public String toString() {
return "Student{" +
"book=" + book +
", mynumber=" + mynumber +
", booknumber=" + booknumber +
'}';
}
}
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="el"></context:component-scan>
</beans>
测试:
@Test
public void setname(){
ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("el.xml");
Student student = cac.getBean("student", Student.class);
System.out.println(student.toString());
}
结果:
Student{book=Book{name='java EE', number='123321'}, mynumber=123, booknumber=123321}
AOP需要掌握什么?
代理模式
SpringAop底层是动态代理实现的,在学习SpringAop之前建议学习代理类。那么什么是代理类呢?简单来说就是创建一个类来对原来的类进行增强。也可以换种方式理解,房东要出租房,你要租房子,由于某种原因你找不到房主,那么此时你想通过房屋中介公司来租房,房屋中介公司带你去看房,还给你介绍房屋的情况等等。那么房屋中介公司就等于我这里说的代理类。我们可以通过代理类来实现不改变代码的情况下对原来的代码进行增强,比如在代码之后添加日志功能、代码之前对参数判断等功能。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5HeuX83A-1605103090019)(C:\Users\a\AppData\Roaming\Typora\typora-user-images\image-20201111160813487.png)]
代理类的分类:
代理类分为静态代理和动态代理。代理模式代理的是接口。
静态代理:
通过实现接口,手动的配置代理类
静态代理角色分析
抽象角色 : 一般使用接口或者抽象类来实现
真实角色 : 被代理的角色
代理角色 : 代理真实角色 ; 代理真实角色后 , 一般会做一些附属的操作 .
客户 : 使用代理角色来进行一些操作 .
案例一
代码实现
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();
}
}
案例二
创建一个抽象角色,比如咋们平时做的用户业务,抽象起来就是增删改查!
//抽象角色:增删改查业务
public interface UserService {
void add();
void delete();
void update();
void query();
}
我们需要一个真实对象来完成这些增删改查操作
//真实对象,完成增删改查操作的人
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("查询了一个用户");
}
}
需求来了,现在我们需要增加一个日志功能,怎么实现!
· 思路1 :在实现类上增加代码 【麻烦!】
· 思路2:使用代理来做,能够不改变原来的业务情况下,实现此功能就是最好的了!
设置一个代理类来处理日志! 代理角色
//代理角色,在这里面增加日志的实现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+"方法");
}
}
测试访问类:
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//代理类
UserServiceProxy proxy = new UserServiceProxy();
//使用代理类实现日志功能!
proxy.setUserService(userService);
proxy.add();
}
}
静态代理的好处
可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
公共的业务由代理来完成 . 实现了业务的分工 ,
公共业务发生扩展时变得更加集中和方便 .
静态代理的缺点
类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
动态代理:
动态代理的角色和静态代理的一样 ,动态代理的代理类是动态生成的 ,静态代理的代理类是我们提前写好的。
动态代理分类
动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
· 基于接口的动态代理----JDK动态代理
· 基于类的动态代理–cglib
· 现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
· 我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
JDK的动态代理需要了解两个类
核心 : InvocationHandler 和 Proxy , 打开JDK帮助文档看看
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqCscMw5-1605103090023)(C:\Users\a\AppData\Roaming\Typora\typora-user-images\image-20201111162751273.png)]
【InvocationHandler:调用处理程序】
Object invoke(Object proxy, 方法 method, Object[] args);//参数
//proxy - 调用该方法的代理实例
//method -所述方法对应于调用代理实例上的接口方法的实例。 方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
//args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。 原始类型的参数包含在适当的原始包装器类的实例中,例如java.lang.Integer或java.lang.Boolean 。
【Proxy : 代理】
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gIlgtTrY-1605103090026)(file:///C:/Users/a/AppData/Local/Temp/msohtmlclip1/01/clip_image004.gif)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1qRfXmx-1605103090027)(file:///C:/Users/a/AppData/Local/Temp/msohtmlclip1/01/clip_image006.gif)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L1al7kgw-1605103090033)(file:///C:/Users/a/AppData/Local/Temp/msohtmlclip1/01/clip_image008.gif)]
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
案例一
抽象角色和真实角色和之前的一样!
Rent . java 即抽象角色
//抽象角色:租房
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();
}
}
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!、
案例二
我们来使用动态代理实现代理我们后面写的UserService!
我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!
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();
}
}
【测试,增删改查,查看结果】
动态代理的好处
静态代理有的它都有,静态代理没有的,它也有!
· 可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
· 公共的业务由代理来完成 . 实现了业务的分工 ,
· 公共业务发生扩展时变得更加集中和方便 .
· 一个动态代理 , 一般代理某一类业务
· 一个动态代理可以代理多个类,代理的是接口!
AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
aop中必须明白的名词
横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …(就是我们想要做什么功能,还没有写代码,存在我们的脑子中)
切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。(就是我想要做的功能的类)
**通知(Advice):**切面必须要完成的工作。即,它是类中的一个方法。(就是我们要做的功能)
目标(Target):被通知对象。
代理(Proxy):向目标对象应用通知之后创建的对象。
**切入点(PointCut):**切面通知 执行的 “地点”的定义。(就是指在方法的的前面加代码还是在后面加代码)
连接点(JointPoint):与切入点匹配的执行点。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PnRtClGz-1605103090037)(file:///C:/Users/a/AppData/Local/Temp/msohtmlclip1/01/clip_image004.gif)]
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gK5JlFjS-1605103090041)(file:///C:/Users/a/AppData/Local/Temp/msohtmlclip1/01/clip_image006.gif)]
即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .
Spring实现Aop(三种)
【重点】使用AOP织入,需要导入一个依赖包!
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
第一种方式 API
通过 Spring API 实现
首先编写我们的业务接口和实现类
public interface UserService {
public void add();
public void delete();
public void update();
public void search();
}
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 search() {
System.out.println("查询用户");
}
}
然后去写我们的增强类 , 我们编写两个 , 一个前置增强 一个后置增强
public class Log implements MethodBeforeAdvice {
//method : 要执行的目标对象的方法
//objects : 被调用的方法的参数
//Object : 目标对象
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println( o.getClass().getName() + "的" + method.getName() + "方法被执行了");
}
}
public class AfterLog implements AfterReturningAdvice {
//returnValue 返回值
//method被调用的方法
//args 被调用的方法的对象的参数
//target 被调用的目标对象
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("执行了" + target.getClass().getName()
+"的"+method.getName()+"方法,"
+"返回值:"+returnValue);
}
}
最后去spring的文件中注册 , 并实现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">
<!--注册bean-->
<bean id="userService" class="com.kuang.service.UserServiceImpl"/>
<bean id="log" class="com.kuang.log.Log"/>
<bean id="afterLog" class="com.kuang.log.AfterLog"/>
<!--aop的配置-->
<aop:config>
<!--切入点 expression:表达式匹配要执行的方法-->
<aop:pointcut id="pointcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<!--执行环绕; advice-ref执行方法 . pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
测试
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.search();
}
}
Aop的重要性 : 很重要 . 一定要理解其中的思路 , 主要是思想的理解这一块 .
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
第二种方式 自定义
自定义类来实现Aop
目标业务类不变依旧是userServiceImpl
第一步 : 写我们自己的一个切入类
public class DiyPointcut {
public void before(){
System.out.println("---------方法执行前---------");
}
public void after(){
System.out.println("---------方法执行后---------");
}
}
去spring中配置
<!--第二种方式自定义实现-->
<!--注册bean-->
<bean id="diy" class="com.kuang.config.DiyPointcut"/>
<!--aop的配置-->
<aop:config>
<!--第二种方式:使用AOP的标签实现-->
<aop:aspect ref="diy">
<aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPonitcut" method="before"/>
<aop:after pointcut-ref="diyPonitcut" method="after"/>
</aop:aspect>
</aop:config>
测试:
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
第三种方式 注解
使用注解实现
第一步:编写一个注解实现的增强类
package com.kuang.config;
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;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
op:config>
测试:
```java
public class MyTest {
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserService userService = (UserService) context.getBean("userService");
userService.add();
}
}
第三种方式 注解
使用注解实现
第一步:编写一个注解实现的增强类
package com.kuang.config;
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;
@Aspect
public class AnnotationPointcut {
@Before("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void before(){
System.out.println("---------方法执行前---------");
}
@After("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void after(){
System.out.println("---------方法执行后---------");
}
@Around("execution(* com.kuang.service.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
System.out.println("签名:"+jp.getSignature());
//执行目标方法proceed
Object proceed = jp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
第二步:在Spring配置文件中,注册bean,并增加支持注解的配置
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.kuang.config.AnnotationPointcut"/>
<aop:aspectj-autoproxy/>
aop:aspectj-autoproxy:说明
通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了
<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。