Spring学习
一、Spring概述
1.1 简介
Spring : 春天 —->给软件行业带来了春天 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。 很难想象Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
官网 : http://spring.io/ 官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/ GitHub : https://github.com/spring-projects 官方文档:Core Technologies (spring.io)
1.2 优点
- Spring是一个开源免费的框架 , 容器 .
- Spring是一个轻量级的框架 , 非侵入式的 .
- 控制反转 IoC , 面向切面 Aop
- 对事物的支持 , 对框架的支持
- 一句话概括:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)
二、IOC
2.1 IOC基础
思想的转变:
从一个案例开始,来说说什么是IOC。
案例一:简单模拟实现对用户数据的读写
方式1:
比如一般新手程序员刚开始写代码:
会首先创建一个接口,接着继承并实现。如下:
public interface UserDao {
void getUser();
}
package cn.chen66.dao;
public class UserDaoImpl implements UserDao{
public void getUser() {
System.out.println("用户数据输出了!!!");
}
}
package cn.chen66.services;
public interface UserService {
void getUser();
}
package cn.chen66.services;
import cn.chen66.dao.UserDao;
import cn.chen66.dao.UserDaoImpl;
public class UserServicImpl implements UserService{
private UserDao userDao=new UserDaoImpl();
public void getUser() {
userDao.getUser();
}
}
最后测试一下,看能不能从service实现类中调用dao层中的方法。
import cn.chen66.services.UserServicImpl;
import org.junit.Test;
public class Mytest {
@Test
public void Test(){
UserServicImpl userServic = new UserServicImpl();
userServic.getUser();
}
}
运行结果:
如果这个时候用户改需求了呢?比如需要再增加一个Dao的实现类:
package cn.chen66.dao;
public class UserDaoMysqlImpl implements UserDao{
@Override
public void getUser() {
System.out.println("Mysql数据查询输出了!");
}
}
这时候如果需要通过service层调用dao层使用Mysql的话,则必须在ServiceImpl中重新修改对应的实现类,修改为:
private UserDao userDao=new UserDaoMysqlImpl();
这样写的程序,程序的主动性强。如果用户要改需求了,程序员必须全部改写代码,程序耦合度搞高,因为是new出来的对象,程序被写死了。
方式2:
对上述代码进行优化。解决上述代码中耦合度高的问题。
在ServiceImpl中不去new UserDao,而是利用字段的属性进行set注入
package cn.chen66.services;
import cn.chen66.dao.UserDao;
public class UserServicImpl implements UserService{
private UserDao userDao;
//利用SET注入
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void getUser() {
userDao.getUser();
}
}
测试类:
import cn.chen66.dao.UserDaoImpl;
import cn.chen66.dao.UserDaoMysqlImpl;
import cn.chen66.services.UserServicImpl;
import org.junit.Test;
public class Mytest {
@Test
public void Test(){
UserServicImpl userService = new UserServicImpl();
userService.setUserDao(new UserDaoMysqlImpl());
userService.getUser();
userService.setUserDao(new UserDaoImpl());
userService.getUser();
}
}
运行结果:
其实这个过程就有点接近IOC的核心思想了!
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
2.2 IOC本质
(摘自狂神笔记)
控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,也有人认为DI只是IoC的另一种说法。没有IoC的程序中 , 我们使用面向对象编程 , 对象的创建与对象间的依赖关系
完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,个人认为
所谓控制反转就是:获得依赖对象的方式反转了。
IoC是Spring框架的核心内容,使用多种方式完美的实现了IoC,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC。
Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从Ioc容器中取出需要的对象。
采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,
从而达到了零配置的目的。
控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,
其实现方法是依赖注入(Dependency Injection,DI)。
三、Hello Spring
第一个Spring小程序:HelloSpring!
1.导入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>SpringLearningDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- 根据自己的 Project SDK 来确定版本 -->
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.compilerVersion>11</maven.compiler.compilerVersion>
<spring-version>5.3.1</spring-version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 只需要导这一个 就能包含很多spring核心的jar包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring-version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
<!-- spring 需要导入commons-logging进行日志记录-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
2.编写pojo类Hello:
package cn.chen66.pojo;
public class Hello {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("name:"+name);
}
}
3.编写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就是java对象 , 由Spring创建和管理-->
<bean id="hello" class="cn.chen66.pojo.Hello">
<property name="name" value="陈66"/>
</bean>
</beans>
4.编写测试类:
import cn.chen66.pojo.Hello;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloTest {
@Test
public void Test(){
//解析beans.xml文件 , 生成管理相应的Bean对象
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//getBean : 参数即为spring配置文件中bean的id
Hello hello = (Hello) applicationContext.getBean("hello");
hello.show();
}
}
运行结果:
通过这个实例对比一下上面的示例一,仔细思考一下,这其中有什么关联?
- Hello对象由谁创建?
- Hello对象是如何实例化的?对象的属性由谁设置的?
依赖注入 : 就是利用set方法来进行注入的.
其实,spring的核心就是IOC。
IOC是一种编程思想,由主动的编程变成被动的接收
所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !
四、IOC创建对象的方式
4.1通过无参构造方法来创建
User.java
package cn.chen66.pojo;
public class User {
private String name;
public User(){
System.out.println("User的无参构造方法执行了!");
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void show(){
System.out.println("User的show方法执行了....");
}
}
applicationContext.xml
<bean id="user" class="cn.chen66.pojo.User">
<property name="name" value="陈66"/>
</bean>
UserTest.java
import cn.chen66.pojo.User;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserTest {
@Test
public void Test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
user.show();
}
}
运行结果:
可以发现:User的无参构造方法在show()方法执行之前就已经实例化了。
4.2通过有参构造
1.UserP.java
package cn.chen66.pojo;
public class UseP {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UseP(String name) {
this.name = name;
}
public void show(){
System.out.println("Name:"+name);
}
}
2.applicationContext.xml
有三种方式编写
<!-- 通过有参构造来创建IOC-->
<!-- 第一种 通过参数下标-->
<bean id="userP" class="cn.chen66.pojo.UseP">
<constructor-arg index="0" value="陈66呀"/>
</bean>
<!-- 第二种 通过参数类型-->
<bean id="userP" class="cn.chen66.pojo.UseP">
<constructor-arg type="java.lang.String" value="你好呀!陈66!"/>
</bean>
<!-- 第三种 通过参数名称-->
<bean id="userP" class="cn.chen66.pojo.UseP">
<constructor-arg name="name" value="陈66呀呀呀!"/>
</bean>
3.UserPTest.java
import cn.chen66.pojo.UseP;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserPTest {
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UseP userP = (UseP) context.getBean("userP");
userP.show();
}
}
结论:在加载配置文件的时候,其中管理的对象就已经被初始化了。
五、Spring配置
5.1别名
alias可以对bean管理的对象设置别名,可以设置多个别名
<!-- 使用alias进行对管理的对象进行命名 可以多次命名-->
<alias name="userP" alias="p"/>
<alias name="userP" alias="p2"/>
在getBean()的时候可以使用这些别名来获取bean
5.2 bean的配置
<!-- id是唯一标识符,如果不配置id 但配置了name 则name可以作为默认标识符-->
<!-- 如果配置了id又配置了name 则name是别名-->
<!-- name可以设置多个别名 可以用 空格 , ; 分隔-->
<!-- 如果没有配置name和id则可以通过反射来得到bean-->
<!-- class里面的类要写全限定名=包名+类名-->
<bean id="hello" name="h1,h2;h3 h4" class="cn.chen66.pojo.Hello">
<property name="name" value="你好呀!!!!chen66 h1"/>
</bean>
5.3 import导入
团队合作可以通过import来导入
<import resource="{path}/beans.xml"/>
六、 依赖注入(DI)
- 依赖注入(Dependency Injection,DI)。
- 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
6.1构造器注入
前面已经涉及到
6.2 Set注入(重点)
要求被注入的属性 , 必须有set方法 , set方法的方法名由set + 属性首字母大写 , 如果属性是boolean类型, 没有set方法 , 是 is .
测试pojo类
student.java
package cn.chen66.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 String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String[] getBooks() {
return books;
}
public void setBooks(String[] books) {
this.books = books;
}
public List<String> getHobbys() {
return hobbys;
}
public void setHobbys(List<String> hobbys) {
this.hobbys = hobbys;
}
public Map<String, String> getCard() {
return card;
}
public void setCard(Map<String, String> card) {
this.card = card;
}
public Set<String> getGames() {
return games;
}
public void setGames(Set<String> games) {
this.games = games;
}
public String getWife() {
return wife;
}
public void setWife(String wife) {
this.wife = wife;
}
public Properties getInfo() {
return info;
}
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);
}
}
Address.java
package cn.chen66.pojo;
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
applicationContext.xml
1.常量注入
<!-- 常量注入-->
<bean name="name1" class="cn.chen66.pojo.Student">
<property name="name" value="陈66888"/>
</bean>
2.bean注入
注意点:这里的值是一个引用,ref
<!-- bean注入-->
<bean id="addr" class="cn.chen66.pojo.Address">
<property name="address" value="南昌ECUT"/>
</bean>
<bean name="student" class="cn.chen66.pojo.Student">
<property name="name" value="小明"/>
<property name="address" ref="addr"/>
</bean>
3.数组注入
<!-- 数组注入-->
<property name="books">
<array>
<value>西游记</value>
<value>红楼梦</value>
<value>水浒传</value>
<value>人性的弱点</value>
</array>
</property>
4.List注入
<!-- list注入-->
<property name="hobbys">
<list>
<value>吃</value>
<value>玩游戏</value>
<value>睡</value>
<value>学习</value>
</list>
</property>
5.Map注入
<!-- Map注入-->
<property name="card">
<map>
<entry key="1" value="1111"/>
<entry key="中国建设银行" value="115666511"/>
</map>
</property>
6.Set注入
<!-- Set注入-->
<property name="games">
<set>
<value>LOL</value>
<value>BOB</value>
<value>王者荣耀</value>
</set>
</property>
7.Null注入
<!-- null注入-->
<property name="wife">
<null/>
</property>
8.Properties注入
<!-- Properties注入-->
<property name="info">
<props>
<prop key="学号">201820180311</prop>
<prop key="班级">182181Z</prop>
<prop key="学校">ECUT</prop>
</props>
</property>
测试类StudentTest.java
import cn.chen66.pojo.Student;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class StudentTest {
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
student.show();
}
}
测试结果:
6.3 扩展注入
P命名空间和C命名空间
User.java : 【注意:这里没有有参构造器!】
public class User {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1、P命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:p="http://www.springframework.org/schema/p"
<!--P(属性: properties)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" p:name="chen66" p:age="18"/>
2.2、c 命名空间注入 : 需要在头文件中加入约束文件
导入约束 : xmlns:c="http://www.springframework.org/schema/c"
<!--C(构造: Constructor)命名空间 , 属性依然要设置set方法-->
<bean id="user" class="com.kuang.pojo.User" c:name="chen66" c:age="18"/>
发现问题:爆红了,刚才我们没有写有参构造!
解决:把有参构造器加上,这里也能知道,c 就是所谓的构造器注入!
测试:
@Test
public void test02(){
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
6.4 bean的作用域
1.单例模式(Spring默认机制):
<bean name="student" class="cn.chen66.pojo.Student" scope="singleton">
测试代码:
public class StudentTest {
@Test
public void test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) context.getBean("student");
Student student2 = context.getBean("student", Student.class);
System.out.println(student==student2);
System.out.println(student.hashCode());
System.out.println(student2.hashCode());
}
结果:
2.原型模式:
<bean name="student" class="cn.chen66.pojo.Student" scope="prototype">
结果:
七、Bean的自动装配
- 自动装配是使用Spring满足bean依赖的一种方式
- spring会在某个上下文中为某个bean寻找其依赖的bean
Spring中bean有三种装配机制,分别是:
1.在XML中显式装配
2.在java中显式装配
3.隐式的bean发现机制和自动装配【重要】
案例切入:
一个人养了两个宠物,简单模拟。
dog类
package pojo;
public class Dog {
public void shout(){
System.out.println("wang~~");
}
}
cat类
package pojo;
public class Cat {
public void shot(){
System.out.println("miao~~~");
}
}
people类
package pojo;
public class People {
private String name;
private Cat cat;
private Dog dog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
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="cat" class="pojo.Cat"/>
<bean id="dog" class="pojo.Dog"/>
<bean id="people" class="pojo.People" >
<property name="name" value="陈66呀呀"/>
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
</bean>
</beans>
MyTest测试类
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.People;
public class MyTest {
@Test
public void Test(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext");
People people = context.getBean("people", People.class);
people.getCat().shot();
people.getDog().shout();
}
}
自动装配
1.ByName
<bean id="cat" class="pojo.Cat"/>
<bean id="dog" class="pojo.Dog"/>
<bean id="people" class="pojo.People" autowire="byName">
<property name="name" value="陈66呀呀"/>
</bean>
2.ByType
<bean id="cat1123" class="pojo.Cat"/>
<bean id="dog23232" class="pojo.Dog"/>
<bean id="people" class="pojo.People" autowire="byType">
<property name="name" value="陈66呀呀"/>
</bean>
使用autowire byType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
3.使用注解
导入依赖的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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
开启注解支持
<context:annotation-config/>
@Autowired
根据ByType自动匹配,当装配的bean的种类不唯一时会报错
这时可以添加@Qualifier(value="...")
进行匹配,此注解不能单独使用
测试:
people.java
private String name;
@Autowired @Qualifier(value = "cat1")
private Cat cat;
@Autowired @Qualifier(value = "dog1")
private Dog dog;
xml
<context:annotation-config/>
<bean id="cat1" class="pojo.Cat"/>
<bean id="cat2" class="pojo.Cat"/>
<bean id="dog1" class="pojo.Dog"/>
<bean id="people" class="pojo.People">
<property name="name" value="陈66呀呀"/>
</bean>
能正常运行:
@Resource
先根据Byname匹配,如果没有则会根据ByType匹配,如果还没有,则会报错
如果刚开始就是由很多id,但是没有名字符合的,则会报错
<bean id="cat1" class="pojo.Cat"/>
<bean id="cat2" class="pojo.Cat"/>
<bean id="cat2312" class="pojo.Cat"/>
<bean id="cat3" class="pojo.Cat"/>
@Resource
private Cat cat;
运行结果:
如果有多个名字,但有一个符合规范的则会运行成功!
<bean id="cat" class="pojo.Cat"/>
<bean id="cat2" class="pojo.Cat"/>
<bean id="cat2312" class="pojo.Cat"/>
<bean id="cat3" class="pojo.Cat"/>
运行结果:
同样,如果名字没有找到,则会通过类型查找,注意只能有一个id
<bean id="cat2312" class="pojo.Cat"/>
结果:
小结
@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.0之后,使用注解必须要导入aop的包.
在配置文件中,还必须导入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 https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 指定扫描的包-->
<context:component-scan base-package="com.chen66"/>
</beans>
8.1Bean的实现
在之前都是使用xml的配置进行bean的注入,但实际中使用注解开发更多,因为比较方便和简单。但如果是复杂一点的,还是推荐使用xml配置来做。
1.配置扫描那些包下的注解需要在配置文件中加入支持
<!-- 指定扫描的包-->
<context:component-scan base-package="com.chen66.pojo"/>
2.在指定的包下创建类,使用注解
package com.chen66.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("p1")//如果不设置括号内的id名,则可以直接使用类名进行匹配
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class people {
@Value("陈66呀呀!")
private String name;
public String getName() {
return name;
}
}
3.编写测试类
import com.chen66.pojo.people;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class PeopleAnnoTest {
@Test
public void Test(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
people people = applicationContext.getBean("p1", people.class);
System.out.println(people.getName());
}
}
4.运行结果:
8.2属性注入
1.可以不用提供set方法,直接在直接名上添加@value("值")
,如上述例子。
2.如果提供了set方法,在set方法上添加@value("值")
;
8.3衍生注解
我们使用的这些注解其实就是代替了配置文件中相应的配置而已,使用起来方便
@conponent
三个衍生注解
@Controller
Web层@Repository
Dao层@Service
Service层
写上这些注解,就相当于将这个类交给Spring管理装配了!
8.4自动装配
@Autowired
:自动装配通过类型。名字- 如果
@Autowired
不能唯一自动装配上属性,可以加@Qualifier(value="...")
进行匹配
- 如果
@Resource
:自动装配通过名字。类型@Nullable
:自动标注了这个注解,说明此字段可以为null
8.5作用域
@scope
- singleton:默认的,Spring会采用单例模式创建这个对象。关闭工厂 ,所有的对象都会销毁。
- prototype:多例模式。关闭工厂 ,所有的对象不会销毁。内部的垃圾回收机制会回收
@Component("people1")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
@Scope("Singleton")
public class people {
@Value("陈66呀呀!")
public String name;
@Value("陈66呀呀111!")
public void setName(String name) {
this.name = name;
}
}
如果同时有setter方法注解,和直接属性注解,则name的值最终还是以setter方法设置的值为准。
public class PeopleAnnoTest {
@Test
public void Test(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
people people = applicationContext.getBean("people1", people.class);
System.out.println(people.name);
}
}
运行结果:
8.6 小结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :推荐最佳实践
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
作用:
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null
8.7 使用java类进行配置Spring
JavaConfig 原来是 Spring 的一个子项目,它通过 Java 类的方式提供 Bean 的定义信息,在 Spring4 的版本, JavaConfig 已正式成为 Spring4 的核心功能 。
现在我们可以不用进行Spring的xml配置了,可以全权交个Java来做。
pojo类
package cn.chen66.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
//标记这是个Component
@Component
public class User {
@Value("陈66")
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
配置类
package cn.chen66.config;
import cn.chen66.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
//标记这是个配置类 ,类似bean.xml
@Configuration
//包扫描
@ComponentScan("cn.chen66")
public class UserConfig {
//注册一个bean
@Bean
User getUser(){
//返回要注入bean的对象
return new User();
}
}
测试类
package cn.pojo;
import cn.chen66.config.UserConfig;
import cn.chen66.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Mytest01 {
@Test
public void Test(){
//如果完全用了java类来配置Spring ,就只能通过ApplicationConfig上下文对象通过配置类的class对象来获取容器
ApplicationContext context = new AnnotationConfigApplicationContext(UserConfig.class);
User getUser = (User) context.getBean("getUser");
System.out.println(getUser.getName());
}
}
运行结果:
九、代理模式
AOP的底层机制就是动态代理
代理模式:
- 静态代理
- 动态代理
1.静态代理
通过生活中租房的例子,简单理解静态代理模式。
1.1 实例
首先定义一个公共的接口,有共同的约束,租房。
rent.java
package cn.chen66.demo01;
public interface Rent {
//出租业务的接口
public void rent();
}
房东要出租,所以要实现这个接口
Host.java
package cn.chen66.demo01;
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东出租房子了!");
}
}
中介帮房东租房,也要有共同的约束动作:租房,因此也要实现出租动作的接口,并且附带一些特有的动作。
Proxy.java
package cn.chen66.demo01;
public class Proxy implements Rent{
private Host host;
//代理房东租房
public Proxy(Host host) {
this.host = host;
}
public Proxy() {
}
@Override
public void rent() {
Visit();
Hetong();
Fare();
}
public void Hetong(){
System.out.println("签租赁合同!");
}
public void Visit(){
System.out.println("中介带你看房");
}
public void Fare(){
System.out.println("交中介费");
}
}
有顾客要租房,首先找到了房东,于是new一个Host类,host房东,要找中介帮他出租,也要new一个中介Proxy,
这时,出租房子的一系列操作,房东不需要管了,直接交给中介。
Client.java
package cn.chen66.demo01;
public class Client {
public static void main(String[] args) {
//房东要出租房
Host host = new Host();
host.rent();
//房东找中介
Proxy proxy = new Proxy(host);
//租客要租房
//你去找中介
proxy.rent();
}
}
运行结果:
1.2静态代理再理解
模拟工作中常见的例子。
如实现对用户的增删改查。
先定义一个业务接口
UserService.java
package cn.chen66.demo02;
public interface UserService {
public void add();
public void del();
public void update();
public void query();
}
对接口进行实现.
UserServiceImpl.java
package cn.chen66.demo02;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加一个用户!");
}
@Override
public void del() {
System.out.println("删除一个用户!");
}
@Override
public void update() {
System.out.println("修改一个用户!");
}
@Override
public void query() {
System.out.println("查询一个用户!");
}
}
这时进行测试
Client.java
package cn.chen66.demo02;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.add();
}
}
运行结果:
好!现在公司的需求来了,叫你实现一个日志功能。
需求实现:
使用代理来做,既不改变原有业务逻辑代码,依然能实现新的业务需求!
1.设置一个日志处理类,代理角色。
package cn.chen66.demo02;
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void del() {
log("del");
userService.del();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
private void log(String msg){
System.out.println("[Debug]实现了"+msg+"方法!");
}
}
2.测试实现
package cn.chen66.demo02;
public class Client {
public static void main(String[] args) {
//真实业务
UserServiceImpl userService = new UserServiceImpl();
//实现日志功能,创建代理类
UserServiceProxy userServiceProxy = new UserServiceProxy();
userServiceProxy.setUserService(userService);
//使用代理类实现添加功能
userServiceProxy.add();
}
}
运行结果:
我们在不改变原有业务逻辑代码的情况下,依然实现了新增的业务需求,这是AOP最核心的思想!
2.动态代理
动态代理非常灵活,而且由前面的静态代理我们也能感受到,写一个代理类,到最终实现相应的业务,代码量极多,极其繁琐,虽然一定程度上解决了程序耦合度的问题,但开发成本很大。因此,动态代理就很好的解决了这个问题。
2.1动态代理的分类:
- 基于接口的动态代理:JDK动态代理
- 基于类的动态代理:cglib
- 现在用的比较多的是java字节码来生成动态代理:使用Javasist
2.2 动态代理的JDK实现:
我们接下来就来学习一下使用JDK来实现动态代理
核心:InvocationHandler 和Proxy类
可以查看JDK文档
【InvocationHandler:调用处理程序】
Object invoke(Object proxy, 方法 method, Object[] args);
/*
参数
proxy - 调用该方法的代理实例
method -所述方法对应于调用代理实例上的接口方法的实例。 方法对象的声明类将是该方法声明的接口,它可以是代理类继承该方法的代理接口的超级接口。
args -包含的方法调用传递代理实例的参数值的对象的阵列,或null如果接口方法没有参数。原始类型的参数包含在适当的原始包装器类的实例中,
例如java.lang.Integer或java.lang.Boolean。
*/
【Proxy:代理】
生成代理类:
//生成代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),
this);
}
案例:
代码实现(以之前的房东租房为例):
Rent.java即抽象角色
package cn.chen66.demo03;
public interface Rent {
//出租业务的接口
public void rent();
}
Host.java即真实角色
package cn.chen66.demo03;
public class Host implements Rent {
@Override
public void rent() {
System.out.println("房东出租房子了!");
}
}
ProxyInvocationHandler即代理角色
package cn.chen66.demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
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
package cn.chen66.demo03;
public class Client {
public static void main(String[] args) {
//找房东租房
Host host = new Host();
//代理实例的调用动态代理程序
ProxyInvocationHandler handler = new ProxyInvocationHandler();
//房东把租房任务直接交给代理,将房东这个真实角色,直接丢给代理来处理
handler.setRent(host);
// 动态生成对应的代理角色
Rent proxy = (Rent) handler.getProxy();
proxy.rent();
}
}
运行结果:
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
2.3深化理解
使用动态代理对之前的Userservice实例进行实现
UserService.java
package cn.chen66.demo04;
public interface UserService {
public void add();
public void del();
public void update();
public void query();
}
UserServiceImpl.java
package cn.chen66.demo04;
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加一个用户!");
}
@Override
public void del() {
System.out.println("删除一个用户!");
}
@Override
public void update() {
System.out.println("修改一个用户!");
}
@Override
public void query() {
System.out.println("查询一个用户!");
}
}
代理类:ProxyInvocationHandler.java
package cn.chen66.demo04;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
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:代理类的调用处理程序的方法对象
@Override
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("[Debug]执行了"+methodName+"方法!");
}
}
测试类:
Client.java
package cn.chen66.demo04;
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//创建代理对象的调用处理程序的对象
ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
//设置要代理的对象
proxyInvocationHandler.setTarget(userService);
//动态生成代理
UserService proxy = (UserService) proxyInvocationHandler.getProxy();
proxy.add();
}
}
运行结果:
十、AOP
10.1 什么是AOP
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
10.2 AOP在Spring中的作用
10.3 使用Spring实现AOP
使用AOP植入,需要导入一个依赖包
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
1.第一种方式:通过SpringAPI实现
接口层:UserService.java
package com.chen66;
public interface UserService {
public void add();
public void del();
public void update();
public void select();
}
业务层:UserServiceImpl.java
package com.chen66;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加一个用户");
}
@Override
public void del() {
System.out.println("删除一个用户");
}
@Override
public void update() {
System.out.println("更新一个用户");
}
@Override
public void select() {
System.out.println("查询一个用户");
}
}
接着写增强类,加入前置后置日志功能
log.java
package com.chen66;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class log implements MethodBeforeAdvice {
/**
*
* @param method:要执行的目标对象的方法
* @param objects:被调用的方法的参数
* @param o:目标对象
* @throws Throwable
*/
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("开始执行"+o.getClass().getName()+"的"+method.getName()+"方法!");
}
}
AfterLog.java
package com.chen66;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class AfterLog implements AfterReturningAdvice {
/**
*
* @param returnValue:返回值
* @param method:被调用的方法对象
* @param args:方法参数
* @param target:被调用的目标对象
* @throws Throwable
*/
@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.chen66.UserServiceImpl"/>
<bean id="log" class="com.chen66.log"/>
<bean id="afterLog" class="com.chen66.AfterLog"/>
<!-- aop的配置-->
<aop:config>
<!-- 切入点:expression表达式,是要匹配执行的方法 execution(要执行的位置 * * * * * )-->
<aop:pointcut id="pointcut" expression="execution(* com.chen66.UserServiceImpl.*(..))"/>
<!-- 执行环绕;advice-config执行方法,pointcut-ref切入点-->
<aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
<aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>
</beans>
最后测试:
package com.chen66;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void Test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserServiceImpl userService = applicationContext.getBean("userService", UserServiceImpl.class);
userService.add();
}
}
运行结果:
错了????报错???
其实仔细回想一下,上面说的动态代理的时候,强调的是代理的是接口!因此将上述代码中的getbean()
的类型改成UserService
这个接口的类型就好了
即:
UserService userService = (UserService) applicationContext.getBean("userService");
运行结果:
2.第二种方式:自定义来实现AOP【主要是切面定义】
目标业务类不变依旧是userServiceImpl
- 先自己写个切入类:DiyPointCut.java
package com.chen66;
public class DiyPointCut {
public void before(){
System.out.println("方法执行前!");
}
public void after(){
System.out.println("方法执行后!");
}
}
- 再去ApplicationContext.xml中配置
<!-- 第二种方式 自定义实现AOP-->
<bean id="diy" class="com.chen66.DiyPointCut"/>
<aop:config>
<aop:aspect id="point" ref="diy">
<aop:pointcut id="diyPointCut" expression="execution(* com.chen66.UserServiceImpl.*(..))"/>
<aop:before pointcut-ref="diyPointCut" method="before"/>
<aop:after pointcut-ref="diyPointCut" method="after"/>
</aop:aspect>
</aop:config>
- 测试类不变
package com.chen66;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void Test1(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//动态代理,代理得是接口
UserService userService = (UserService) applicationContext.getBean("userService");
userService.update();
}
}
运行结果:
3.方法三:使用注解实现AOP
新建一个类:
AnnotationPointCut.java
package com.chen66;
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.chen66.UserServiceImpl.*(..))")
public void before(){
System.out.println("执行方法前!");
}
@After("execution(* com.chen66.UserServiceImpl.*(..))")
public void After(){
System.out.println("执行方法后!");
}
@Around("execution(* com.chen66.UserServiceImpl.*(..))")
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前");
System.out.println(pjp.getSignature());//签名
//执行目标方法
Object proceed = pjp.proceed();
System.out.println("环绕后");
System.out.println(proceed);
}
}
注意:导入的包是aspectj的lang下的annotation包
在xml中配置
配置bean
<bean id="annotationPointcut" class="com.chen66.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动态代理。