举例:代码中分别实现了三个类,其中UserAction属于表示层,UserService表示业务层,UserDao表示持久层。
public class UserAction {
private UserService userService = new UserServiceImpl();
/**
* 删除用户信息的请求
*/
public void deleteRequest(){
userService.deleteUser();
}
}
public interface UserService {
/**
* 删除用户信息
*/
void deleteUser();
}
public class UserServiceImpl implements UserService {
UserDao userDao = new UserDaoImplForMySQL();
@Override
public void deleteUser() {
userDao.deleteById();
// 处理业务的代码……
}
}
public interface UserDao
{
/**
* 根据id删除用户信息
*/
void deleteById();
}
public class UserDaoImplForMySQL implements UserDao {
@Override
public void deleteById() {
System.out.println("MySQL正在删除用户信息……");
}
}
OCP原则(开闭原则)
OCP原则是软件开发七大原则中最基本的原则,开指的是扩展开放,闭指的是修改关闭。该原则是最核心、最基本的,其他的六个原则都是为这个原则服务。
**OCP核心:**在扩展系统功能的时候,没有修改以前写好的代码,则表示符合OCP原则。
当进行系统功能扩展的时候,如果修改了之前稳定的程序,则之前所有的程序都需要进行重新测试,造成很多麻烦。
DIP原则(依赖倒置原则)
上图中UserAction中通过实例化UserServiceImpl进行业务操作,而UserServiceImpl实例化UserDaoImplForMySQL进行数据操作。UserServiceImpl是依赖UserDaoImplForMySQL,同时UserAction依赖UserServiceImpl,因此这发个程序违背了依赖依赖倒置原则(下面一动,上面就会受到牵连)。
依赖倒置原则的核心: 倡导面向接口编程,面向抽象编程,不要面向具体编程。
以上举例的程序既违背了OCP原则,也违背了DIP原则,可以使用控制反转的编程思想解决问题。
控制反转(IoC Inversion of Control)
控制反转思想:
控制反转是一种编程思想,或者叫做一种新型的设计模式,但由于出现的比较新,所以没有被纳入GoF23种设计模式范围内。
Spring框架的作用:
创建模块(module),并对pom.xml进行以下配置:
1)配置仓库:
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
2)设置依赖:
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.1.0-M2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
dependencies>
刷新后,maven中会引入我们在pom.xml中配置的依赖:
并在java中创建包——com.powernode.spring6.bean,并添加User类
随后,在main下的resources文件中添加配置文件——命名为spring:
然后IDEA会自动生成以下内容:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
beans>
这个文件不一定是spring。这个文件最好放在类路径当中,方便后期的移植,放在resources根目录(resources是类的根路径)下,就相当于放到了类的根路径下。配重bean,这样spring才能帮助我们管理这个对象。
bean标签有两个重要属性:
<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 = "userBean" class="com.powernode.spring6.bean.User" />
beans>
package com.powernode.spring6.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstSpringTest {
@Test
public void testFirstSpringCode(){
// 第一步:获取Spring容器对象
// ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring配置文件的路径");
//下行代码只要一执行,就相当于启动spring容器,并且解析spring.xml文件,然后将里面的bean的对象全new出来,放在spring容器中。
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
// 第二步:根据bean的id从Spring容器中获取这个对象
//applicationContext.getBean("bean的id")
Object userBean= applicationContext.getBean("userBean");
System.out.println(userBean);
}
}
ApplicationContext 翻译为:应用上下文,其实就是Spring容器
ApplicationContext 是一个接口,他有很多实现类,其中一个实现类叫做ClassPathXmlApplicationContext
ClassPathXmlApplicationContext专门从类路径文件加载spring配置文件的一个对象。
以上代码的输出结果:
Spring实例化对象:默认情况下,Spring会通过反射机制,调用类的无参构造方法来实例化对象;若只定义了一个有参数构造函数,则会报错,因此要把无参构造函数给加上。
创建好的对象的存储:Spring创建后对象后,会以一个Map结构的形式存储对象,如:
同时运行多个配置文件:
1)引入Log4j2的依赖:在pom.xml中添加相关依赖,并重新加载。
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.19.0version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j2-implartifactId>
<version>2.19.0version>
dependency>
2)在类的根路径下提供log4j2.xml配置文件(文件名固定为:log4j2.xml,文件必须放在类根路径下)
(我在网上随便找了一个)
<configuration monitorInterval="5" status="warn">
<Properties>
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss,SSS} %highlight{%-5level} [%t] %highlight{%c{1.}.%M(%L)}: %msg%n" />
<property name="FILE_PATH" value="log" />
Properties>
<appenders>
<console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
console>
<RollingFile name="RollingFileDebug" fileName="${FILE_PATH}/debug.log" filePattern="${FILE_PATH}/debug/DEBUG-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="100MB"/>
Policies>
<DefaultRolloverStrategy max="15">
<Delete basePath="${FILE_PATH}" maxDepth="2">
<IfFileName glob="*/*.log.gz" />
<IfLastModified age="360H" />
Delete>
DefaultRolloverStrategy>
RollingFile>
<RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/info/INFO-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="100MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/error/ERROR-%d{yyyy-MM-dd}_%i.log.gz">
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
<PatternLayout pattern="${LOG_PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true" />
<SizeBasedTriggeringPolicy size="100MB"/>
Policies>
<DefaultRolloverStrategy max="15"/>
RollingFile>
<Async name="Async" bufferSize="20000" blocking="true">
<AppenderRef ref="Console"/>
<AppenderRef ref="RollingFileDebug"/>
<AppenderRef ref="RollingFileInfo"/>
<AppenderRef ref="RollingFileError"/>
Async>
appenders>
<loggers>
<logger name="org.mybatis" level="info" additivity="false">
<AppenderRef ref="Async"/>
logger>
<Logger name="org.springframework" level="info" additivity="false">
<AppenderRef ref="Async"/>
Logger>
<root level="debug">
<AppenderRef ref="Async" />
root>
loggers>
configuration>
根据视频整了个简易版:
<configuration>
<loggers>
<root level="DEBUG">
<appender-ref ref="spring6log" />
root>
loggers>
<appenders>
<console name="spring6log" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-3level %logger{1024} - %msg%n"/>
console>
appenders>
configuration>
3)使用日志框架
package com.powernode.spring6.test;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class FirstSpringTest {
@Test
public void testLog4j2(){
// 自己使用log4j2记录日志信息
// 1、创建日志记录器对象
// 获取的是FirstSpringTest类的日志记录器对象,也就是说只要是FirstSpringTest类中的代码执行记录日志的话,就输出相关的日志信息。
Logger logger = LoggerFactory.getLogger(FirstSpringTest.class);
// 2、记录日志,根据不同的级别输出日志
logger.info("这是一条消息");
logger.info("这是一条调试消息");
logger.info("这是一条错误消息");
}
}
依赖注入实现了控制反转的思想,Spring是通过依赖注入的方式来完成Bean管理的。Bean管理指的是——Bean对象的创建,以及Bean对象中属性的赋值(或者叫做Bean对象之间关系的维护)。
依赖注入:依赖值得是对象和对象之间的关联关系;注入值得是一种数据的传递行为,通过注入行为让对象和对象产生关系。
依赖注入常见的实现方式包括两种:set注入和构造注入
set注入是基于set方法实现的,底层会通过反射机制调用属性对应的set方法然后给属性赋值。这种方式要求属性必须对外提供set方法。
举例:
1)创建模块spring6-003-dependency-injection 并设置pom.xml文件和log4j2.xml
pom.xml:
<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.0modelVersion>
<groupId>com.xxxgroupId>
<artifactId>spring6-003-dependency-injectionartifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<repositories>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/milestoneurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.1.0-M2version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13.2version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.19.0version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-slf4j2-implartifactId>
<version>2.19.0version>
dependency>
dependencies>
<properties>
<maven.compiler.source>18maven.compiler.source>
<maven.compiler.target>18maven.compiler.target>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
project>
log4j2.xml:
<configuration>
<loggers>
<root level="INFO">
<appender-ref ref="spring6log" />
root>
loggers>
<appenders>
<console name="spring6log" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} [%t] %-3level %logger{1024} - %msg%n"/>
console>
appenders>
configuration>
package com.xxx.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息……");
// 使用kog4j2日志框架
logger.info("数据库正在保存用户信息……");
}
}
UserService.java:
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
public class UserService {
private UserDao userDao;
// set注入的话,必须提供一个set方法
// Spring容器会调用这个set方法,来给userDao属性赋值
// 自定义set方法,不符合javabean规范,但至少这个方法要以set单词开始(前三个字母不能随便写)
public void setMySQLUserDao(UserDao userDao) {
this.userDao = userDao;
}
// 以下方法是由IDEA生成的,复合javabean规范
//public void setUserDao(UserDao userDao) {
// this.userDao = userDao;
//}
public void saveUser(){
//保存用户信息到数据库
userDao.insert();
}
}
一般用IDEA自动生成的方法,更规范,这里写自定义方法是举一个例子。
3)添加spring.xml即Spring配置文件:
property中调用set方法,并传递参数。
<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="userDaoBean" class="com.xxx.spring6.dao.UserDao"/>
<bean id="userServiceBean" class="com.xxx.spring6.service.UserService">
<property name="mySQLUserDao" ref="userDaoBean"/>
bean>
beans>
package com.xxx.spring6.test;
import com.xxx.spring6.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDITest {
@Test
public void testSetDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userServiceBean", UserService.class);
userService.saveUser();
}
}
核心原理:通过调用构造方法来给属性赋值。
在set注入的代码基础上进行修改:
1)添加了一个持久层的类:
package com.xxx.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VipDao {
private static final Logger logger = LoggerFactory.getLogger(VipDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息……");
// 使用kog4j2日志框架
logger.info("数据库正在保存VIP用户信息……");
}
}
2)业务层新建一个CustomerService:使用构造函数给Dao中的类赋值
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import com.xxx.spring6.dao.VipDao;
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
public CustomerService(UserDao userDao, VipDao vipDao) {
this.userDao = userDao;
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
3)重新写了一个配置文件:spring2.xml
<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="userDaoBean" class="com.xxx.spring6.dao.UserDao"/>
<bean id="vipDaoBean" class="com.xxx.spring6.dao.VipDao"/>
<bean id="csBean" class="com.xxx.spring6.service.CustomerService">
<constructor-arg index="0" ref="userDaoBean"/>
<constructor-arg index="1" ref="vipDaoBean"/>
bean>
<bean id="xxx" class="com.xxx.spring6.dao.UserDao"/>
<bean id="yyy" class="com.xxx.spring6.dao.VipDao"/>
<bean id="csBean2" class="com.xxx.spring6.service.CustomerService">
<constructor-arg name="userDao" ref="xxx"/>
<constructor-arg name="vipDao" ref="yyy"/>
bean>
<bean id="csBean3" class="com.xxx.spring6.service.CustomerService">
<constructor-arg ref="yyy"/>
<constructor-arg ref="xxx"/>
bean>
beans>
4)添加测试文件:
package com.xxx.spring6.test;
import com.xxx.spring6.service.CustomerService;
import com.xxx.spring6.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringDITest {
@Test
public void testConstructorDI(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
CustomerService customerService = applicationContext.getBean("csBean",CustomerService.class);
customerService.save();
CustomerService customerService1 =applicationContext.getBean("csBean2",CustomerService.class);
customerService1.save();
CustomerService customerService2 =applicationContext.getBean("csBean3",CustomerService.class);
customerService2.save();
}
}
外部Bean和内部Bean
外部Bean:指的是在使用set注入时,通过在property标签中的ref来指定注入对象;
内部Bean:指的是在使用set注入时,通过在property标签中再添加一个bean标签,用来指定被注入的对象;
举例如下:
<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="orderDaoBean" class="com.xxx.spring6.dao.OrderDao"/>
<bean id="orderServiceBean" class="com.xxx.spring6.service.OrderService">
<property name="orderDao" ref="orderDaoBean"/>
bean>
<bean id="orderServiceBean2" class="com.xxx.spring6.service.OrderService">
<property name="orderDao">
<bean class="com.xxx.spring6.dao.OrderDao"/>
property>
bean>
beans>
注入简单类型
当给Bean注入简单类型时,和注入Bean对象类似,不同在于——注入复杂的Bean对象时,需要ref指向注入对象;而注入简单类型的数据时,使用的是value进行赋值。
例如:添加一个Use对象:该对象的属性都是简单类型的数据,并提供修改属性值的set方法。
package com.xxx.spring6.bean;
public class User {
private String userName; //String是简单类型
private String pswd;
private int age;//int是简单类型
public void setUserName(String userName) {
this.userName = userName;
}
public void setPswd(String pswd) {
this.pswd = pswd;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", pswd='" + pswd + '\'' +
", age=" + age +
'}';
}
}
在配置文件进行注入:
<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="userBean" class="com.xxx.spring6.bean.User">
<property name="userName" value="李四"/>
<property name="pswd" value="666"/>
<property name="age" value="20"/>
bean>
beans>
测试:
@Test
public void testSetSimple(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("set-di.xml");
User user =applicationContext.getBean("userBean",User.class);
System.out.println(user);
}
查看Spring框架下的简单类型:
1)Ctrl+N搜索BeanUtils,进入到该类中:
2)Ctrl+f12 输入isSimple:
找到它:
然后按住Ctrl键,再点击return中的isSimpleValueType,跳转到该方法中:
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
ZoneId.class.isAssignableFrom(type) ||
TimeZone.class.isAssignableFrom(type) ||
File.class.isAssignableFrom(type) ||
Path.class.isAssignableFrom(type) ||
Charset.class.isAssignableFrom(type) ||
Currency.class.isAssignableFrom(type) ||
InetAddress.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
UUID.class == type ||
Locale.class == type ||
Pattern.class == type ||
Class.class == type));
}
注意:如果把时间作为简单数据类型,使用value赋值的话,这个日期字符串格式有要求,例如:
这样比较麻烦,所以在实际开发中,一般不会把Date作为一个简单,而是通过ref进行赋值。
注入数组
以下的代码都来自教程
数组的元素是简单类型:
Person
package com.xxx.spring6.beans;
import java.util.Arrays;
public class Person {
private String[] favariteFoods;
public void setFavariteFoods(String[] favariteFoods) {
this.favariteFoods = favariteFoods;
}
@Override
public String toString() {
return "Person{" +
"favariteFoods=" + Arrays.toString(favariteFoods) +
'}';
}
}
配置文件:
<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="person" class="com.xxx.spring6.beans.Person">
<property name="favariteFoods">
<array>
<value>鸡排value>
<value>汉堡value>
<value>鹅肝value>
array>
property>
bean>
beans>
当数组中的元素是非简单类型:一个订单中包含多个商品
Goods
package com.xxx.spring6.beans;
public class Goods {
private String name;
public Goods() {
}
public Goods(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
'}';
}
}
Order
package com.xxx.spring6.beans;
import java.util.Arrays;
public class Order {
// 一个订单中有多个商品
private Goods[] goods;
public Order() {
}
public Order(Goods[] goods) {
this.goods = goods;
}
public void setGoods(Goods[] goods) {
this.goods = goods;
}
@Override
public String toString() {
return "Order{" +
"goods=" + Arrays.toString(goods) +
'}';
}
}
配置文件:
<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="goods1" class="com.xxx.spring6.beans.Goods">
<property name="name" value="西瓜"/>
bean>
<bean id="goods2" class="com.xxx.spring6.beans.Goods">
<property name="name" value="苹果"/>
bean>
<bean id="order" class="com.xxx.spring6.beans.Order">
<property name="goods">
<array>
<ref bean="goods1"/>
<ref bean="goods2"/>
array>
property>
bean>
beans>
注入List、Set、Map、Properties
People:
package com.xxx.spring6.bean;
import java.util.List;
public class People {
private List<String> names;
private Set<String> phones;
private Map<Integer, String> addrs;
// Properties的key和value只能是String类型
private Properties properties; //Properties本质上也是一个Map集合
public void setNames(List<String> names) {
this.names = names;
}
public void setPhones(Set<String> phones) {
this.phones = phones;
}
public void setAddrs(Map<Integer, String> addrs) {
this.addrs = addrs;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "People{" +
"names=" + names +
", phones=" + phones +
", addrs=" + addrs +
", properties=" + properties +
'}';
}
}
配置文件:
<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="peopleBean" class="com.xxx.spring6.bean.People">
<property name="names">
<list>
<value>铁锤value>
<value>张三value>
<value>张三value>
<value>张三value>
<value>狼value>
list>
property>
<property name="phones">
<set>
<value>110value>
<value>110value>
<value>120value>
<value>120value>
<value>119value>
<value>119value>
set>
property>
<property name="addrs">
<map>
<entry key="1" value="北京大兴区"/>
<entry key="2" value="上海浦东区"/>
<entry key="3" value="深圳宝安区"/>
map>
property>
<property name="properties">
<props>
<prop key="driver">com.mysql.cj.jdbc.Driverprop>
<prop key="url">jdbc:mysql://localhost:3306/springprop>
<prop key="username">rootprop>
<prop key="password">123456prop>
props>
property>
bean>
beans>
测试
@Test
public void testArray(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-array.xml");
People peopleBean = applicationContext.getBean("peopleBean", People.class);
System.out.println(peopleBean);
}
输出:
People{names=[铁锤, 张三, 张三, 张三, 狼], phones=[110, 120, 119], addrs={1=北京大兴区, 2=上海浦东区, 3=深圳宝安区}, properties={password=123456, driver=com.mysql.cj.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring, username=root}}
注入null和空字符串
Vip:
package com.xxx.spring6.beans;
public class Vip {
private String email;
private int age;
public void setEmail(String email) {
this.email = email;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Vip{" +
"email='" + email + '\'' +
", age=" + age +
'}';
}
}
配置文件:
<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="vipBean" class="com.xxx.spring6.bean.Vip">
<property name="email">
<value/>
property>
bean>
beans>
测试:
@Test
public void testNull(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-null.xml");
Vip vipBean = applicationContext.getBean("vipBean", Vip.class);
System.out.println(vipBean);
}
注入含有特殊符号:XML中有5个特殊字符,分别是:<、>、'、"、&
解决方法:
<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="mathBean" class="com.powernode.spring6.beans.Math">
<property name="result">
<value>value>
property>
bean>
beans>
p命名空间注入本质上还是set注入,可以用来简化配置文件,使用p命名空间注入的前提条件包括两个:
举例:
Dog
package com.xxx.spring6.bean;
import java.util.Date;
public class Dog {
// 简单数据类型
private String name;
private int age;
// 非简单类型
private Date birth;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", birth=" + birth +
'}';
}
}
配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dogBean" class="com.xxx.spring6.bean.Dog" p:name="发财" p:age="3" p:birth-ref="birthBean"/>
<bean id="birthBean" class="java.util.Date"/>
beans>
测试:
@Test
public void testSpringP(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-p.xml");
Dog dog = applicationContext.getBean("dogBean", Dog.class);
System.out.println(dog);
}
c命名空间是简化构造方法注入的。使用c命名空间的两个前提条件:
举例:
MyTime
package com.xxx.spring6.bean;
public class MyTime {
private int year;
private int month;
private int day;
public MyTime(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
@Override
public String toString() {
return "MyTime{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
}
配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myTimeBean" class="com.xxx.spring6.bean.MyTime" c:year="2023" c:month="10" c:day="25"/>
beans>
测试:
@Test
public void testSpringC(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-c.xml");
MyTime myTime = applicationContext.getBean("myTimeBean", MyTime.class);
System.out.println(myTime);
}
注意:不管是p命名空间还是c命名空间,注入的时候都可以注入简单类型以及非简单类型。
使用util命名空间可以让配置复用。
使用util命名空间的前提是:在spring配置文件头部添加配置信息。
MyDataSource1:
package com.xxx.spring6.bean;
import java.util.Properties;
public class MyDataSource1 {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource1{" +
"properties=" + properties +
'}';
}
}
MyDataSource2:
package com.xxx.spring6.bean;
import java.util.Properties;
public class MyDataSource2 {
private Properties properties;
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public String toString() {
return "MyDataSource1{" +
"properties=" + properties +
'}';
}
}
配置文件:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<util:properties id="prop">
<prop key="driver">com.mysql.cj.jdbc.Driverprop>
<prop key="url">jdbc:mysql://localhost:3306/springprop>
<prop key="username">rootprop>
<prop key="password">123456prop>
util:properties>
<bean id="dataSource1" class="com.xxx.spring6.bean.MyDataSource1">
<property name="properties" ref="prop"/>
bean>
<bean id="dataSource2" class="com.xxx.spring6.bean.MyDataSource2">
<property name="properties" ref="prop"/>
bean>
beans>
注意:修改了 xmlns:util和xsi:schemaLocation两处。
测试
@Test
public void testSpringUtil(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-util.xml");
MyDataSource1 dataSource1 = applicationContext.getBean("dataSource1", MyDataSource1.class);
System.out.println(dataSource1);
MyDataSource2 dataSource2 = applicationContext.getBean("dataSource2", MyDataSource2.class);
System.out.println(dataSource2);
}
输出:
MyDataSource1{properties={password=123456, driver=com.mysql.cj.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring, username=root}}
MyDataSource2{properties={password=123456, driver=com.mysql.cj.jdbc.Driver, url=jdbc:mysql://localhost:3306/spring, username=root}}
Spring可以根据名字进行自动转配,也可以根据类型进行自动装配。自动装配是基于set注入的,因此要提供set函数。
根据名字自动装配举例:
package com.xxx.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OrderDao {
private static final Logger logger = LoggerFactory.getLogger(OrderDao.class);
public void insert(){
System.out.println("订单正在生成……");
}
}
package com.xxx.spring6.service;
import com.xxx.spring6.dao.OrderDao;
public class OrderService {
private OrderDao orderDao;
// 通过set给orderDao赋值
public void setOrderDao(OrderDao orderDao) {
this.orderDao = orderDao;
}
//这是生成订单的业务方法
public void generate(){
orderDao.insert();
}
}
<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="orderDao" class="com.xxx.spring6.dao.OrderDao"/>
<bean id="orderService" class="com.xxx.spring6.service.OrderService" autowire="byName"/>
beans>
测试:
@Test
public void testAutoWire(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
OrderService orderService = applicationContext.getBean("orderService",OrderService.class);
orderService.generate();
}
关键:
根据类型自动装配:注意——根据类型匹配时,在有效的配置当中,该类型实例(Bean)只能有一个,否则会报错。
package com.xxx.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserDao {
private static final Logger logger = LoggerFactory.getLogger(UserDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息……");
// 使用kog4j2日志框架
logger.info("数据库正在保存用户信息……");
}
}
package com.xxx.spring6.dao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VipDao {
private static final Logger logger = LoggerFactory.getLogger(VipDao.class);
public void insert(){
//System.out.println("数据库正在保存用户信息……");
// 使用kog4j2日志框架
logger.info("数据库正在保存VIP用户信息……");
}
}
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import com.xxx.spring6.dao.VipDao;
public class CustomerService {
private UserDao userDao;
private VipDao vipDao;
//public CustomerService(UserDao userDao, VipDao vipDao) {
// this.userDao = userDao;
// this.vipDao = vipDao;
//}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setVipDao(VipDao vipDao) {
this.vipDao = vipDao;
}
public void save(){
userDao.insert();
vipDao.insert();
}
}
<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 class="com.xxx.spring6.dao.VipDao">bean>
<bean class="com.xxx.spring6.dao.UserDao">bean>
<bean id="cs" class="com.xxx.spring6.service.CustomerService" autowire="byType">bean>
beans>
测试:
@Test
public void testAutoWire(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-autowire.xml");
CustomerService cs = applicationContext.getBean("cs",CustomerService.class);
cs.save();
}
(这里的代码直接复制的教程)
第一步:写一个数据源类,提供相关属性。
package com.xxx.spring6.bean;
public class MyDataSource {
@Override
public String toString() {
return "MyDataSource{" +
"driver='" + driver + '\'' +
", url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
private String driver;
private String url;
private String username;
private String password;
public void setDriver(String driver) {
this.driver = driver;
}
public void setUrl(String url) {
this.url = url;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
第二步:在类路径下新建jdbc.properties文件,并配置信息。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/spring
username=root
password=root123
第三步:在spring配置文件中引入context命名空间。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.xxx.spring6.bean.MyDataSource">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
bean>
beans>
测试程序:
@Test
public void testProperties(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-properties.xml");
MyDataSource dataSource = applicationContext.getBean("dataSource", MyDataSource.class);
System.out.println(dataSource);
}
输出:
MyDataSource{driver='com.mysql.cj.jdbc.Driver', url='jdbc:mysql://localhost:3306/spring', username='zxy', password='root123'}
Spring默认情况下管理Bean:
设置Bean的作用域:
<bean id="userDaoBean" class="com.xxx.spring6.dao.UserDao" scope="prototype"/>
scope表示当前Bean的作用域,singleton(默认值)表示单例,prototype表示多例(原型)。
如果将Bean的scope属性设置为prototype,则spring初始化上下文的时候,不会实例化该Bean对象,每一次调用getBean()方法的时候,实例化该Bean对象。
如果当前项目是一个Web项目(比如在pom.xml中引入spring-web的时候),scope还可以设置其他值,scope的取值一共有8种:
设计模式:一种可以被重复利用的解决方案。
《Design Patterns: Elements of Reusable Object-Oriented Software》(即《设计模式》一书),1995年由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著。这几位作者常被称为"四人组(Gang of Four)"。
GoF23种设计模式可分为三大类:
工厂模式通常有三种形态:
简单工厂模式
简单工厂模式的角色:
简单工厂模式优缺点:
工厂方法模式
工厂方法模式的角色包括:
工厂方法模式的优缺点:
抽象工厂模式
工厂方法模式的角色包括:
Spring有4种常用的获取化Bean的方式:
通过构造方法获取
其实就是构造方法注入的方式,举例:
package com.xxx.spring6.bean;
public class SpringBean {
public SpringBean() {
System.out.println("Bean 的五参数构造方法执行");
}
}
<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="sb" class="com.xxx.spring6.bean.SpringBean"/>
beans>
@Test
public void testIntantiation2() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Star sf = applicationContext.getBean("sf", Star.class);
System.out.println(sf);
}
简单工厂模式获取
在构造方法获取的基础上,添加了一个工厂类,该工厂有一个静态的生产方法get用于获取实例,通过在Spring配置文件中设置获取Bean实例的方式,举例如下:
package com.xxx.spring6.bean;
public class Star {
public Star() {
System.out.println("正在创建star对象……");
}
}
package com.xxx.spring6.bean;
public class StarFactory {
static public Star get(){
return new Star();
}
}
配置文件直接在上面的基础上添加就可以了:
<bean id="star" class="com.xxx.spring6.bean.StarFactory" factory-method="get"/>
@Test
public void testIntantiation2() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Star st = applicationContext.getBean("star", Star.class);
System.out.println(sf);
}
工厂方法模式获取
该方式与简单工厂模式的差别在于——简单工厂模式中工厂使用的静态方法获取实例对象;而工厂方法模式中采用的是实例方法获取实例对象,因此要调用该工厂的生产方法get,就需要实例化工厂类。举例如下:
package com.xxx.spring6.bean;
/**
* ClassName: Gun 工厂方法模式当中的具体产品角色
*/
public class Gun {
public Gun() {
System.out.println("正在创建Gun产品……");
}
}
package com.xxx.spring6.bean;
/**
* ClassName: GunFactory 工厂方法模式当中的具体工厂角色
*/
public class GunFactory {
// 工厂方法模式中的具体工厂角色的方法是:实例方法。
public Gun get(){
// 实际上new这个对象还是我们程序员自己new的
return new Gun();
}
}
配置文件直接在上面的基础上添加就可以了:
<bean id="gunFactory" class="com.xxx.spring6.bean.GunFactory"/>
<bean id="gun" factory-bean="gunFactory" factory-method="get"/>
@Test
public void testIntantiation3() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Gun gun = applicationContext.getBean("gun", Gun.class);
System.out.println(gun);
}
FactoryBean接口实例化
工厂方法模式中,factory-bean是我们自定义的,factory-method也是我们自己定义的。
在Spring中,当你编写的类直接实现FactoryBean接口之后,factory-bean不需要指定了,factory-method也不需要指定了。
factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向getObject()方法。
举例:
package com.xxx.spring6.bean;
public class Person { // PersonFactoryBean是一个普通Bean。
public Person() {
System.out.println("Person的无参构造方法……");
}
}
package com.xxx.spring6.bean;
import org.springframework.beans.factory.FactoryBean;
public class PersonFactoryBean implements FactoryBean<Person>{ // PersonFactoryBean是一个工厂Bean,可以通过工厂Bean获取普通Bean。
//这个方法在接口中有默认实现,默认返回true,表示单例的,如果想要多例,则返回false
@Override
public boolean isSingleton() {
return FactoryBean.super.isSingleton();
}
@Override
public Person getObject() throws Exception {
//最终这个Bean的创建还是程序员自己new的
return new Person();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
配置文件直接在上面的基础上添加就可以了:
<bean id="person" class="com.xxx.spring6.bean.PersonFactoryBean"/>
@Test
public void testIntantiation4() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Person person = applicationContext.getBean("person", Person.class);
System.out.println(person);
}
BeanFactory和FactoryBean的区别
Spring就是一个管理Bean对象的工厂,它负责Bean对象的创建,对象的销毁等。
生命周期——对象从创建开始到最终销毁的整个过程,其本质是在特定的时间点上调用特定的方法。
Bean生命周期可以粗略的划分为五大步:
Bean生命周期的管理,可以参考Spring的源码:AbstractAutowireCapableBeanFactory类的doCreateBean()方法。
测试程序:
package com.xxx.spring6.bean;
public class User {
private String name;
public User() {
System.out.println("1.实例化Bean");
}
public void setName(String name) {
this.name = name;
System.out.println("2.Bean属性赋值");
}
public void initBean(){
System.out.println("3.初始化Bean");
}
public void destroyBean(){
System.out.println("5.销毁Bean");
}
}
<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="userBean" class="com.xxx.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
<property name="name" value="zhangsan"/>
bean>
beans>
@Test
public void testLifecycle() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println("4.使用Bean");
// 只有正常关闭spring容器才会执行销毁方法
ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
context.close();
}
执行结果:
Bean 的五参数构造方法执行
正在创建star对象……
正在创建Gun产品……
Person的无参构造方法……
com.xxx.spring6.bean.Person@14fc1f0
注意:
若我们想在Bean初始化化前和初始化后添加代码,可以加入“Bean后处理器”。这样Bean的生命周期就有了7步:
加入“Bean后处理器”:
1)编写一个类实现BeanPostProcessor类,并且重写before和after方法:
package com.xxx.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class LogBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的before方法");
return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
}
// 方法有两个参数:
// 第一个参数:刚创建的bean对象;
// 第二个参数:bean的名字
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行Bean后处理器的after方法");
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}
}
2)在spring.xml文件中配置“Bean后处理器”:
<bean class="com.xxx.spring6.bean.LogBeanPostProcessor"/>
一定要注意:在spring.xml文件中配置的Bean后处理器将作用于当前配置文件中所有的Bean。
添加的这三个点为的特点:都是检查这个Bean是否实现了某些特定的接口,如果实现了这些接口,则Spring容器会调用这个接口中的方法。
Aware相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware
测试以上10步,可以让User类实现5个接口,并实现所有方法:
测试代码如下:
package com.xxx.spring6.bean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.*;
public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
private String name;
public User() {
System.out.println("1.实例化Bean");
}
public void setName(String name) {
this.name = name;
System.out.println("2.Bean属性赋值");
}
public void initBean(){
System.out.println("6.初始化Bean");
}
public void destroyBean(){
System.out.println("10.销毁Bean");
}
@Override
public void setBeanClassLoader(ClassLoader classLoader) {
System.out.println("3.类加载器:" + classLoader);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("3.Bean工厂:" + beanFactory);
}
@Override
public void setBeanName(String name) {
System.out.println("3.bean名字:" + name);
}
@Override
public void destroy() throws Exception {
System.out.println("9.DisposableBean destroy");
}
//来自InitializingBean
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("5.afterPropertiesSet执行");
}
}
结果:
1.实例化Bean
2.Bean属性赋值
3.bean名字:userBean
3.类加载器:jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7
3.Bean工厂:org.springframework.beans.factory.support.DefaultListableBeanFactory@78a773fd: defining beans [com.xxx.spring6.bean.LogBeanPostProcessor#0,userBean]; root of factory hierarchy
执行Bean后处理器的before方法
5.afterPropertiesSet执行
6.初始化Bean
执行Bean后处理器的after方法
4.使用Bean
9.DisposableBean destroy
10.销毁Bean
注意:Spring对不同作用域的Bean有不同管理方式——
@Test
public void testBeanRegister() {
// 自己new的对象
User user = new User();
System.out.println(user);
//创建 默认可列表BeanFactory 对象
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 注册
factory.registerSingleton("userBean",user);
// 从Spring容器中获取Bean
User userBean = factory.getBean("userBean",User.class);
System.out.println(userBean);
}
测试结果:
1.实例化Bean
com.xxx.spring6.bean.User@1ae369b7
com.xxx.spring6.bean.User@1ae369b7
A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
测试举例:
package com.xxx.spring6.bean;
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
package com.xxx.spring6.bean;
public class Wife {
private String name;
private Husband husband;
public void setHusband(Husband husband) {
this.husband = husband;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<bean id="husbandBean" class="com.xxx.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
bean>
<bean id="wifeBean" class="com.xxx.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
bean>
测试:
@Test
public void testCD(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
Husband husband = applicationContext.getBean("husbandBean", Husband.class);
System.out.println(husband);
Wife wife = applicationContext.getBean("wifeBean", Wife.class);
System.out.println(wife);
}
输出
Husband{name='张三', wife=小花}
Wife{name='小花', husband=张三}
在singleton+setter模式下,循环依赖不会出现问题的原因:
在这种模式下,Spring对Bean的管理主要分为清晰的两个阶段:
核心解决方案就是——实例化对象和对象的属性赋值分为两个阶段来完成的。注意:只有在scope是singleton的情况下,Bean才会采取提前“曝光”的措施。(因为singleton的模式下,可以保证该类型的对象在整个Spring容器中只有一个。)
如果是prototype + setter模式下出现循环依赖,则代码会抛出异常:.BeanCurrentlyInCreationException,表示当前Bean正处于创建中异常,如图所示:
如果其中一个是singleton+setter,其他是prototype + setter模式,不会出现上述异常。
singleton+构造注入产生的循环依赖:
package com.xxx.spring6.bean2;
import com.xxx.spring6.bean2.Wife;
public class Husband {
private String name;
private Wife wife;
public Husband(String name, Wife wife) {
this.name = name;
this.wife = wife;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
package com.xxx.spring6.bean2;
import com.xxx.spring6.bean.Husband;
public class Wife {
private String name;
private Husband husband;
public Wife(String name, Husband husband) {
this.name = name;
this.husband = husband;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<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="h" scope="singleton" class="com.xxx.spring6.bean2.Husband">
<constructor-arg index="0" value="张三"/>
<constructor-arg index="1" ref="w"/>
bean>
<bean id="w" scope="singleton" class="com.xxx.spring6.bean2.Wife">
<constructor-arg index="0" value="小花"/>
<constructor-arg index="1" ref="h"/>
bean>
beans>
测试:
@Test
public void testCD2(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
Husband husband = applicationContext.getBean("h", Husband.class);
System.out.println(husband);
Wife wife = applicationContext.getBean("w", Wife.class);
System.out.println(wife);
}
依旧会抛出BeanCurrentlyInCreationException异常,**原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。**因此基于构造方式下产生的循环依赖也是无法解决的。
Spring解决循环依赖(set + singleton模式)的机理: ClassA依赖ClassB,ClassB又依赖ClassA,形成依赖闭环。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。
根据方法的调用过程,我们知道调用一个方法,一般涉及到4个要素:
通过反射机制调用方法,我们也要按照上诉步骤来:
Spring中就是通过解析XML配置文件,然后利用反射机制创建Bean实例的。
自定义注解:
/* 标注注解的注解,叫做元注解。
@Target注解用来修饰@Component可以出现的位置。
@Target(value = {ElementType.TYPE, ElementType.FIELD})表示@Component注解可以出现在类(接口)上、属性上。
@Target(value = {ElementType.TYPE})表示@Component注解只能出现在类(接口)上
小技巧:
使用某个注解的时候,如果注解的属性名是value的话,value可以省略,比如:@Target({ElementType.TYPE})
使用某个注解的时候,如果注解的属性值是数组,并且数组中只有一个元素,大括号可以省略,比如:@Target(ElementType.TYPE)
@Retention 也是一个元注解。
用来标注@Component注解最终保留在class文件当中,并且可以被反射机制读取。
注解的单独使用没有意义,正确的用法是配合反射机制使用:
通过反射机制可以读取到某一个类或属性或方法上的注解里面提供的属性值
* */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) // 唯有RUNTIME阶段才能被反射读取到
public @interface Component {
// 定义注解的属性
// String是属性类型
// value是属性名
String value();
}
以上是自定义了一个注解:Component
该注解上面修饰的注解包括:Target注解和Retention注解,这两个注解被称为元注解(用在注解的注解)。
Target注解用来设置Component注解可以出现的位置,以上代表表示Component注解只能用在类和接口上。
Retention注解用来设置Component注解的保持性策略,以上代表Component注解可以被反射机制读取。
注解的使用:
package com.xxx.bean;
import com.powernode.xxx.Component;
// @Component(value = "userBean")
@Component("userBean") // 省略value
public class User {
}
用法简单,语法格式:@注解类型名(属性名=属性值, 属性名=属性值, 属性名=属性值…)
反射解析注解:
package com.xxx.test;
import com.xxx.annotation.Component;
import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws Exception {
// 存放Bean的Map集合。key存储beanId。value存储Bean。
Map<String,Object> beanMap = new HashMap<>();
// 获取包的完整路径
String packageName = "com.xxx.bean";
String path = packageName.replaceAll("\\.", "/");
URL url = ClassLoader.getSystemClassLoader().getResource(path);
File file = new File(url.getPath());
File[] files = file.listFiles();
Arrays.stream(files).forEach(f -> {
String className = packageName + "." + f.getName().split("\\.")[0];
try {
Class<?> clazz = Class.forName(className);
// 判断该类是否有Component注解
if (clazz.isAnnotationPresent(Component.class)) {
// 解析注解
Component component = clazz.getAnnotation(Component.class);
String beanId = component.value();
Object bean = clazz.newInstance();
beanMap.put(beanId, bean);
}
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(beanMap);
}
}
负责声明Bean的注解,常见的包括四个:
package com.powernode.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value = {ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Component {
String value();
}
@Controller注解:
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Service注解:
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Repository注解:
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
通过源码可以看到,@Controller、@Service、@Repository这三个注解都是@Component注解的别名( @AliasFor这个表示别名)。也就是说:这四个注解的功能都一样。用哪个都可以。他们都是只有一个value属性。value属性用来指定bean的id,也就是bean的名字。
使用步骤:
第一步:加入aop的依赖
可以看到当加入spring-context依赖之后,会关联加入aop的依赖:
第二步:在配置文件中添加context命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
beans>
第三步:在配置文件中指定要扫描的包
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.spring6.bean"/>
beans>
第四步:在Bean类上使用注解
package com.xxx.spring6.bean;
import org.springframework.stereotype.Component;
@Component(value = "userBean")
public class User {
}
测试:
@Test
public void testBean(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
User userBean = applicationContext.getBean("userBean", User.class);
System.out.println(userBean);
}
小细节:查看@Component源码可以发现,value具有默认值,也就是说,我们在给类添加该注解时,如果没有指定value值,那么Spring会自动给该Bean取值,并且默认名字的规律是:该Bean类名首字母小写即可,例如BankDao的bean的名字为bankDao。
若要扫描的包有多个,有两种解决方案:
第一种方式:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.spring6.bean,com.xxx.spring6.bean2"/>
beans>
第二种方式:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.spring6"/>
beans>
假设在某个包下有很多Bean,有的Bean上标注了Component,有的标注了Controller,有的标注了Service,有的标注了Repository,现在由于某种特殊业务的需要,只允许其中所有的Controller参与Bean管理,其他的都不实例化。这应该怎么办呢?
架设几个类都定义在同一个java源文件中:
package com.xxx.spring6.bean3;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
@Component
public class A {
public A() {
System.out.println("A的无参数构造方法执行");
}
}
@Controller
class B {
public B() {
System.out.println("B的无参数构造方法执行");
}
}
@Service
class C {
public C() {
System.out.println("C的无参数构造方法执行");
}
}
@Repository
class D {
public D() {
System.out.println("D的无参数构造方法执行");
}
}
@Controller
class E {
public E() {
System.out.println("E的无参数构造方法执行");
}
}
@Controller
class F {
public F() {
System.out.println("F的无参数构造方法执行");
}
}
如果只想实例化bean3包下的Controller,配置文件为:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.spring6.bean3" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
<context:component-scan base-package="com.powernode.spring6.bean3">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
context:component-scan>
beans>
通过注解给Bean的属性赋值,需要用到这些注解:
@Value的使用
当属性的类型时简单类型时,可以使用@Value注解进行注入:
package com.xxx.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
@Value(value = "zhangsan")
private String name;
@Value("20")
private int age;
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.spring6.bean4"/>
beans>
@Test
public void testValue(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
Object user = applicationContext.getBean("user");
System.out.println(user);
}
注意: 使用@Value注解注入属性值的时候,可以用在属性上,并且不提供set方法。
将@Value用在setter方法上:
package com.xxx.spring6.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
@Value("李四")
public void setName(String name) {
this.name = name;
}
@Value("30")
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
通过构造方法注入
package com.powernode.xxx.bean4;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
public User(@Value("隔壁老王") String name, @Value("33") int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
通过以上代码:@Value注解可以出现在属性上、setter方法上、以及构造方法的形参上。
@Autowired与@Qualifier
@Autowired注解可以用来注入非简单类型。被翻译为:自动连线的,或者自动装配。单独使用@Autowired注解,默认根据类型装配。【默认是byType】如果想要根据名字进行装配,则要一起使用@Autowired与@Qualifier。
@Autowired源码:
package org.springframework.beans.factory.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
通过源码,可以知道:
属性上使用@Autowired :
package com.xxx.spring6.dao;
public interface UserDao {
void insert();
}
package com.xxx.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository //纳入bean管理
public class UserDaoForMySQL implements UserDao{
@Override
public void insert() {
System.out.println("正在向mysql数据库插入User数据");
}
}
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service // 纳入bean管理
public class UserService {
@Autowired // 在属性上注入
private UserDao userDao;
// 没有提供构造方法和setter方法。
public void save(){
userDao.insert();
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.xxx.spring6.dao,com.xxx.spring6.service"/>
beans>
@Test
public void testAutowired(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-injection.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
setter方法 测试@Autowired注解:
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
构造方法 测试@Autowired注解:
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
// 方式一
//@Autowired
//public UserService(UserDao userDao) {
// this.userDao = userDao;
//}
// 方式二
public UserService(@Autowired UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
当构造方法只有一个,同时该构造方法只有一个形参且能匹配,@Autowired注解可以省略(但最好别省):
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
public UserService(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
注意:如果有多个构造方法,@Autowired肯定是不能省略的。
结合上述程序,若UserDao接口还有另一个实现类:
package com.xxx.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository //纳入bean管理
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
此时,使用@Autowired注解会报错,因为此时Spring不能识别要装配UserDao下的哪一个实现类了。为了解决这个问题,我们可以通过byName 根据名称进行装配。
@Autowired注解和@Qualifier注解联合起来才可以根据名称进行装配,在@Qualifier注解中指定Bean名称。
package com.xxx.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository // 这里没有给bean起名,默认名字是:userDaoForOracle
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private UserDao userDao;
@Autowired
@Qualifier("userDaoForOracle") // 这个是bean的名字。
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void save(){
userDao.insert();
}
}
总结:
@Resource(官方建议使用)
@Resource注解也可以完成非简单类型注入。那它和@Autowired注解有什么区别?
如果你是Spring6+ 版本请使用这个依赖:
<dependency>
<groupId>jakarta.annotationgroupId>
<artifactId>jakarta.annotation-apiartifactId>
<version>2.1.1version>
dependency>
注意:如果你用Spring6,要知道Spring6不再支持JavaEE,它支持的是JakartaEE9。(Oracle把JavaEE贡献给Apache了,Apache把JavaEE的名字改成JakartaEE了,大家之前所接触的所有的 javax. 包名统一修改为 jakarta.包名了。
如果你是Spring5- 版本请使用这个依赖:
<dependency>
<groupId>javax.annotationgroupId>
<artifactId>javax.annotation-apiartifactId>
<version>1.3.2version>
dependency>
@Resource注解的源码:
package jakarta.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(Resources.class)
public @interface Resource {
String name() default "";
String lookup() default "";
Class<?> type() default Object.class;
AuthenticationType authenticationType() default Resource.AuthenticationType.CONTAINER;
boolean shareable() default true;
String mappedName() default "";
String description() default "";
public static enum AuthenticationType {
CONTAINER,
APPLICATION;
private AuthenticationType() {
}
}
}
通过源码可得:
使用举例:
package com.xxx.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository("xyz")
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource(name = "xyz")
private UserDao userDao;
public void save(){
userDao.insert();
}
}
把UserDaoForOracle的名字xyz修改为userDao,让这个Bean的名字和UserService类中的UserDao属性名一致:
package com.xxx.spring6.dao;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoForOracle implements UserDao{
@Override
public void insert() {
System.out.println("正在向Oracle数据库插入User数据");
}
}
UserService类中Resource注解并没有指定name:
package com.xxx.spring6.service;
import com.xxx.spring6.dao.UserDao;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Resource
private UserDao userDao;
public void save(){
userDao.insert();
}
}
所谓的全注解开发就是不再使用spring配置文件了。写一个配置类来代替配置文件。
配置类代替Spring配置文件:
package com.xxx.spring6.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.xxx.spring6.dao", "com.xxx.spring6.service"})
public class Spring6Configuration {
}
编写测试程序:不再new ClassPathXmlApplicationContext()对象了。
@Test
public void testNoXml(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
代理模式是GoF23种设计模式之一,属于结构型设计模式。
业务场景:系统中有A、B、C三个模块,使用这些模块的前提是需要用户登录,也就是说在A模块中要编写判断登录的代码,B模块中也要编写,C模块中还要编写,这些判断登录的代码反复出现,显然代码没有得到复用,可以为A、B、C三个模块提供一个代理,在代理当中写一次登录判断即可。代理的逻辑是:请求来了之后,判断用户是否登录了,如果已经登录了,则执行对应的目标,如果没有登录则跳转到登录页面。【在程序中,目标不但受到保护,并且代码也得到了复用。】
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
代理模式中的角色:
静态代理举例:
OrderService接口:
package com.xxx.mall.service;
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 查看订单详情
*/
void detail();
/**
* 修改订单
*/
void modify();
}
OrderService接口的实现类:
package com.xxx.mall.service.impl;
import com.xxx.mall.service.OrderService;
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void detail() {
try {
Thread.sleep(2541);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单信息如下:******");
}
@Override
public void modify() {
try {
Thread.sleep(1010);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
}
客户端:
package com.xxx.mall;
import com.xxx.mall.service.OrderService;
import com.xxx.mall.service.OrderServiceProxy;
import com.xxx.mall.service.impl.OrderServiceImpl;
public class Client {
public static void main(String[] args) {
OrderService order = new OrderServiceImpl();
order.generate();
order.modify();
order.detail();
}
}
现有需求——获取每次操作的运行时间:
添加静态代理——为OrderService接口提供一个代理类:
package com.xxx.mall.service;
public class OrderServiceProxy implements OrderService{ // 代理对象
// 目标对象
// 将目标对象作为代理对象的一个属性,这种关系叫做关联关系,必继承关系的耦合度低。
private OrderService orderService;
// 通过构造方法将目标对象传递给代理对象
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generate() {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.generate();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void detail() {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.detail();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
@Override
public void modify() {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.modify();
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
}
}
客户端:
package com.xxx.mall;
import com.xxx.mall.service.OrderService;
import com.xxx.mall.service.OrderServiceProxy;
import com.xxx.mall.service.impl.OrderServiceImpl;
public class Client {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxy = new OrderServiceProxy(target);
// 调用代理对象的代理方法
proxy.generate();
proxy.modify();
proxy.detail();
}
}
以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。这种方式的优点:符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。
缺点:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。解决方式——动态代理,在内存中动态的为我们生成代理类的字节码。代码只需要写一次,以后得代理可以直接复用该代码。
动态代理:在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题。
在内存当中动态生成类的技术常见的包括:
动态代理举例:
OrderService接口:
package com.xxx.mall.service;
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 查看订单详情
*/
void detail();
/**
* 修改订单
*/
void modify();
}
OrderService接口的实现类:
package com.xxx.mall.service.impl;
import com.xxx.mall.service.OrderService;
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成");
}
@Override
public void detail() {
try {
Thread.sleep(2541);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单信息如下:******");
}
@Override
public void modify() {
try {
Thread.sleep(1010);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改");
}
}
客户端:
package com.xxx.mall;
import com.xxx.mall.service.OrderService;
import com.xxx.mall.service.impl.OrderServiceImpl;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 第一步:创建目标对象
OrderService target = new OrderServiceImpl();
// 第二步:创建代理对象
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
// 第三步:调用代理对象的代理方法
orderServiceProxy.detail();
orderServiceProxy.modify();
orderServiceProxy.generate();
}
}
OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
这行代码做了两件事:
newProxyInstance()方法有三个参数:
java.lang.reflect.InvocationHandler接口的实现类:
package com.xxx.mall.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
}
强行要求实现InvocationHandler接口的原因:因为JDK在底层调用invoke()方法的程序已经提前写好了,所以必须实现这个接口。注意:invoke方法不是程序员负责调用的,而是JDK负责调用的。
invoke什么时候被调用:当代理对象调用代理方法的时候,注册在InvocationHandler处理器当中的invoke被调用。
InvocationHandler接口中有一个方法invoke,这个invoke方法上有三个参数:
完善——给TimerInvocationHandler提供一个构造方法,可以通过这个构造方法传过来“目标对象”,代码如下:
package com.xxx.mall.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class TimerInvocationHandler implements InvocationHandler {
// 目标对象
private Object target;
// 通过构造方法来传目标对象
public TimerInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 目标执行之前增强。
long begin = System.currentTimeMillis();
// 调用目标对象上的目标方法
// 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值
Oject retValue = method.invoke(target,args);
// 目标执行之后增强。
long end = System.currentTimeMillis();
System.out.println("耗时"+(end - begin)+"毫秒");
System.out.println("增强2");
// 代理对象需要返回的结果
return retValue;
}
}
完善——Client程序:
package com.xxx.mall;
import com.xxx.mall.service.OrderService;
import com.xxx.mall.service.TimerInvocationHandler;
import com.xxx.mall.service.impl.OrderServiceImpl;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
// 调用代理对象的代理方法
orderServiceProxy.detail();
orderServiceProxy.modify();
orderServiceProxy.generate();
}
}
为了简化代码,实现一个工具类:
package com.xxx.mall.util;
import com.xxx.mall.service.TimerInvocationHandler;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Object newProxyInstance(Object target) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimerInvocationHandler(target));
}
}
客户端代码:
package com.xxx.mall;
import com.xxx.mall.service.OrderService;
import com.xxx.mall.service.TimerInvocationHandler;
import com.xxx.mall.service.impl.OrderServiceImpl;
import com.xxx.mall.util.ProxyUtil;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
// 调用代理对象的代理方法
orderServiceProxy.detail();
orderServiceProxy.modify();
orderServiceProxy.generate();
}
}
CGLIB动态代理
CGLIB既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用final修饰。
引入依赖:
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>3.3.0version>
dependency>
一个没有实现接口的类:
package com.xxx.mall.service;
public class UserService {
public void login(){
System.out.println("用户正在登录系统....");
}
public void logout(){
System.out.println("用户正在退出系统....");
}
}
使用CGLIB在内存中为UserService类生成代理类,并创建对象:
package com.xxx.mall;
import com.xxx.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// 创建字节码增强器
// 这个对象时CGLIB库当中的核心对象,就是依靠它来生成代理类
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(方法拦截器对象);
// 生成源码,编译class,加载到JVM,并创建代理对象
// 在内存中生成UserService类的子类,起始就是代理类的字节码;创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login();
userServiceProxy.logout();
}
}
和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
编写MethodInterceptor接口实现类:
package com.xxx.mall.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TimerMethodInterceptor implements MethodInterceptor {
private Object target; // 目标对象
public MethodInterceptorImpl(Object object){
this.target= object;
}
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return null;
}
}
MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
在MethodInterceptor的intercept()方法中调用目标以及添加增强:
package com.xxx.mall.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 前增强
long begin = System.currentTimeMillis();
// 调用目标
Object retValue = methodProxy.invokeSuper(proxy, objects);
// 后增强
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
// 一定要返回
return retValue;
}
}
修改客户端程序:
package com.xxx.mall;
import com.xxx.mall.service.TimerMethodInterceptor;
import com.xxx.mall.service.UserService;
import net.sf.cglib.proxy.Enhancer;
public class Client {
public static void main(String[] args) {
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(new TimerMethodInterceptor());
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login();
userServiceProxy.logout();
}
}
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
AOP(Aspect Oriented Programming):面向切面编程,面向方面编程。AOP是一种编程技术,AOP是对OOP的补充延伸。AOP底层使用的就是动态代理来实现的。
Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。
一个系统的系统服务,如:日志、事务管理、安全等,这些又被称为:交叉业务。交叉业务几乎是通用的,如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:
使用AOP解决以上问题:
AOP::将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
AOP的优点:
AOP的七大术语
切点表达式: 切点表达式用来定义通知(Advice)往哪些方法上切入。
语法格式:
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符:
返回值类型:
全限定类名:
方法名:
形式参数列表:
异常:
举例:
Spring对AOP的实现包括以下3种方式:
实际开发中,都是Spring+AspectJ来实现AOP。
AspectJ:(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ) AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。
准备工作
1)引入相关的jar包:
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>6.1.0-M2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>6.1.0-M2version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>6.1.0-M2version>
dependency>
2)修改配置文件:
<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"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
beans>
第一步:定义目标类以及目标方法:
package com.xxx.spring6.service;
import org.springframework.stereotype.Service;
@Service("userService")
public class UserService { //目标类
// 目标方法
public void login(){
System.out.println("系统正在进行身份认证……");
}
}
第二步:定义切面类
package com.xxx.spring6.service;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component("logAspect")
public class LogAspect { // 切面
// 切面 = 切点 +通知
}
第三步:目标类和切面类都纳入spring bean管理
在目标类UserService上添加@Service注解。
在切面类LogAspect类上添加@Component注解。
第四步:在spring配置文件中添加组建扫描
<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"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.xxx.spring6.service"/>
beans>
第五步:在切面类中添加通知
package com.xxx.spring6.service;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Component("logAspect")
public class LogAspect { // 切面
// 切面 = 切点 +通知
// 通知就是增强,就是具体的要编写的增强代码
// 这里通知Advice以方法的形式出现
public void advice(){
System.out.println("我是一个通知,这是一个增强代码……");
}
}
第六步:在通知上添加切点表达式
@Aspect // 切面类需要用@Aspect标注,若不标注,则不认为是切面类
@Component("logAspect")
public class LogAspect { // 切面
// 切面 = 切点 +通知
// 通知就是增强,就是具体的要编写的增强代码
// 这里通知Advice以方法的形式出现
//切点表达式
// @Before注解标注的方法就是一个前置通知
@Before("execution(* com.xxx.spring6.service.UserService.*(..))")
public void advice(){
System.out.println("我是一个通知,这是一个增强代码……");
}
}
第七步:在spring配置文件中启用自动代理
<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"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.xxx.spring6.service"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
beans>
proxy-target-class=“true” 表示采用cglib动态代理。
proxy-target-class=“false” 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
测试程序:
@Test
public void testBefore(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.login();
}
结果:
我是一个通知,这是一个增强代码……
系统正在进行身份认证……
通知类型包括:
测试举例
package com.xxx.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
* ClassName: LogAspect
* PackageName: com.xxx.spring6.service
* Description:
*
* @Author Xiyan Zhong
* @Create 2023/10/30 下午5:32
* @Version 1.0
*/
@Aspect
@Component("logAspect")
public class LogAspect { // 切面
// 前置通知
@Before("execution(* com.xxx.spring6.service..*(..))")
public void beforeAdvice() {
System.out.println("前置通知");
}
// 后置通知
@AfterReturning("execution(* com.xxx.spring6.service..*(..))")
public void afterReturningAdvice(){
System.out.println("后置通知");
}
// 环绕通知 是最大的通知,在前置通知之前,在后置通知之后
@Around("execution(* com.xxx.spring6.service..*(..))")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 前面的代码
System.out.println("环绕前面的通知");
// 执行目标
joinPoint.proceed();//
// 后面的代码
System.out.println("环绕后面的通知");
}
// 异常通知
@AfterThrowing("execution(* com.xxx.spring6.service..*(..))")
public void afterThrowingAdvice(){
System.out.println("异常通知");
}
// 最终通知 finally语句块中的通知
@After("execution(* com.xxx.spring6.service..*(..))")
public void afterAdvice(){
System.out.println("最终通知");
}
}
业务层代码:
package com.xxx.spring6.service;
import org.springframework.stereotype.Service;
@Service("orderService")
public class OrderService {
public void generate() {
System.out.println("生成订单……");
//
//if (1 == 1) {
// throw new RuntimeException("运行时异常……");
//}
}
}
若业务层不发生异常:
环绕前面的通知
前置通知
生成订单……
后置通知
最终通知
环绕后面的通知
若业务层发生异常:
环绕前面的通知
前置通知
生成订单……
异常通知
最终通知
java.lang.RuntimeException: 运行时异常……
当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,后置通知和环绕通知的结束部分不会执行。
前面的先后顺序
业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
举例:
package com.xxx.spring6.service;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component("securityAspect")
@Order(1)
public class SecurityAspect { //安全切面
@Before("execution(* com.xxx.spring6.service..*(..))")
public void beforeAdvice(){
System.out.println("安全前置通知");
}
}
package com.xxx.spring6.service;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component("logAspect")
@Order(2)
public class LogAspect { //安全切面
@Before("execution(* com.xxx.spring6.service..*(..))")
public void beforeAdvice(){
System.out.println("前置通知");
}
}
输出:
安全前置通知
前置通知
生成订单……
以上使用切点的缺点:
优化:定义一个通用切点的方法,用其代替切点表达式,注意该方式能跨切面使用,举例如下:
package com.xxx.spring6.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
@Aspect
@Component("logAspect")
@Order(2)
public class LogAspect { // 切面
// 定义通用的切点表达式
@Pointcut("execution(* com.xxx.spring6.service..*(..))")
public void commonPointcut(){
}
// 前置通知
//@Before("execution(* com.xxx.spring6.service..*(..))")
@Before("commonPointcut()")
public void beforeAdvice() {
System.out.println("前置通知");
}
// 后置通知
@AfterReturning("commonPointcut()")
public void afterReturningAdvice(){
System.out.println("后置通知");
}
// 环绕通知 是最大的通知,在前置通知之前,在后置通知之后
@Around("commonPointcut()")
public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 前面的代码
System.out.println("环绕前面的通知");
// 执行目标
joinPoint.proceed();//
// 后面的代码
System.out.println("环绕后面的通知");
}
// 异常通知
@AfterThrowing("commonPointcut()")
public void afterThrowingAdvice(){
System.out.println("异常通知");
}
// 最终通知 finally语句块中的通知
@After("commonPointcut()")
public void afterAdvice(){
System.out.println("最终通知");
}
}
package com.xxx.spring6.service;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Aspect
@Component("securityAspect")
@Order(1)
public class SecurityAspect { //安全切面
//@Before("execution(* com.xxx.spring6.service..*(..))")
@Before("com.xxx.spring6.service.LogAspect.commonPointcut()")
public void beforeAdvice(){
System.out.println("安全前置通知");
}
}
输出:
安全前置通知
环绕前面的通知
前置通知
生成订单……
后置通知
最终通知
环绕后面的通知
连接点 joinPoint
在调用切面的方法时,Spring会自动传入连接点作为方法的实参(所有切点都可以使用),如:
@Before("commonPointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
// JoinPoint joinPoint 在Spring容器调用这个方法的时候自动传过来,可以直接用
// joinPoint 的作用:可获取目标方法的签名,通过方法的签名可以获取到方法的具体信息
//Signature signature = joinPoint.getSignature();
//例如——获取目标方法的方法名
//String name = joinPoint.getSignature().getName();
System.out.println("目标方法的方法名:"+ joinPoint.getSignature().getName());
System.out.println("前置通知");
}
@AfterReturning("commonPointcut()")
public void afterReturningAdvice(JoinPoint joinPoint){
System.out.println("后置通知");
}
全注解方式即不适用XML配置文件。
1)创建配置类:
package com.xxx.spring6.service;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@CacheConfig // 代替XML文件
@ComponentScan({"com.xxx.spring6.service"}) // 配置组件扫描
@EnableAspectJAutoProxy(proxyTargetClass = true) // 启用aspectJ自动代理
public class Spring6Config {
}
@Test
public void testNoXML(){
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Config.class);
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.generate();
}
一个通知具有多个切点表达式举例——用逻辑运算符||进行拼接:
package com.xxx.spring6.biz;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class SecurityAspect {
@Pointcut("execution(* com.powernode.spring6.biz..save*(..))")
public void savePointcut(){}
@Pointcut("execution(* com.powernode.spring6.biz..delete*(..))")
public void deletePointcut(){}
@Pointcut("execution(* com.powernode.spring6.biz..modify*(..))")
public void modifyPointcut(){}
@Before("savePointcut() || deletePointcut() || modifyPointcut()")
public void beforeAdivce(JoinPoint joinpoint){
System.out.println("XXX操作员正在操作"+joinpoint.getSignature().getName()+"方法");
}
}