今日学习目标
一、基于注解的IOC和DI
1. 快速入门(重点)
需求描述
- 有dao层:
UserDao
和UserDaoImpl
- 有service层:
UserService
和UserServiceImpl
- 使用注解配置bean,并注入依赖
需求分析
-
准备工作:创建Maven项目,导入依赖坐标
-
编写代码并注解配置:
编写dao层、service层代码,使用注解
@Component
配置bean:代替xml里的bean
标签使用注解
@Autowired
依赖注入:代替xml里的property
和constructor-arg
标签 -
在xml配置文件中开启组件扫描
-
测试
需求实现
1) 准备工作
- 创建Maven项目,导入依赖坐标
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
2) 编写代码,并注解配置
UserDao
接口
package com.itheima.dao;
public interface UserDao {
void add();
}
UserDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
@Component
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("调用了UserDaoImpl的add方法~!~");
}
}
UserService
接口
package com.itheima.service;
public interface UserService {
void add();
}
UserServiceImpl
实现类
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/*
需求:
在UserServiceImpl里面调用UserDaoImpl的add方法
分析:
1. 把这两个类交给spring管理,让spring创建这两个类的对象
2. 在UserServiceImpl里面注入UserDaoImpl 的对象
3. 使用对象来调用方法
步骤:
1. 在UserServiceImpl和UserDaoImpl身上打注解 : @Component
2. 在UserServiceImpl里面定义属性,private UserDao userDao;
3. 在属性身上打注解: @AutoWired
4. 在xml里面打开扫描的开关,这样spring才能知道哪些类要创建对象,里面的什么属性要注入!
*/
@Component
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDaoImpl;
public void add() {
System.out.println("调用了UserServiceImpl的add方法~!");
userDaoImpl.add();
}
}
3) 开启组件扫描
- 创建
applicationContext.xml
,注意引入的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">
<!--
1. 告诉spring要扫描指定的包,因为这个包下有些类要创建对象
context:component-scan : 用来指定扫描组件的标签
base-package : 表示要扫描哪个包
a. 可以写一个总的包名
b. 可以写具体的包名,可以写多!使用 空格,逗号,分号,tab 分隔!
-->
<!--<context:component-scan base-package="com.itheima.dao.impl com.itheima.service.impl"/>-->
<context:component-scan base-package="com.itheima"/>
</beans>
4) 功能测试
- 创建一个测试类,调用Service
package com.itheima.test;
import com.itheima.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUserServiceImpl {
@Test
public void testAdd(){
//1. 创建工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 问工厂要对象
UserService us = context.getBean(UserService.class);
//3. 调用方法
us.add();
}
}
步骤小结
-
导入依赖
-
定义接口和实现类(dao 和 service)
-
在实现类上面打上注解 @Component
-
在属性上面打上注解@AutoWired
-
在applicationContext.xml里面打开扫描的开关
<context:component-scan base-package="com.itheima"/>
2. 注解使用详解
2.1 开启组件扫描
- 在Spring中,如果要使用注解开发,就需要在
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"
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">
<!--
1. 告诉spring要扫描指定的包,因为这个包下有些类要创建对象
context:component-scan : 用来指定扫描组件的标签
base-package : 表示要扫描哪个包
a. 可以写一个总的包名
b. 可以写具体的包名,可以写多!使用 空格,逗号,分号,tab 分隔!
-->
<!--<context:component-scan base-package="com.itheima.dao.impl com.itheima.service.impl"/>-->
<context:component-scan base-package="com.itheima"/>
</beans>
2.2 声明bean的注解【IOC】
简介
注解 | 说明 |
---|---|
@Component |
用在类上,相当于bean标签 |
@Controller |
用在web层类上,配置一个bean(是@Component 的衍生注解) |
@Service |
用在service层类上,配置一个bean(是@Component 的衍生注解) |
@Repository |
用在dao层类上,配置一个bean(是@Component 的衍生注解) |
@Component
:类级别的一个注解,用于声明一个bean,使用不多value
属性:bean的唯一标识 (id值)。如果不配置,默认以首字母小写的类名为id
@Controller, @Service, @Repository
,作用和@Component
完全一样,但更加的语义化,使用更多@Controller
:用于web层的bean@Service
:用于Service层的bean@Repository
:用于dao层的bean
示例
UserDaoImpl
类上使用注解@Repository
@Repository("userDao")
public class UserDaoImpl implements UserDao{
}
UserServiceImpl
类上使用注解@Service
@Service("userService")
public class UserServiceImpl implements UserService{
}
UserController
类上使用注解@Controller
@Controller
public class UserController{}
2.3 配置bean的注解 【IOC】
注解 | 说明 |
---|---|
@Scope |
相当于bean标签的scope 属性 |
@PostConstruct |
相当于bean标签的init-method 属性 |
@PreDestroy |
相当于bean标签的destory-method 属性 |
配置bean的作用范围:
-
@Scope
:配置bean的作用范围,相当于bean标签的scope属性。加在bean对象上 -
@Scope
的常用值有: -
singleton
:单例的,容器中只有一个该bean对象- 何时创建:容器初始化时
- 何时销毁:容器关闭时
-
prototype
:多例的,每次获取该bean时,都会创建一个bean对象- 何时创建:获取bean对象时
- 何时销毁:长时间不使用,垃圾回收
@Scope("prototype")
@Service("userService")
public class UserServiceImpl implements UserService{
//...
}
配置bean生命周期的方法
@PostConstruct
是方法级别的注解,用于指定bean的初始化方法@PreDestroy
是方法级别的注解,用于指定bean的销毁方法
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
/*
需求:
在UserServiceImpl里面调用UserDaoImpl的add方法
分析:
1. 把这两个类交给spring管理,让spring创建这两个类的对象
2. 在UserServiceImpl里面注入UserDaoImpl 的对象
3. 使用对象来调用方法
步骤:
1. 在UserServiceImpl和UserDaoImpl身上打注解 : @Component
2. 在UserServiceImpl里面定义属性,private UserDao userDao;
3. 在属性身上打注解: @AutoWired
4. 在xml里面打开扫描的开关,这样spring才能知道哪些类要创建对象,里面的什么属性要注入!
*/
/*
IOC的注解:
@Component : 是通用的注解
@Controller :针对web层
@Service : 针对service层
@Repository :针对dao层
属性:
value : 用来指定id值,如果不指定,那么将会把类名(首字母小写)作为id值!
@Scope: 用来配置单例或者多例
singleton: 单例, 默认就是单例
prototype: 多例
@PostConstruct: 创建对象的时候,调用指定的方法
@PreDestroy: 销毁对象的时候调用指定的方法
*/
//@Component //组件
@Service("us")
@Scope("prototype")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDaoImpl;
public void add() {
System.out.println("调用了UserServiceImpl的add方法~!");
userDaoImpl.add();
}
//=========================================
//对象创建完毕,就执行这个方法
@PostConstruct
public void init(){
System.out.println("调用了UserServiceImpl的init方法~!");
}
//对象销毁的时候,就执行这个方法! 只有单例才会走这个方法
@PreDestroy
public void destroy(){
System.out.println("调用了UserServiceImpl的destroy方法~!");
}
}
2.4 依赖注入的注解【DI】
注解 | 说明 |
---|---|
@Autowired |
相当于property标签的ref 注入对象 |
@Qualifier |
结合@Autowired 使用,用于根据名称(标识符)注入依赖 |
@Resource |
相当于@Autowired + @Qualifier |
@Value |
相当于property标签的value ,注入普通 的属性 |
注入bean对象
@Autowired
:用于byType(按照类型来找对象)注入bean对象,按照依赖(属性)的类型,从Spring容器中查找要注入的bean对象-
- 如果找到一个,直接注入
-
- 如果找到多个,则以变量名为id,查找bean对象并注入
- 如果找不到,抛异常
- 如果找到多个,则以变量名为id,查找bean对象并注入
-
@Qualifier
:是按id注入,但需要和@Autowired
配合使用。@Resource
:(是jdk提供的)用于注入bean对象(byName注入),相当于@Autowired + @Qualifier
绝大多数情况下,只要使用
@Autowired
注解注入即可使用注解注入时,不需要set方法了
- UserDao
package com.itehima.dao;
public interface UserDao {
void add();
}
- UserDao实现
package com.itheima.dao.impl;
import com.itheima.dao.UserDao;
import org.springframework.stereotype.Component;
@Repository
public class UserDaoImpl implements UserDao {
public void add() {
System.out.println("调用了UserdaoImpl的add方法~!~");
}
}
- UserService
package com.itehima.service;
public interface UserService {
void add();
}
- UserService实现
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
@Service("us02")
public class UserServiceImpl02 implements UserService {
/*
@Autowired :自动注入
1. 按照的属性类型去找spring的工厂里面找对象,找到对象(只有一个)就注入进来
2. 如果在spring的工厂里面,有多个对象满足这种类型,
2.1 会拿着属性的名字当成id ,再去找对象,如果有匹配的就注入。
2.2 如果还没有找到匹配的,就会报错!
@Qualifier : 用于给@Autowired指定id值。告诉它用这个id去找对象注入
@Resource : 等价于@Autowired + @Qualifier
1. 按照id去找对象注入
以上三个注解,就是用注入对象的,最常用的是@Autowired,而且95%的场景下,是只有一种实现类\
@Value : 用来注入普通的数据
1. 主要是用来注入properties里面的数据
2. @Value("${properties里面的KEY}") , 要先导入进来properties的文件才可以注入
*/
@Autowired
private UserDao userDaoImpl02;
@Autowired
@Qualifier("userDaoImpl02")
private UserDao abc;
@Resource(name = "userDaoImpl02")
private UserDao cba;
@Value("深圳")
private String address;
public void add() {
System.out.println("调用了UserServiceImpl02的add方法~!" + address);
//userDaoImpl02.add();
//abc.add();
cba.add();
}
}
- 测试
package com.itheima.test;
import com.itheima.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestUserServiceImpl02 {
@Test
public void testAdd(){
//1. 创建工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//2. 问工厂要对象
UserService us = (UserService) context.getBean("us02");
//3. 调用方法
us.add();
}
}
注入普通值
@Value
:注入简单类型值,例如:基本数据类型和String
@Service
public class UserServiceImpl02 implements UserService{
@Value("深圳")
private String address;
//...
}
小结
- 在xml里要开启组件扫描
<context:component-scan base-package="com.itheima"/>
- 声明bean的注解(注册bean的注解) | IOC的注解
@Component("bean的名称")
, 括号里面bean的名称就是id 值, 可以用在任何类上,注册bean对象@Controller("bean名称"), @Service("bean名称"), @Repository("bean名称")
,分别用于web层、service层、dao层的类上
- 配置bean的注解
- 如果要给一个bean设置作用范围:在bean上加注解
@Scope("singleton/prototype")
- 如果要给一个bean设置一个初始化方法:就在方法上加注解
@PostConstruct
- 如果要给一个bean设置一个销毁方法:就在方法上加注解
@PreDestroy
- 如果要给一个bean设置作用范围:在bean上加注解
- 依赖注入的注解
@Autowired
:byType注入,直接加在依赖项那个成员变量上- Spring会根据类型,从容器里查找bean并注入进来
- 如果只找到一
- 合的就会报错
@Autowired + @Qualifier("要注入的bean的名称")
: 这种组合一般不怎么用,因为比较麻烦。@Resource(name="要注入的bean的名称")
:byName注入@Value("要注入的简单值")
:用于注入简单值@Value("${properties里的key}")
:把properties里指定key的值注入进来。前提条件是必须已经引入了properties文件
二、注解方式CURD练习
需求描述
- 使用注解开发帐号信息的CURD功能
需求分析
- 使用注解代替某些XML配置,能够代替的有:service层和dao层里面的类可以使用注解来托管。
- dao层bean对象,可以在类上增加注解
@Repository
- service层bean对象,可以在类上增加注解
@Service
- Service层依赖于dao层,可以使用注解注入依赖
@AutoWired
- dao层bean对象,可以在类上增加注解
- 不能使用注解代替,仍然要使用XML配置的的有:
- QueryRunner的bean对象,是DBUtils工具包里提供的类,我们不能给它的源码上增加注解
- 连接池的bean对象,是c3p0工具包里提供的类ComboPooledDataSource,我们不能修改源码增加注解
需求实现
- 导入依赖
<dependencies>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- c3p0连接池(也可以用其它连接池) -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- DBUtils工具包 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
JavaBean
package com.itheima.bean;
import lombok.Data;
@Data
public class Account {
private int id;
private String name;
private int money;
}
dao层代码
AccountDao
接口
package com.itheima.dao;
import com.itheima.bean.Account;
import java.util.List;
public interface AccountDao {
int add(Account account) throws Exception;
int delete(int id) throws Exception;
int update(Account account) throws Exception;
Account findById(int id) throws Exception;
List<Account> findAll() throws Exception;
}
AccountDaoImpl
实现类
package com.itheima.dao.impl;
import com.itheima.bean.Account;
import com.itheima.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
/*
1. 把这个类交给spring管理
2. 让spring注入QueryRunner对象
*/
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner runner;
public int add(Account account) throws SQLException {
String sql = "insert into t_account values(null , ? , ?)";
return runner.update(sql , account.getName(),account.getMoney());
}
public int delete(int id) throws SQLException {
String sql = "delete from t_account where id = ?";
return runner.update(sql , id);
}
public int update(Account account) throws SQLException {
String sql = "update t_account set name = ?, money = ? where id = ?";
return runner.update(sql , account.getName() ,account.getMoney() ,account.getId());
}
public Account findById(int id) throws SQLException {
String sql = "select * from t_account where id = ?";
return runner.query(sql , new BeanHandler<Account>( Account.class), id);
}
public List<Account> findAll() throws SQLException {
String sql = "select * from t_account ";
return runner.query(sql , new BeanListHandler<Account>( Account.class));
}
}
service层代码
AccountService
接口
package com.itheima.service;
import com.itheima.bean.Account;
import java.util.List;
public interface AccountService {
int add(Account account) throws Exception;
int delete(int id) throws Exception;
int update(Account account) throws Exception;
Account findById(int id) throws Exception;
List<Account> findAll() throws Exception;
}
AccountServiceImpl
接口
package com.itheima.service.impl;
import com.itheima.bean.Account;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
import java.util.List;
/*
1. 把这个类交给spring管理
打注解: @Service
2. 让spring把dao的对象给注入进来
@Autowired
private AccountDao dao;
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao dao;
public int add(Account account) throws SQLException {
return dao.add(account);
}
public int delete(int id) throws SQLException {
return dao.delete(id);
}
public int update(Account account) throws SQLException {
return dao.update(account);
}
public Account findById(int id) throws SQLException {
return dao.findById(id);
}
public List<Account> findAll() throws SQLException {
return dao.findAll();
}
}
提供配置
- db.properties
db.driverClass=com.mysql.jdbc.Driver
db.jdbcUrl=jdbc:mysql://localhost:3306/day41_spring
# 如果是windows系统,那么使用${username} 那么取到的不是这里的root 而是计算机的用户名
# 如果是mac系统,那么使用${user} 那么取到的不是这里的root, 而是计算机的用户名!
db.user=root
db.password=root
- 创建
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"
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">
<!--1. 打开扫描的开关-->
<context:component-scan base-package="com.itheima"/>
<!--2. 把QueryRunner交给spring管理-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
<constructor-arg name="ds" ref="ds"/>
</bean>
<!--3. 把DataSource交给spring管理-->
<context:property-placeholder location="db.properties"/>
<bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${db.driverClass}"/>
<property name="jdbcUrl" value="${db.jdbcUrl}"/>
<property name="user" value="${db.user}"/>
<property name="password" value="${db.password}"/>
</bean>
</beans>
功能测试
package com.itheima.test;
import com.itheima.bean.Account;
import com.itheima.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.sql.SQLException;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAccountServiceImpl {
@Autowired
private AccountService as;
@Test
public void testAdd() throws SQLException {
Account a = new Account();
a.setName("zhangsan");
a.setMoney(10);
as.add(a);
}
@Test
public void testDelete() throws SQLException {
as.delete(5);
}
@Test
public void testUpdate() throws SQLException {
//1. 先查询
Account a = as.findById(3);
a.setMoney(66666);
//2. 再修改
as.update(a);
}
@Test
public void testFindAll() throws SQLException {
System.out.println(as.findAll());
}
}
小结
- 导入依赖
- 定义dao接口和service 接口 各自的实现
- 使用注解托管dao的实现类和service的实现类 @Repository@Service
- 不要忘记了在applicationContext.xml 打开开关
- 在applicationContext.xml里面托管QueryRunner,这样才能给Dao里面注入
- 在applicationContext.xml里面托管ComboPooledDataSource 这样才能给QueryRunner注入
三、纯注解开发IOC和DI
在上边的CURD练习中,仍然有部分配置需要使用applicationContext.xml
,那么能不能使用注解替换掉所有的xml呢?Spring提供了一些新注解,可以达到这个目标。
请注意:Spring提供的这部分新注解,并非为了完全代替掉XML,只是提供了另外一种配置方案
注解简介
注解 | 说明 |
---|---|
@Configuration |
被此注解标记的类,是配置类 等同于applicationContext.xml |
@ComponentScan |
用在配置类上,开启注解扫描。使用basePackage属性指定扫描的包 |
@PropertySource |
用在配置类上,加载properties文件。使用value属性指定properties文件路径 |
@Import |
用在配置类上,引入子配置类。用value属性指定子配置类的Class |
@Bean |
用在配置类的方法上,把返回值声明为一个bean。用name/value属性指定bean的id |
注解详解
- 导入依赖
<dependencies>
<!-- c3p0连接池(也可以用其它连接池) -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- DBUtils工具包 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
</dependencies>
1 @Configuration
配置类
@Configuration
把一个Java类声明为核心配置类- 加上Java类上,这个Java类就成为了Spring的核心配置类,用于代替
applicationContext.xml
- 是
@Component
的衍生注解,所以:核心配置类也是bean,也会被spring管理起来,当然里边也可以注入依赖
- 加上Java类上,这个Java类就成为了Spring的核心配置类,用于代替
/*
@Configuration:
1. 表示这个类是一个核心配置类
2. 它的作用等价于以前的applicationContext.xml
3. @Configuration 是从@Component注解衍生出来的,所以这个类也会被spring管理起来(创建对象)
*/
@Configuration
public class AppConfig {
}
2 配置类上的注解
-
@ComponentScan
配置组件注解扫描basePackages
或者value
属性:指定扫描的基本包- 等同于替代了applicationContext.xml里面的这句话
<!--1. 打开包的扫描开关--> <context:component-scan base-package="com.itheima"/>
-
@PropertySource
用于加载properties文件value
属性:指定propertis文件的路径,从类加载路径里加载- 等同于替代了applicationContext.xml里面的这句话
<!--导入properties文件--> <context:property-placeholder location="classpath:db.properties"/>
-
@Import
用于导入其它配置类- Spring允许提供多个配置类(模块化配置),在核心配置类里加载其它配置类
- 相当于
xml
中的<import resource="模块化xml文件路径"/>
标签
-
核心配置类
package com.itheima.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
/*
@Configuration:
1. 表示这个类是一个核心配置类
2. 它的作用等价于以前的applicationContext.xml
3. @Configuration 是从@Component注解衍生出来的,所以这个类也会被spring管理起来(创建对象)
@ComponentScan :
1. 用来指定扫描包,等价于 <context:component-scan base-package=""/>
@PropertyScource:
1. 用来导入外部的properties文件,等价于<context:property-placeholder location=""/>
2. 导入这个properties文件的时候,如果有错(1. 找不到这个文件,2.这个文件无法打开,)可以在
名字的前面加上 classpath:
3. 要想把properties里面的内容注入进来,就需要定义好对应的属性,然后使用@Value来注入值
@Value("${KEY}")
@Import:
1. 可以用来导入子配置类
*/
@Configuration
//@ComponentScan("com.itheima")
//@PropertySource("db.properties")
@Import(AppConfig01.class) //导入子配置类
public class AppConfig {
@Value("${db.driverClass}")
private String driverClass;
@Value("${db.jdbcUrl}")
private String jdbcUrl;
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
public void show(){
System.out.println("driverClass = " + driverClass);
System.out.println("jdbcUrl = " + jdbcUrl);
System.out.println("user = " + user);
System.out.println("password = " + password);
}
//==============================以下代码是说明@Bean=============================
/*
@Bean
1. 打在方法身上,spring会来调用这个方法
2. spring会把这个方法的返回值管理起来,丢到spring的工厂里面去
3. 可以在@Bean的value属性里面设置对象的id,如果不设置,那么将会使用方法的名字作为id值
*/
//1. 把QueryRunner对象交给spring管理,使用方法名字作为id值
@Bean
public QueryRunner runner01(){
return new QueryRunner();
}
// 2. 把QueryRunner对象交给spring管理,使用属性来指定id值
@Bean("run02")
public QueryRunner runner02(){
return new QueryRunner();
}
/*
3.使用@Bean注解标记的方法,方法内部需要用到spring工厂(容器)里面的某一个对象,怎么办?
3.1 此时可以在方法的参数上,写上想要的对象参数即可
a. 其实它就是在方法参数的前面,隐藏着一个注解: @Autowired
3.2 但是也要考虑出现极端的情况:如果在spring的容器里面,有多个对象满足这个参数的类型,咋办?
a. 搭配使用@Qualifier ,指定id值。
b. 投机取巧,把方法的参数名字,写成id的名字即可
*/
@Bean
public QueryRunner runner03(@Qualifier("dataSource") DataSource ds){
System.out.println("ds===" + ds);
return new QueryRunner(ds);
}
@Bean
public QueryRunner runner04(DataSource dataSource2){
System.out.println("dataSource2===" + dataSource2);
return new QueryRunner(dataSource2);
}
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
@Bean
public DataSource dataSource2() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
}
- 子配置类
package com.itheima.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.PropertySource;
@ComponentScan("com.itheima")
@PropertySource("db.properties")
public class AppConfig01 {
}
3 @Bean
声明bean
1) @Bean
定义bean
-
@Bean
注解:方法级别的注解-
作用: 方法会由spring调用,并且把方法返回值声明成为一个bean,作用相当于
<bean>
标签 , 这个方法的返回值将会被Spring管理起来。 -
@Bean注解可以写在方法上,这些方法可以写在核心配置类里面,也可以写在其他的组件类里面,但是一般会写在核心配置类里面。
-
-
@Bean
注解的属性:value
属性:bean的id。如果不设置,那么方法名就是bean的id
@Configuration
@ComponentScan("com.itheima")
public class AppConfig {
...
//==============================以下代码是说明@Bean=============================
/*
@Bean
1. 打在方法身上,spring会来调用这个方法
2. spring会把这个方法的返回值管理起来,丢到spring的工厂里面去
3. 可以在@Bean的value属性里面设置对象的id,如果不设置,那么将会使用方法的名字作为id值
*/
//1. 把QueryRunner对象交给spring管理,使用方法名字作为id值
@Bean
public QueryRunner runner01(){
return new QueryRunner();
}
// 2. 把QueryRunner对象交给spring管理,使用属性来指定id值
@Bean("run02")
public QueryRunner runner02(){
return new QueryRunner();
}
/*
3.使用@Bean注解标记的方法,方法内部需要用到spring工厂(容器)里面的某一个对象,怎么办?
3.1 此时可以在方法的参数上,写上想要的对象参数即可
a. 其实它就是在方法参数的前面,隐藏着一个注解: @Autowired
3.2 但是也要考虑出现极端的情况:如果在spring的容器里面,有多个对象满足这个参数的类型,咋办?
a. 搭配使用@Qualifier ,指定id值。
b. 投机取巧,把方法的参数名字,写成id的名字即可
*/
@Bean
public QueryRunner runner03(@Qualifier("dataSource") DataSource ds){
System.out.println("ds===" + ds);
return new QueryRunner(ds);
}
@Bean
public QueryRunner runner04(DataSource dataSource2){
System.out.println("dataSource2===" + dataSource2);
return new QueryRunner(dataSource2);
}
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
@Bean
public DataSource dataSource2() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
}
2) @Bean
的依赖注入
@Bean
注解的方法可以有任意参数,这些参数即是bean所需要的依赖,默认采用byType方式注入- 可以在方法参数上增加注解
@Qualifier
,用于byName注入
3) 完整代码
package com.itheima.config;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
/*
@Configuration:
1. 表示这个类是一个核心配置类
2. 它的作用等价于以前的applicationContext.xml
3. @Configuration 是从@Component注解衍生出来的,所以这个类也会被spring管理起来(创建对象)
@ComponentScan :
1. 用来指定扫描包,等价于 <context:component-scan base-package=""/>
@PropertyScource:
1. 用来导入外部的properties文件,等价于<context:property-placeholder location=""/>
2. 导入这个properties文件的时候,如果有错(1. 找不到这个文件,2.这个文件无法打开,)可以在
名字的前面加上 classpath:
3. 要想把properties里面的内容注入进来,就需要定义好对应的属性,然后使用@Value来注入值
@Value("${KEY}")
@Import:
1. 可以用来导入子配置类
*/
@Configuration
//@ComponentScan("com.itheima")
//@PropertySource("db.properties")
@Import(AppConfig01.class) //导入子配置类
public class AppConfig {
@Value("${db.driverClass}")
private String driverClass;
@Value("${db.jdbcUrl}")
private String jdbcUrl;
@Value("${db.user}")
private String user;
@Value("${db.password}")
private String password;
public void show(){
System.out.println("driverClass = " + driverClass);
System.out.println("jdbcUrl = " + jdbcUrl);
System.out.println("user = " + user);
System.out.println("password = " + password);
}
//==============================以下代码是说明@Bean=============================
/*
@Bean
1. 打在方法身上,spring会来调用这个方法
2. spring会把这个方法的返回值管理起来,丢到spring的工厂里面去
3. 可以在@Bean的value属性里面设置对象的id,如果不设置,那么将会使用方法的名字作为id值
*/
//1. 把QueryRunner对象交给spring管理,使用方法名字作为id值
@Bean
public QueryRunner runner01(){
return new QueryRunner();
}
// 2. 把QueryRunner对象交给spring管理,使用属性来指定id值
@Bean("run02")
public QueryRunner runner02(){
return new QueryRunner();
}
/*
3.使用@Bean注解标记的方法,方法内部需要用到spring工厂(容器)里面的某一个对象,怎么办?
3.1 此时可以在方法的参数上,写上想要的对象参数即可
a. 其实它就是在方法参数的前面,隐藏着一个注解: @Autowired
3.2 但是也要考虑出现极端的情况:如果在spring的容器里面,有多个对象满足这个参数的类型,咋办?
a. 搭配使用@Qualifier ,指定id值。
b. 投机取巧,把方法的参数名字,写成id的名字即可
*/
@Bean
public QueryRunner runner03(@Qualifier("dataSource") DataSource ds){
System.out.println("ds===" + ds);
return new QueryRunner(ds);
}
@Bean
public QueryRunner runner04(DataSource dataSource2){
System.out.println("dataSource2===" + dataSource2);
return new QueryRunner(dataSource2);
}
@Bean
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
@Bean
public DataSource dataSource2() throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
}
小结
-
配置类上要加注解
@Configuration
变成核心配置类,主要是用来替代applicationContext.xml -
要开启组件扫描,在配置类上
@ComponentScan("com.itheima")
-
如果要把jar包里的类注册成bean:
- 在配置类里加方法,方法上加@Bean,会把方法返回值注册bean对象
-
如果要引入外部的properties文件,在配置类上加
@PropertySource("classpath:xxx.properties")
-
引入模块配置类,在配置类上使用
@Import(子配置类.class)
-
@Bean ,如果期望让spring来管理某个方法的返回值(注意: 这个返回值大多数都是对象,很少是一个普通的数据,比如:数字、字符串...)
四、纯注解方式CURD练习
需求描述
- 使用Spring的新注解,代替CURD练习里,
applicationContext.xml
的所有配置
需求实现
- 导入依赖
<dependencies>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- c3p0连接池(也可以用其它连接池) -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!-- DBUtils工具包 -->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
- account
package com.itheima.bean;
import lombok.Data;
@Data
public class Account {
private int id;
private String name;
private int money;
}
- dao接口
package com.itheima.dao;
import com.itheima.bean.Account;
import java.util.List;
public interface AccountDao {
int add(Account account) throws Exception;
int delete(int id) throws Exception;
int update(Account account) throws Exception;
Account findById(int id) throws Exception;
List<Account> findAll() throws Exception;
}
- dao实现
package com.itheima.dao.impl;
import com.itheima.bean.Account;
import com.itheima.dao.AccountDao;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import java.sql.SQLException;
import java.util.List;
/*
1. 把这个类交给spring管理
2. 让spring注入QueryRunner对象
*/
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner runner;
public int add(Account account) throws SQLException {
String sql = "insert into t_account values(null , ? , ?)";
return runner.update(sql , account.getName(),account.getMoney());
}
public int delete(int id) throws SQLException {
String sql = "delete from t_account where id = ?";
return runner.update(sql , id);
}
public int update(Account account) throws SQLException {
String sql = "update t_account set name = ?, money = ? where id = ?";
return runner.update(sql , account.getName() ,account.getMoney() ,account.getId());
}
public Account findById(int id) throws SQLException {
String sql = "select * from t_account where id = ?";
return runner.query(sql , new BeanHandler<Account>( Account.class), id);
}
public List<Account> findAll() throws SQLException {
String sql = "select * from t_account ";
return runner.query(sql , new BeanListHandler<Account>( Account.class));
}
}
- service接口
package com.itheima.service;
import com.itheima.bean.Account;
import java.util.List;
public interface AccountService {
int add(Account account) throws Exception;
int delete(int id) throws Exception;
int update(Account account) throws Exception;
Account findById(int id) throws Exception;
List<Account> findAll() throws Exception;
}
- service实现
package com.itheima.service.impl;
import com.itheima.bean.Account;
import com.itheima.dao.AccountDao;
import com.itheima.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.sql.SQLException;
import java.util.List;
/*
1. 把这个类交给spring管理
打注解: @Service
2. 让spring把dao的对象给注入进来
@Autowired
private AccountDao dao;
*/
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao dao;
public int add(Account account) throws SQLException {
return dao.add(account);
}
public int delete(int id) throws SQLException {
return dao.delete(id);
}
public int update(Account account) throws SQLException {
return dao.update(account);
}
public Account findById(int id) throws SQLException {
return dao.findById(id);
}
public List<Account> findAll() throws SQLException {
return dao.findAll();
}
}
- 提供jdbc配置文件:db.properties
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://localhost:3306/day41_spring
user=root
password=root
- 提供核心配置类:AppConfig
package com.itheima.config;
/*
这是一个核心配置类
*/
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
@Configuration //这是核心配置类
@ComponentScan("com.itheima") //扫描组件
@PropertySource("db.properties")
public class AppConfig {
@Value("${driverClass}")
private String driverClass;
@Value("${jdbcUrl}")
private String jdbcUrl;
@Value("${user}")
private String user;
@Value("${password}")
private String password;
//把QueryRunner交给spring管理
@Bean
public QueryRunner runner(DataSource ds){
return new QueryRunner(ds);
}
//把DataSource交给spring管理
@Bean
public DataSource ds () throws PropertyVetoException {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driverClass);
ds.setJdbcUrl(jdbcUrl);
ds.setUser(user);
ds.setPassword(password);
return ds;
}
}
功能测试
package com.itheima.test;
import com.itheima.bean.Account;
import com.itheima.config.AppConfig;
import com.itheima.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.sql.SQLException;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestAccountServiceImpl {
@Autowired
private AccountService as;
@Test
public void testAdd() throws SQLException {
Account a = new Account();
a.setName("zhagnsan");
a.setMoney(10);
as.add(a);
}
@Test
public void testDelete() throws SQLException {
as.delete(6);
}
@Test
public void testUpdate() throws SQLException {
Account a = as.findById(3);
a.setMoney(999);
as.update(a);
}
@Test
public void testFindAll() throws SQLException {
System.out.println(as.findAll());
}
}
五、注解深入【拓展了解】
准备环境
1. 创建Module,引入依赖
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- Spring整合Junit -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- snakeyaml,用于解析yaml文件的工具包 -->
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- c3p0连接池 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
2. 创建核心配置类
- 在
com.itheima
包里创建核心配置类AppConfig
@Configuration
@ComponentScan("com.itheima")
public class AppConfig {
}
3.创建Service
- 接口
package com.itheima.service;
public interface UserService {
void add();
}
- 实现
package com.itheima.service.impl;
import com.itheima.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("调用了UserServiceImpl的add方法~!~!");
}
}
4.创建单元测试类
package com.itheima.test;
import com.itheima.config.AppConfig;
import com.itheima.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestUserServiceImpl {
//把工厂给注入进来
@Autowired
private ApplicationContext context;
@Test
public void testBeanNames(){
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}
@ComponentScan
这个注解的作用就是用来扫描指定包下的所有类。如果哪个类身上打了注解(@Controller | @Service | @Repository | @Component),就被spring给管理起来。默认情况下Spring管理这些对象的时候,他们的id名字就是类的名字,但是第一个字母小写。我们是否可用修改这种命名策略呢?
BeanName生成策略
说明
-
默认的BeanName生成策略:
- 如果注册bean时指定了id/name,以配置的id/name作为bean的名称
- 如果没有指定id/name,则以类名首字母小写作为bean的名称
-
在模块化开发中,多个模块共同组成一个工程。
- 可能多个模块中,有同名称的类,按照默认的BeanName生成策略,会导致名称冲突。
- 这个时候可以自定义beanname生成策略解决问题
-
@ComponentScan
的nameGenerator
属性,可以配置自定义的BeanName生成策略,步骤:
-
创建Java类,实现
BeanNameGenerator
接口,定义BeanName生成策略 -
在注解
@ComponentScan
中,使用nameGenerator属性
指定生成策略即可
示例
- 创建Java类,实现
BeanNameGenerator
接口,定义BeanName生成策略
package com.itheima.demo1_componentscan;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
/*
自定义的id生成策略
1. id的生成靠的是generateBeanName的返回值
2. 这个方法的返回值是什么,id就是什么。
*/
public class MyBeanNameGenerator implements BeanNameGenerator {
/**
* 用来创建id值
* @param beanDefinition 表示(包装)现在正在扫描(处理)的类
* @param beanDefinitionRegistry
* @return 对象的id值
*/
public String generateBeanName(BeanDefinition beanDefinition, BeanDefinitionRegistry beanDefinitionRegistry) {
//1 得到现在正在扫描到的这个类的全路径地址:
String className = beanDefinition.getBeanClassName();
System.out.println("className = " + className);
//2. 返回的是全路径。
return className;
}
}
- 在注解
@ComponentScan
中,使用nameGenerator
指定生成策略
@Configuration
@ComponentScan(value = "com.itheima" , nameGenerator = MyBeanNameGenerator.class) //扫描包 & 命名策略
public class AppConfig {
}
- 执行上面的单元测试
扫描规则过滤器
说明
@ComponentScan
默认的扫描规则:- 扫描指定包里的
@Component
及衍生注解(@Controller
,@Service
,@Repository
)配置的bean
- 扫描指定包里的
@ComponentScan
注解也可以自定义扫描规则,来包含或排除指定的bean。步骤:- 创建Java类,实现
TypeFilter
接口,重写match
方法- 方法返回boolean。true表示匹配过滤规则;false表示不匹配过滤规则
- 使用
@ComponentScan
注解的属性,配置过滤规则:includeFilter
:用于包含指定TypeFilter过滤的类,符合过滤规则的类将被扫描excludeFilter
:用于排除指定TypeFilter过滤的类,符合过滤规则的类将被排除
- 创建Java类,实现
示例1-根据注解过滤
哪个类身上有指定的注解,那么就忽略它 , 不扫描。这是按照注解的名字来忽略的。
@Configuration // 这是核心配置类
@ComponentScan(value = "com.itheima" , nameGenerator = MyBeanNameGenerator.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION , classes = Service.class)
public class AppConfig {
}
示例2-根据指定类过滤
@Configuration // 这是核心配置类
@ComponentScan(value = "com.itheima" , nameGenerator = MyBeanNameGenerator.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE , classes = UserServiceImpl.class) // 按照类不扫描
public class AppConfig {
}
示例3-自定义过滤
- 编写过滤器,实现
TypeFilter
接口,重写match
方法
package com.itheima.demo1_componentscan;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter;
import java.io.IOException;
/*
这是自定扫描(包含|排除)的规则
*/
public class MyTypeFilter implements TypeFilter {
/**
* 用于设置什么样的规则就排除|包含 类
* @param metadataReader
* @param metadataReaderFactory
* @return true: 即表示匹配规则, false: 即表示不匹配规则。
* @throws IOException
*/
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//1. 得到现在正要被检查的类的元数据
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//2. 获取现在的类的全路径名字
String className = classMetadata.getClassName();
//如果哪个类的名字包含了User,那么就返回true
return className.contains("User");
}
}
- 使用注解
@ComponentScan
,配置过滤规则
@ComponentScan(value = "com.itheima" , nameGenerator = MyBeanNameGenerator.class,
excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM , classes = MyTypeFilter.class) // 按照自定义的规则来忽略类不扫描!
)
public class AppConfig {
}
- 执行单元测试,看看打印的结果。
@PropertySource
yml
配置文件介绍
- 大家以前学习过的常用配置文件有
xml
和properties
两种格式,但是这两种都有一些不足:properties
:- 优点:键值对的格式,简单易读
- 缺点:不方便表示复杂的层级
xml
:- 优点:层次结构清晰
- 缺点:配置和解析语法复杂
- springboot采用了一种新的配置文件:
yaml
(或yml
),它综合了xml
和properties
的优点。yaml are't markup language
=> yaml- 使用空格表示层次关系:相同空格的配置项属于同一级
- 配置格式是
key:空格value
,键值对之间用:空格
表示
yaml
文件示例:
jdbc:
driver: com.mysql.jdbc.Driver # 注意:英文冒号后边必须有一个空格
url: jdbc:mysql:///spring
username: root
password: root
jedis:
host: localhost
port: 6379
使用@PropertySource
加载yml
说明
@PropertySource
可以使用factory
属性,配置PropertySourceFactory
,用于自定义配置文件的解析- 步骤:
- 创建
yaml
文件:application.yml
- 导入依赖
snakeyaml
,它提供了解析yml文件的功能 - 创建Java类,实现
PropertySourceFactory
接口,重写createPropertySource
方法 - 使用
@PropertySource
注解,配置工厂类
- 创建
示例
- 在
resources
目录里创建yaml
文件:application.yml
jdbc:
driver: com.mysql.jdbc.Driver # 注意:英文冒号后边必须有一个空格
url: jdbc:mysql:///spring
username: root
password: root
jedis:
host: localhost
port: 6379
- 在
pom.xml
增加导入依赖snakeyaml
,它提供了解析yml文件的功能
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
</dependency>
- 创建Java类,实现
PropertySourceFactory
接口,重写createPropertySource
方法
public class YamlSourceFactory implements PropertySourceFactory {
/**
* 解析yaml配置文件
* @param name 名称
* @param resource 配置文件EncodedResource对象
* @return PropertySource
*/
@Override
public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
//1. 创建yaml解析的工厂
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
//2. 设置要解析的资源内容
factoryBean.setResources(resource.getResource());
//3. 把资源文件解析成Properties对象
Properties properties = factoryBean.getObject();
//4. 把properties封装成PropertySource对象并返回
return new PropertiesPropertySource("application", properties);
}
}
- 使用
@PropertySource
注解,配置工厂类
@Configuration("appConfig")
@PropertySource(value = "application.yml" , factory = YamlSourceFactory.class) //导入yml文件
public class AppConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
public void show(){
System.out.println("driver = " + driver);
System.out.println("url = " + url);
System.out.println("username = " + username);
System.out.println("password = " + password);
}
}
- 测试
package com.itheima.test;
import com.itheima.config.AppConfig;
import com.itheima.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestAppConfig {
@Autowired
private AppConfig config;
@Test
public void testShow(){
config.show();
}
}
@Import
注册bean的方式
如果要注解方式配置一个bean,可以如下方式:
- 在类上使用
@Component
,@Controller
,@Service
,@Repository
:只能用于自己编写的类上,jar包里的类不能使用(比如ComboPooledDataSource) - 在方法上使用
@Bean
:把方法返回值配置注册成bean到IoC容器,通常用于注册第三方jar里的bean - 在核心配置类上使用
@Import
:@Import(类名.class)
,注册的bean的id是全限定类名@Import(自定义ImportSelector.class)
:把自定义ImportSelector返回的类名数组,全部注册bean@Import(自定义ImportBeanDefinitionRegister.class)
:在自定义ImportBeanDefinitionRegister里手动注册bean
ImportSelector导入器
示例1-直接导入注册bean
使用@import来到一个类
@Configuration
@ComponentScan("com.itheima")
@Import(Teacher.class)
public class AppConfig {
}
示例2-使用ImportSelector注册bean
- 导入器
package com.itheima.demo3_import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
//自定义导入选择器
public class MyImportSelector implements ImportSelector {
/**
* 用于控制到底导入那个类到spring的容器|工厂里面
* @param annotationMetadata
* @return 数组,数组里面包含类的全路径地址。
*/
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
return new String[]{Teacher.class.getName() , Student.class.getName()};
}
}
- 示例
@Configuration
@ComponentScan("com.itheima")
@Import(MyImportSelector.class)
public class AppConfig {
...
}
示例3-ImportSelector的高级使用
说明
- springboot框架里有大量的
@EnableXXX
注解,底层就是使用了ImportSelector解决了模块化开发中,如何启动某一模块功能的问题 - 例如:
- 我们开发了一个工程,要引入其它的模块,并启动这个模块的功能:把这个模块的bean进行扫描装载
- 步骤:
- 创建一个Module:module_a
- 在包
com.a
里创建几个Bean - 创建核心配置文件AConfig,扫描
com.a
- 定义一个ImportSelector:
AImportSelector
,导入AConfig
类 - 定义一个注解
@EnableModuleA
- 在包
- 在我们的Module的核心配置文件
AppConfig
上增加注解:@EnableModuleA
- 引入module_a的坐标
- 测试能否获取到Module a里的bean
- 创建一个Module:module_a
第一步:创建新Module:module_a
<!-- module_a的坐标 -->
<groupId>com.itheima</groupId>
<artifactId>spring_module_a</artifactId>
<version>1.0-SNAPSHOT</version>
-
在包
com.a.beans
里创建类DemoBeanA
@Component public class DemoBeanA { }
-
创建核心配置类
AConfig
@Configuration
@ComponentScan("com.a")
public class AConfig {
}
- 创建导入器:创建Java类,实现
ImportSelector
接口,重写selectImports
方法
public class AImportSelector implements ImportSelector {
/**
* @param importingClassMetadata。被@Import注解标注的类上所有注解的信息
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AConfig.class.getName()};
}
}
- 定义注解
@EnableModuleA
@Import(AImportSelector.class)
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableModuleA {
}
第二步:引入module_a,启动模块a
- 在我们自己的工程pom.xml里增加依赖
<dependency>
<groupId>com.itheima</groupId>
<artifactId>spring_module_a</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 在我们自己的工程核心配置类上,使用注解@EnableModuleA启动模块a
@Configuration
@EnableModuleA
public class AppConfig {
}
- 在我们自己的工程里测试
@Test
public void testBean(){
//在我们自己的工程里,可以获取到module_a里的bean
DemoBeanA demoBeanA = app.getBean(DemoBeanA.class);
System.out.println(demoBeanA);
}
ImportBeanDefinitionRegister注册器
说明
-
ImportBeanDefinitionRegister
提供了更灵活的注册bean的方式 -
AOP里的
@EnableAspectJAutoProxy
就使用了这种注册器,用于注册不同类型的代理对象 -
步骤:
-
创建注册器:
创建Java类,实现
ImportBeanDefinitionRegister
接口,重写registerBeanDefinitions
方法 -
在核心配置类上,使用
@Import
配置注册器
-
示例
- 创建类
com.other.Other
public class Other {
public void show(){
System.out.println("Other.show....");
}
}
- 创建注册器
public class CustomImportBeanDefinitionRegister implements ImportBeanDefinitionRegistrar {
/**
* @param importingClassMetadata 当前类的注解信息
* @param registry 用于注册bean的注册器
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//获取bean定义信息
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition("com.other.Other").getBeanDefinition();
//注册bean,方法参数:bean的id,bean的定义信息
registry.registerBeanDefinition("other", beanDefinition);
}
}
- 在核心配置类上,使用
@Import
配置注册器
@Configuration
@Import({CustomImportBeanDefinitionRegister.class})
public class AppConfig {
}
- 测试
@Test
public void testImportRegister(){
//获取扫描范围外,使用ImportBeanDefinitionRegister注册的bean
Other other = app.getBean("other",Other.class);
other.showOther();
}
@Conditional (条件)
说明
@Conditional
一般是搭配@Bean注解使用,用于表示设定一个条件,如果条件满足,spring就会管理方法的返回值!- 符合Condition条件的,Spring会生成bean对象 存储容器中
- 不符合Condition条件的,不会生成bean对象
- 示例:
- 有一个类Person(姓名name,年龄age)
- 如果当前操作系统是Linux:就创建Person(linus, 62)对象,并注册bean
- 如果当前操作系统是Windows:就创建Person(BillGates, 67)对象,并注册bean
- 步骤
- 创建Person类
- 创建两个Java类,都实现
Condition
接口:- WindowsCondition:如果当前操作系统是Windows,就返回true
- LinuxCondition:如果当前操作系统是Linux,就返回true
- 在核心配置类里创建两个bean
- 一个bean名称为bill,加上
@Conditional(WindowsCondition.class)
- 一个bean名称为linus,加上
@Conditional(LinuxCondition.class)
- 一个bean名称为bill,加上
示例
- 创建Person类
package com.itheima.demo4_conditional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private int age;
}
- 创建两个Java类,实现
Condition
接口
package com.itheima.demo4_conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/*
判断当前的操作系统是不是windows
*/
public class WindowsCondition implements Condition {
/*
返回true: 即表明是windows操作系统
返回false : 即表示不是windows系统
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//1. 得到当前的环境对象
Environment environment = conditionContext.getEnvironment();
//2. 得到当前系统的名字
String osName = environment.getProperty("os.name");
return osName.contains("Windows");
}
}
package com.itheima.demo4_conditional;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/*
判断当前的操作系统是不是Linux
*/
public class LinuxCondition implements Condition {
/*
返回true: 即表明是Linux操作系统
返回false : 即表示不是Linux系统
*/
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//1. 得到当前的环境对象
Environment environment = conditionContext.getEnvironment();
//2. 得到当前系统的名字
String osName = environment.getProperty("os.name");
return osName.contains("Linux");
}
}
- 核心配置类
@Configuration
@ComponentScan("com.itheima")
public class AppConfig {
...
//=========================以下代码使用@Conditional来判断创建对象============================
@Bean
@Conditional(WindowsCondition.class)
public Person windows(){
System.out.println("创建了windows对象!~");
return new Person("比尔", 75);
}
@Bean
@Conditional(LinuxCondition.class)
public Person linux(){
System.out.println("创建了linux对象!~");
return new Person("林纳斯", 62);
}
...
}
- 测试
package com.itheima.test;
import com.itheima.config.AppConfig;
import com.itheima.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestUserServiceImpl {
//把工厂给注入进来
@Autowired
private ApplicationContext context;
@Test
public void testBeanNames(){
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}
执行一次单元测试方法之后,按照以下方式,可以通过JVM参数的方式,设置os.name的参数值。
设置之后,再次执行单元测试方法
Conditional的扩展注解
@Conditional
在springboot里应用非常多,以下列出了一些@Conditional
的扩展注解:
- @ConditionalOnBean:当容器中有指定Bean的条件下进行实例化。
- @ConditionalOnMissingBean:当容器里没有指定Bean的条件下进行实例化。
- @ConditionalOnClass:当classpath类路径下有指定类的条件下进行实例化。
- @ConditionalOnMissingClass:当类路径下没有指定类的条件下进行实例化。
- @ConditionalOnWebApplication:当项目是一个Web项目时进行实例化。
- @ConditionalOnNotWebApplication:当项目不是一个Web项目时进行实例化。
- @ConditionalOnProperty:当指定的属性有指定的值时进行实例化。
- @ConditionalOnExpression:基于SpEL表达式的条件判断。
- @ConditionalOnJava:当JVM版本为指定的版本范围时触发实例化。
- @ConditionalOnResource:当类路径下有指定的资源时触发实例化。
- @ConditionalOnJndi:在JNDI存在的条件下触发实例化。
- @ConditionalOnSingleCandidate:当指定的Bean在容器中只有一个,或者有多个但是指定了首选的Bean时触发实例化。
@Profile
说明
- 在开发中,我们编写的工程通常要部署不同的环境,比如:开发环境、测试环境、生产环境。不同环境的配置信息是不同的,比如:数据库配置信息;如果每次切换环境,都重新修改配置的话,会非常麻烦,且容易出错
- 针对这种情况,Spring提供了
@Profile
注解:可以根据不同环境配置不同的bean,激活不同的配置@Profile
注解的底层就是@Conditional
- 例如:
- 定义三个数据源:
- 开发环境一个
DataSource
,使用@Profile
配置环境名称为dev
- 测试环境一个
DataSource
,使用@Profile
配置环境名称为test
- 生产环境一个
DataSource
,使用@Profile
配置环境名称release
- 开发环境一个
- 在测试类上,使用
@ActiveProfiles
激活哪个环境,哪个环境的数据源会生效- 实际开发中有多种方式可以进行激活,这里演示一个单元测试类里是怎样激活的
- 定义三个数据源:
示例
- 在
pom.xml
中增加导入依赖mysql驱动
,c3p0
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- 在配置类里创建三个数据源,并配置
@Profile
@Configuration
public class AppConfig {
//生成三个数据源
@Bean
@Profile("dev")
public DataSource devDataSource() throws PropertyVetoException {
System.out.println("dev 开发环境的");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///devdb");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setMaxPoolSize(20);
return dataSource;
}
@Bean
@Profile("test")
public DataSource testDataSource() throws PropertyVetoException {
System.out.println("test 测试环境的");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///testdb");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setMaxPoolSize(20);
return dataSource;
}
@Bean
@Profile("release")
public DataSource releaseDataSource() throws PropertyVetoException {
System.out.println("release 生产环境的");
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql:///releasedb");
dataSource.setUser("root");
dataSource.setPassword("root");
dataSource.setMaxPoolSize(20);
return dataSource;
}
}
-
在测试类上,使用
@ActiveProfiles
激活哪个环境,哪个环境的数据源会生效或者使用JVM参数
-Dspring.profiles.active=dev
package com.itheima.test;
import com.itheima.config.AppConfig;
import com.itheima.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@ActiveProfiles("dev")
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AppConfig.class)
public class TestUserServiceImpl {
//把工厂给注入进来
@Autowired
private ApplicationContext context;
@Test
public void testBeanNames(){
String[] names = context.getBeanDefinitionNames();
for (String name : names) {
System.out.println("name = " + name);
}
}
}
六、动态代理复习
1. JDK的基于接口的动态代理
API介绍
Proxy
类
-
使用JDK的动态代理的要求:目标对象必须实现了接口。
-
相关类:JDK的类
java.lang.reflect.Proxy
,提供了生成代理对象的方法 -
生成代理对象的方法:
Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces, InvocationHandler h)
loader
:类加载器interfaces
:目标对象所实现的接口 字节码数组h
:用于写代理对象要做的事情,通常写成InvocationHandler
接口的匿名内部类,实现其invoke
方法
InvocationHandler
接口
- 接口只有一个方法:每次当代理对象被调用时,这个方法都会执行。在方法里通常写代理对象的行为
invoke(Object proxy, Method method, Object[] args)
- 方法的参数:
Object proxy
:最终生成的代理对象Method method
:用户在调用代理对象时,所执行的方法对象Object[] args
:用户在调用代理对象,执行方法时,所传递的实参
- 方法的返回值:
- 当用户调用的代理对象的方法后,得到的返回值
使用示例
有目标类(待增强的类)
- 导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.2.RELEASE</version>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
- 接口
package com.itheima.proxy.jdk;
public interface Star {
void sing(String name);
void dance(String name);
}
- 实现类:
package com.itheima.proxy.jdk;
public class SuperStar implements Star {
public void sing(String name) {
System.out.println("明星在唱歌:" + name);
}
public void dance(String name) {
System.out.println("明星在跳舞:" + name);
}
}
有通知类(用于进行功能增强的)
主要是用来做增强的。
package com.itheima.proxy.cglib;
//增强出来的功能
public class StarAdvice {
public void before(){
System.out.println("先彩排一下~~");
}
public void after(){
System.out.println("收钱~~");
}
}
使用动态代理生成代理对象
package com.itheima.test;
import com.itheima.proxy.jdk.Star;
import com.itheima.proxy.jdk.StarAdvice;
import com.itheima.proxy.jdk.SuperStar;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class TestJDKProxyDemo {
@Test
public void test01(){
//1. 创建真实对象
final Star realObj = new SuperStar();
//2. 创建代理
Star proxyObj = (Star) Proxy.newProxyInstance(
realObj.getClass().getClassLoader(), //类加载器
realObj.getClass().getInterfaces(), //实现的接口
new InvocationHandler() { //调用处理器
/*
这个方法是代理对象和真实对象对话的桥梁。
1. 在外面用代理对象做任何事情,这个invoke方法都会被调用!
2. 在这个方法里面就得让真实对象去唱歌|跳舞!
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//System.out.println("invoke~!~");
//正向调用: 对象.方法(参数);
//realObj.sing("男人哭吧哭吧!");
//增强:
StarAdvice sa = new StarAdvice();
sa.before();
//反射调用: 方法对象.invoke(对象 , 参数);
Object result = method.invoke(realObj , args);
sa.after();
return result;
}
});
//3. 让代理唱歌:
proxyObj.sing("喵喵喵...");
proxyObj.dance("广场舞...");
}
}
2. cglib的基于子类的动态代理【了解】
API介绍
Enhancer
类
-
使用cglib的要求:
- 目标对象不需要有接口
- 目标类不能是final类
- 要增强的方法不能是final方法
-
相关类介绍:
- jar包:Spring框架已经把cglib包含进去了,所以只要导入
spring-context
即可 - 核心类:
org.springframework.cglib.proxy.Enhancer
,提供了生成代理对象的方法
- jar包:Spring框架已经把cglib包含进去了,所以只要导入
-
生成代理对象的方法:
Enhancer.create(Class superClass, Callback callback)
-
superClass
:目标对象的字节码 -
callback
:回调函数,用于写代理对象要做的事情,通常写成MethodInterceptor
的匿名内部类对象callback相当于jdk动态代理中的
InvocationHandler
-
MethodInterceptor
接口
- 接口只有一个方法:每次当代理对象被调用时,这个方法都会执行。在方法里通常写代理对象的行为
intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)
- 方法的参数:
Object proxy
:最终生成的代理对象Method method
:用户在调用代理对象时,所执行的方法对象Object[] args
:用户在调用代理对象,执行方法时,所传递的实参MethodProxy methodProxy
:用户在调用代理对象时,所执行的方法的代理对象methodProxy.invokeSuper(proxy, args)
:调用目标对象的方法,性能更强
- 方法的返回值:
- 当用户调用的代理对象的方法后,得到的返回值
使用示例
目标类(待增强的)
package com.itheima.proxy.cglib;
public class SuperStar {
public void sing(String name) {
System.out.println("明星在唱歌:" + name);
}
public void dance(String name) {
System.out.println("明星在跳舞:" + name);
}
}
通知类(用于增强的)
package com.itheima.proxy.cglib;
//增强出来的功能
public class StarAdvice {
public void before(){
System.out.println("先彩排一下~~");
}
public void after(){
System.out.println("收钱~~");
}
}
使用cglib生成代理对象
package com.itheima.test;
import com.itheima.proxy.cglib.StarAdvice;
import com.itheima.proxy.cglib.SuperStar;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TestCglibProxyDemo {
@Test
public void test01(){
//1. 创建真实对象
final SuperStar realObj = new SuperStar();
//2. 创建代理
SuperStar proxyObj = (SuperStar) Enhancer.create(realObj.getClass(), new MethodInterceptor() {
/*
o : 代理对象
method: 方法对象
objects : 方法参数
methodProxy : 方法代理
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//增强:
StarAdvice sa = new StarAdvice();
sa.before();
//System.out.println("intercept...");
//调用真实对象的方法
//Object result = method.invoke(realObj ,objects);
Object result = methodProxy.invokeSuper(o , objects);
sa.after();
return result;
}
});
//3. 调用方法
proxyObj.sing("世界第一等");
proxyObj.dance("广场舞");
}
}
总结
-
IOC的注解:
@Componnet: 通用的注解
@Controller : 针对web层
@Service : 针对service层
@Repository : 针对dao层
细节:
1. 默认创建的还是单例对象,如果想做成多例: @Scope("prototype") | @Scope("singleton") |
2. 可以在注解里面使用value属性来指定对象的id值,如果不指定,那么将会使用类名(首字母小写)作为id值DI的注解:
@Autowired
1. 自动注入,注入对象。
2. 它是按照类型来查找对象的,找到的只有一个对象话,就直接注入!
3. 如果找到有多个满足注入关系的对象,那么会把属性名当成id名再去匹配一次!
3.1 如果能匹配上,就注入
3.2 如果没有匹配的,就报错了@Qualifier
1. 不能单独使用,需要搭配@Autowired使用。
2. 用于告诉@Autowired,用指定的id去找对象注入@Resource:
等价于@Autowired + @Qualifier@Value :
1. 用来注入普通数据的注解
2. 一般是用来注入proper贴上来里面的内容!只要是用来了注解,就一定要记得打开扫描的开关!
<context:componennt-scan base-package="com.itheima"/>纯注解:
@Configuration
用于表示某一个类是核心配置类
@ComponentScan
用来扫描包@PropertySource
用来导入properties文件@Import
用来导入子配置类@Bean
1. 用在方法上,spring会调用这个方法,并且把这个方法的返回值管理起来。
2. 管理这个对象的时候,可以在@Bean里面使用value属性来指定id值,如果不指定,那么将会使用
方法的名字,作为对象的id值。